From dedf89202e08cd8dbebfc9c2517af82a589a5ad5 Mon Sep 17 00:00:00 2001 From: Ivan Koldakov Date: Mon, 22 Jan 2024 22:00:32 +0100 Subject: [PATCH] Add endpoint to add users --- app/main.py | 6 ++++ app/routers/users.py | 34 ++++++++++++++++++++++ app/services/users.py | 65 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 app/routers/users.py create mode 100644 app/services/users.py diff --git a/app/main.py b/app/main.py index 918921c..8a9c72c 100644 --- a/app/main.py +++ b/app/main.py @@ -13,6 +13,7 @@ from app.routers.notifications import router as notifications_router from app.routers.root import router as root_router from app.routers.seasons import router as seasons_router +from app.routers.users import router as users_router mimetypes.add_type("image/webp", ".webp") @@ -61,6 +62,11 @@ prefix="/api", include_in_schema=False, ) +app.include_router( + users_router, + tags=["users"], + prefix="/api", +) app.mount("/static", StaticFiles(directory="static"), name="static") diff --git a/app/routers/users.py b/app/routers/users.py new file mode 100644 index 0000000..9d1d25e --- /dev/null +++ b/app/routers/users.py @@ -0,0 +1,34 @@ +from fastapi import APIRouter, Depends, status +from sqlalchemy.ext.asyncio.session import AsyncSession + +from app.repositories.sessions import get_async_session +from app.services.users import ( + User, + UserAdd, + process_add_user, +) + +router = APIRouter(prefix="/users") + + +@router.post( + "/", + status_code=status.HTTP_201_CREATED, + response_model=User, + name="user", +) +async def add_user( + body: UserAdd, + session: AsyncSession = Depends(get_async_session), +) -> User: + """Create User. + + The user add endpoint is an API function allowing the creation of new user accounts. + It receives user details via HTTP requests, validates the information, + and stores it in the system's database. + This endpoint is essential for user registration and onboarding. + + Please note that currently endpoint is not protected. + However, if there are a lot of spam requests, the endpoint will be blocked or limited. + """ + return await process_add_user(body, session) diff --git a/app/services/users.py b/app/services/users.py new file mode 100644 index 0000000..c598a58 --- /dev/null +++ b/app/services/users.py @@ -0,0 +1,65 @@ +from datetime import datetime + +from fastapi import HTTPException, status +from pydantic import BaseModel, ConfigDict, EmailStr, Field +from sqlalchemy.ext.asyncio.session import AsyncSession + +from app.repositories.models import User as UserModel, UserAlreadyExists +from app.services.hashers import hasher + + +class UserBase(BaseModel): + name: str = Field( + min_length=1, + max_length=64, + ) + surname: str = Field( + min_length=1, + max_length=64, + ) + middle_name: str | None = Field( + default=None, + alias="middleName", + min_length=1, + max_length=64, + ) + email: EmailStr + username: str = Field( + min_length=1, + max_length=64, + ) + password: str = Field( + min_length=8, + max_length=128, + ) + is_subscribed: bool = Field( + default=True, + alias="isSubscribed", + ) + + model_config = ConfigDict( + from_attributes=True, + populate_by_name=True, + ) + + +class UserAdd(UserBase): + ... + + +class User(UserBase): + id: int + is_confirmed: bool = Field(alias="isConfirmed") + created_at: datetime = Field(alias="createdAt") + + +async def process_add_user(body: UserAdd, session: AsyncSession, /): + body.password = hasher.encode(body.password) + try: + user: UserModel = await UserModel.add(session, body) + except UserAlreadyExists: + raise HTTPException( + detail="User with username or email already exists", + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + ) + return User.model_validate(user)