mirror of
https://github.com/grey-cat-1908/formaptix-server.git
synced 2024-09-22 19:52:00 +03:00
иногда лучше закоммитить на всякий
This commit is contained in:
parent
7e4d9b7f77
commit
d9cf862040
13 changed files with 137 additions and 7 deletions
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Viktor K. (mrkrk)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -2,6 +2,8 @@ from models import settings
|
||||||
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
|
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
|
||||||
from sqlalchemy.orm import DeclarativeBase
|
from sqlalchemy.orm import DeclarativeBase
|
||||||
|
|
||||||
|
from .user import *
|
||||||
|
|
||||||
engine = create_async_engine(settings.database)
|
engine = create_async_engine(settings.database)
|
||||||
sessions = async_sessionmaker(engine)
|
sessions = async_sessionmaker(engine)
|
||||||
|
|
||||||
|
|
11
database/user.py
Normal file
11
database/user.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
|
from database import Base
|
||||||
|
|
||||||
|
|
||||||
|
class User(Base):
|
||||||
|
__tablename__ = "users"
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
|
username: Mapped[str] = mapped_column(unique=True)
|
||||||
|
password: Mapped[str]
|
||||||
|
salt: Mapped[str]
|
|
@ -9,6 +9,8 @@ services:
|
||||||
- DATABASE=postgresql+asyncpg://${DATABASE_USER}:${DATABASE_PASSWORD}@inputly-database:5432/${DATABASE_NAME}
|
- DATABASE=postgresql+asyncpg://${DATABASE_USER}:${DATABASE_PASSWORD}@inputly-database:5432/${DATABASE_NAME}
|
||||||
- SECRET=${SECRET}
|
- SECRET=${SECRET}
|
||||||
- PORT=${PORT}
|
- PORT=${PORT}
|
||||||
|
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
||||||
|
- DISABLE_ADMIN=${DISABLE_ADMIN}
|
||||||
ports:
|
ports:
|
||||||
- ${PORT}:${PORT}
|
- ${PORT}:${PORT}
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
from fastapi import APIRouter
|
|
||||||
|
|
||||||
router = APIRouter()
|
|
4
main.py
4
main.py
|
@ -6,7 +6,7 @@ from fastapi import FastAPI
|
||||||
|
|
||||||
import models
|
import models
|
||||||
import database
|
import database
|
||||||
import endpoints
|
import routes
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
@ -19,6 +19,6 @@ async def lifespan(app: FastAPI):
|
||||||
yield
|
yield
|
||||||
|
|
||||||
app = FastAPI(lifespan=lifespan)
|
app = FastAPI(lifespan=lifespan)
|
||||||
app.include_router(endpoints.router)
|
app.include_router(routes.router)
|
||||||
|
|
||||||
uvicorn.run(app, host="0.0.0.0", port=models.settings.port)
|
uvicorn.run(app, host="0.0.0.0", port=models.settings.port)
|
||||||
|
|
|
@ -1 +1,10 @@
|
||||||
from .settings import *
|
import pydantic
|
||||||
|
|
||||||
|
|
||||||
|
class BaseModel(pydantic.BaseModel):
|
||||||
|
class Config:
|
||||||
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
|
from .settings import settings
|
||||||
|
from .user import *
|
||||||
|
|
|
@ -5,6 +5,8 @@ class Settings(BaseSettings):
|
||||||
database: str
|
database: str
|
||||||
secret: str
|
secret: str
|
||||||
port: int
|
port: int
|
||||||
|
admin_password: str
|
||||||
|
disable_admin: bool
|
||||||
|
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
|
|
19
models/user.py
Normal file
19
models/user.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
from pydantic import Field
|
||||||
|
|
||||||
|
from models import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class User(BaseModel):
|
||||||
|
id: int
|
||||||
|
username: str
|
||||||
|
|
||||||
|
|
||||||
|
class Auth(BaseModel):
|
||||||
|
username: str
|
||||||
|
password: str
|
||||||
|
|
||||||
|
|
||||||
|
class Token(BaseModel):
|
||||||
|
id: int
|
||||||
|
username: str
|
||||||
|
token: str
|
|
@ -3,7 +3,7 @@ name = "inputly-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Simple forms service backend"
|
description = "Simple forms service backend"
|
||||||
authors = ["Viktor K. <tech@arbuz.icu>"]
|
authors = ["Viktor K. <tech@arbuz.icu>"]
|
||||||
license = "APGL-3.0-only"
|
license = "MIT"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
|
|
7
routes/__init__.py
Normal file
7
routes/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from . import admin
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
router.include_router(admin.router)
|
52
routes/admin.py
Normal file
52
routes/admin.py
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import secrets
|
||||||
|
from typing import Annotated
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
|
from sqlalchemy import select
|
||||||
|
|
||||||
|
import database
|
||||||
|
from models import settings, user
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/admin")
|
||||||
|
|
||||||
|
|
||||||
|
def hash_password(password: str, salt: str) -> str:
|
||||||
|
return hashlib.sha512((password + salt).encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def verify_admin(token: str):
|
||||||
|
if token != settings.admin_password:
|
||||||
|
raise HTTPException(401, "Unauthorized")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
Admin = Annotated[bool, Depends(verify_admin, use_cache=False)]
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/user")
|
||||||
|
async def create_user(auth: user.Auth, admin_token: Admin):
|
||||||
|
if len(auth.username.strip()) == 0:
|
||||||
|
raise HTTPException(400, "Username must not be empty")
|
||||||
|
if len(auth.password.strip()) == 0:
|
||||||
|
raise HTTPException(400, "Password must not be empty")
|
||||||
|
if settings.disable_admin:
|
||||||
|
raise HTTPException(403, "You are not admin")
|
||||||
|
|
||||||
|
salt = secrets.token_hex(8)
|
||||||
|
|
||||||
|
async with database.sessions.begin() as session:
|
||||||
|
stmt = select(database.User).where(database.User.username == auth.username)
|
||||||
|
db_user = session.execute(stmt).scalar_one_or_none()
|
||||||
|
if db_user is not None:
|
||||||
|
raise HTTPException(400, "User with this username already exists")
|
||||||
|
|
||||||
|
new_user = database.User(
|
||||||
|
username=auth.username,
|
||||||
|
password=hash_password(auth.password, salt),
|
||||||
|
salt=salt,
|
||||||
|
)
|
||||||
|
session.add(new_user)
|
||||||
|
await session.flush()
|
||||||
|
|
||||||
|
return {'status': 'Success'}
|
8
routes/user.py
Normal file
8
routes/user.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
from fastapi import APIRouter
|
||||||
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
|
|
||||||
|
import database
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/user")
|
Loading…
Reference in a new issue