From 73bffcc8f7b43314f4c03c76d828a50befded71c Mon Sep 17 00:00:00 2001 From: Andrei Duvakin Date: Fri, 7 Feb 2025 10:52:06 +0500 Subject: [PATCH] =?UTF-8?q?=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80=D0=B0=D1=86=D0=B8=D1=8E=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=B8=20=D0=B2=D1=8B=D0=B4=D0=B0=D1=87=D1=83=20?= =?UTF-8?q?=D1=82=D0=BE=D0=BA=D0=B5=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/app/application/roles_repository.py | 19 +++++++ api/app/application/users_repository.py | 2 +- api/app/controllers/auth_routes.py | 8 +-- api/app/controllers/register_routes.py | 10 ++-- api/app/database/session.py | 2 +- api/app/domain/entities/user.py | 14 +++++ api/app/infrastructure/auth_service.py | 4 +- api/app/infrastructure/user_service.py | 76 +++++++++++++++++++++++++ 8 files changed, 123 insertions(+), 12 deletions(-) create mode 100644 api/app/application/roles_repository.py create mode 100644 api/app/domain/entities/user.py create mode 100644 api/app/infrastructure/user_service.py diff --git a/api/app/application/roles_repository.py b/api/app/application/roles_repository.py new file mode 100644 index 0000000..8409951 --- /dev/null +++ b/api/app/application/roles_repository.py @@ -0,0 +1,19 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.future import select + +from app.domain.models import Role + + +class RolesRepository: + def __init__(self, db: AsyncSession): + self.db = db + + async def get_all(self): + stmt = select(Role) + result = await self.db.execute(stmt) + return result.scalars().all() + + async def get_by_id(self, role_id: int): + stmt = select(Role).filter(Role.id == role_id) + result = await self.db.execute(stmt) + return result.scalars().first() diff --git a/api/app/application/users_repository.py b/api/app/application/users_repository.py index fbc0fd3..ce7caff 100644 --- a/api/app/application/users_repository.py +++ b/api/app/application/users_repository.py @@ -28,7 +28,7 @@ class UsersRepository: result = await self.db.execute(stmt) return result.scalars().first() - async def create(self, user: User): + async def create(self, user: User) -> User: self.db.add(user) await self.db.commit() await self.db.refresh(user) diff --git a/api/app/controllers/auth_routes.py b/api/app/controllers/auth_routes.py index 44ede53..be33d8b 100644 --- a/api/app/controllers/auth_routes.py +++ b/api/app/controllers/auth_routes.py @@ -11,9 +11,9 @@ router = APIRouter() @router.post( "/login/", response_model=dict, - responses={401: {"description": "Неверный логин или пароль"}}, - summary="Аутентификация пользователя", - description="Производит вход пользователя и выдает `access_token` в `cookie`.", + responses={401: {"description": "Invalid username or password"}}, + summary="User authentication", + description="Logs in the user and outputs the `access_token` in the `cookie'", ) async def auth_user( response: Response, @@ -29,7 +29,7 @@ async def auth_user( if check is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, - detail="Неверный логин или пароль", + detail="Invalid username or password", ) access_token = auth_service.create_access_token({"sub": str(check.id)}) diff --git a/api/app/controllers/register_routes.py b/api/app/controllers/register_routes.py index ef686a8..7fdb5d8 100644 --- a/api/app/controllers/register_routes.py +++ b/api/app/controllers/register_routes.py @@ -4,6 +4,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.database.session import get_db from app.domain.entities.register import RegisterEntity from app.infrastructure.auth_service import AuthService +from app.infrastructure.user_service import UserService router = APIRouter() @@ -11,12 +12,13 @@ router = APIRouter() @router.post( "/register/", response_model=dict, - summary="Регистрация пользователя", - description="Производит регистрацию пользователя в системе.", + summary="User Registration", + description="Performs user registration in the system", ) async def register_user( - response: Response, user_data: RegisterEntity, db: AsyncSession = Depends(get_db) ): - return {} + user_service = UserService(db) + user = await user_service.register_user(user_data) + return user.model_dump() diff --git a/api/app/database/session.py b/api/app/database/session.py index 7e0a76e..5b37650 100644 --- a/api/app/database/session.py +++ b/api/app/database/session.py @@ -3,7 +3,7 @@ from sqlalchemy.orm import sessionmaker from app.settings import settings -engine = create_async_engine(settings.DATABASE_URL, echo=True) +engine = create_async_engine(settings.DATABASE_URL, echo=False) async_session_maker = sessionmaker( bind=engine, class_=AsyncSession, expire_on_commit=False diff --git a/api/app/domain/entities/user.py b/api/app/domain/entities/user.py new file mode 100644 index 0000000..8ab8c37 --- /dev/null +++ b/api/app/domain/entities/user.py @@ -0,0 +1,14 @@ +from typing import Optional + +from pydantic import BaseModel, Field + + +class UserEntity(BaseModel): + id: int = Field(..., example=1) + first_name: str = Field(..., example='Ivan') + last_name: str = Field(..., example='Ivanov') + patronymic: Optional[str] = Field(None, example='Ivanov') + login: str = Field(..., example='user@example.com') + + role_id: int = Field(..., example=1) + diff --git a/api/app/infrastructure/auth_service.py b/api/app/infrastructure/auth_service.py index 10d7b2e..3acd40a 100644 --- a/api/app/infrastructure/auth_service.py +++ b/api/app/infrastructure/auth_service.py @@ -9,10 +9,10 @@ from app.settings import get_auth_data class AuthService: def __init__(self, db: AsyncSession): - self.repository = UsersRepository(db) + self.user_repository = UsersRepository(db) async def authenticate_user(self, login: str, password: str): - user = await self.repository.get_by_login(login) + user = await self.user_repository.get_by_login(login) if not user: return None diff --git a/api/app/infrastructure/user_service.py b/api/app/infrastructure/user_service.py new file mode 100644 index 0000000..8f991de --- /dev/null +++ b/api/app/infrastructure/user_service.py @@ -0,0 +1,76 @@ +import re + +from fastapi import HTTPException, status +from sqlalchemy.ext.asyncio import AsyncSession + +from app.application.roles_repository import RolesRepository +from app.application.users_repository import UsersRepository +from app.domain.entities.register import RegisterEntity +from app.domain.entities.user import UserEntity +from app.domain.models import User + + +class UserService: + def __init__(self, db: AsyncSession): + self.user_repository = UsersRepository(db) + self.role_repository = RolesRepository(db) + + async def register_user(self, register_entity: RegisterEntity) -> UserEntity: + role = await self.role_repository.get_by_id(register_entity.role_id) + if not role: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail='The role with this ID was not found' + ) + + user = await self.user_repository.get_by_login(register_entity.login) + if user: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail='Such a login already exists' + ) + + if not self.is_strong_password(register_entity.password): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Password is too weak. It must contain at least 8 characters, including an uppercase letter, a lowercase letter, a digit, and a special character." + ) + + user_model = User( + first_name=register_entity.first_name, + last_name=register_entity.last_name, + patronymic=register_entity.patronymic, + login=register_entity.login, + role_id=register_entity.role_id, + ) + user_model.set_password(register_entity.password) + + created_user = await self.user_repository.create(user_model) + + return UserEntity( + id=created_user.id, + first_name=created_user.first_name, + last_name=created_user.last_name, + patronymic=created_user.patronymic, + login=created_user.login, + role_id=created_user.role_id, + ) + + @staticmethod + def is_strong_password(password: str) -> bool: + if len(password) < 8: + return False + + if not re.search(r"[A-Z]", password): + return False + + if not re.search(r"[a-z]", password): + return False + + if not re.search(r"\d", password): + return False + + if not re.search(r"[!@#$%^&*(),.?\":{}|<>]", password): + return False + + return True