From 2903330a2f25ad8cbf2994b53d606193d4f948eb Mon Sep 17 00:00:00 2001 From: andrei Date: Tue, 29 Apr 2025 21:42:34 +0500 Subject: [PATCH] =?UTF-8?q?=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20CRUD=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20profiles,=20=D0=B0=20=D1=82=D0=B0=D0=BA?= =?UTF-8?q?=D0=B6=D0=B5=20=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20=D1=81?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=83=20=D0=BF=D0=B0=D1=80=D0=BE=D0=BB=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- API/app/application/profiles_repository.py | 20 ++- API/app/application/users_repository.py | 5 + API/app/contollers/profiles_router.py | 57 +++++++++ API/app/contollers/users_router.py | 27 +++++ API/app/infrastructure/dependencies.py | 6 +- API/app/infrastructure/profiles_service.py | 135 +++++++++++++++++++++ API/app/infrastructure/users_service.py | 27 +++++ API/app/main.py | 8 +- 8 files changed, 280 insertions(+), 5 deletions(-) create mode 100644 API/app/contollers/profiles_router.py create mode 100644 API/app/contollers/users_router.py create mode 100644 API/app/infrastructure/profiles_service.py diff --git a/API/app/application/profiles_repository.py b/API/app/application/profiles_repository.py index 49bb390..29af714 100644 --- a/API/app/application/profiles_repository.py +++ b/API/app/application/profiles_repository.py @@ -1,3 +1,6 @@ +from typing import Optional + +from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.domain.models import Profile @@ -7,8 +10,23 @@ class ProfilesRepository: def __init__(self, db: AsyncSession): self.db = db + async def get_by_id(self, profile_id: int) -> Optional[Profile]: + stmt = select(Profile).filter_by(id=profile_id) + result = await self.db.execute(stmt) + return result.scalars().first() + async def create(self, profile: Profile) -> Profile: self.db.add(profile) await self.db.commit() await self.db.refresh(profile) - return profile \ No newline at end of file + return profile + + async def update(self, profile: Profile) -> Profile: + await self.db.merge(profile) + await self.db.commit() + return profile + + async def delete(self, profile: Profile) -> Profile: + await self.db.delete(profile) + await self.db.commit() + return profile diff --git a/API/app/application/users_repository.py b/API/app/application/users_repository.py index ae283c9..09f832f 100644 --- a/API/app/application/users_repository.py +++ b/API/app/application/users_repository.py @@ -40,3 +40,8 @@ class UsersRepository: await self.db.commit() await self.db.refresh(user) return user + + async def update(self, user: User) -> User: + await self.db.merge(user) + await self.db.commit() + return user diff --git a/API/app/contollers/profiles_router.py b/API/app/contollers/profiles_router.py new file mode 100644 index 0000000..40989d3 --- /dev/null +++ b/API/app/contollers/profiles_router.py @@ -0,0 +1,57 @@ +from typing import Optional + +from fastapi import APIRouter, Depends +from sqlalchemy.ext.asyncio import AsyncSession + +from app.database.session import get_db +from app.domain.entities.profile import ProfileEntity +from app.infrastructure.dependencies import get_current_user, require_admin +from app.infrastructure.profiles_service import ProfilesService + +router = APIRouter() + + +@router.post( + '/', + response_model=Optional[ProfileEntity], + summary='Create a new profile', + description='Creates a new profile', +) +async def create_team( + profile: ProfileEntity, + db: AsyncSession = Depends(get_db), + user=Depends(require_admin), +): + profiles_service = ProfilesService(db) + return await profiles_service.create_profile(profile) + + +@router.put( + '/{profile_id}/', + response_model=Optional[ProfileEntity], + summary='Update a profile', + description='Updates a profile', +) +async def create_team( + profile_id: int, + profile: ProfileEntity, + db: AsyncSession = Depends(get_db), + user=Depends(require_admin), +): + profiles_service = ProfilesService(db) + return await profiles_service.update_profile(profile_id, profile, user) + + +@router.delete( + '/{profile_id}/', + response_model=Optional[ProfileEntity], + summary='Delete a profile', + description='Delete a profile', +) +async def create_team( + profile_id: int, + db: AsyncSession = Depends(get_db), + user=Depends(require_admin), +): + profiles_service = ProfilesService(db) + return await profiles_service.delete(profile_id, user) diff --git a/API/app/contollers/users_router.py b/API/app/contollers/users_router.py new file mode 100644 index 0000000..5b360aa --- /dev/null +++ b/API/app/contollers/users_router.py @@ -0,0 +1,27 @@ +from typing import Optional + +from fastapi import APIRouter, Depends +from sqlalchemy.ext.asyncio import AsyncSession + +from app.database.session import get_db +from app.domain.entities.user import UserEntity +from app.infrastructure.dependencies import require_admin +from app.infrastructure.users_service import UsersService + +router = APIRouter() + + +@router.put( + '/{user_id}/', + response_model=Optional[UserEntity], + summary='Change user password', + description='Change user password', +) +async def create_team( + user_id: int, + new_password: str, + db: AsyncSession = Depends(get_db), + user=Depends(require_admin), +): + users_service = UsersService(db) + return await users_service.change_user_password(user_id, new_password) diff --git a/API/app/infrastructure/dependencies.py b/API/app/infrastructure/dependencies.py index e7a6973..75a7fc4 100644 --- a/API/app/infrastructure/dependencies.py +++ b/API/app/infrastructure/dependencies.py @@ -1,3 +1,5 @@ +from typing import Optional + import jwt from fastapi import Depends, HTTPException, Security from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials @@ -15,7 +17,7 @@ security = HTTPBearer() async def get_current_user( credentials: HTTPAuthorizationCredentials = Security(security), db: AsyncSession = Depends(get_db) -): +) -> Optional[User]: auth_data = get_auth_data() try: @@ -36,7 +38,7 @@ async def get_current_user( return user -def require_admin(user: User = Depends(get_current_user)): +def require_admin(user: User = Depends(get_current_user)) -> Optional[User]: if user.profile.role.title != "Администратор": raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied") diff --git a/API/app/infrastructure/profiles_service.py b/API/app/infrastructure/profiles_service.py new file mode 100644 index 0000000..71422d9 --- /dev/null +++ b/API/app/infrastructure/profiles_service.py @@ -0,0 +1,135 @@ +from typing import Optional + +from fastapi import HTTPException, status +from sqlalchemy.ext.asyncio import AsyncSession + +from app.application.profiles_repository import ProfilesRepository +from app.application.roles_repository import RolesRepository +from app.application.teams_repository import TeamsRepository +from app.application.users_repository import UsersRepository +from app.domain.entities.profile import ProfileEntity +from app.domain.models import Profile, User + + +class ProfilesService: + def __init__(self, db: AsyncSession): + self.profiles_repository = ProfilesRepository(db) + self.teams_repository = TeamsRepository(db) + self.roles_repository = RolesRepository(db) + self.users_repository = UsersRepository(db) + + async def create_profile(self, profile: ProfileEntity) -> Optional[ProfileEntity]: + team = await self.teams_repository.get_by_id(profile.team_id) + if team is None: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="The team with this ID was not found", + ) + + role = await self.roles_repository.get_by_id(profile.role_id) + if role is None: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="The role with this ID was not found", + ) + + profile_model = self.entity_to_model(profile) + + profile_model = await self.profiles_repository.create(profile_model) + + return self.model_to_entity(profile_model) + + async def update_profile(self, profile_id: int, profile: ProfileEntity, user: User) -> Optional[ + ProfileEntity + ]: + user = await self.users_repository.get_by_id(user.id) + if user is None: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="The user with this ID was not found", + ) + + profile_model = await self.profiles_repository.get_by_id(profile_id) + if profile_model.id != user.profile_id and user.profile.role.title != 'Администратор': + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Permission denied", + ) + + team = await self.teams_repository.get_by_id(profile.team_id) + if team is None: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="The team with this ID was not found", + ) + + role = await self.roles_repository.get_by_id(profile.role_id) + if role is None: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="The role with this ID was not found", + ) + + profile_model.first_name = profile.first_name + profile_model.last_name = profile.last_name + profile_model.patronymic = profile.patronymic + profile_model.birthday = profile.birthday + profile_model.email = profile.email + profile_model.phone = profile.phone + profile_model.role_id = profile.role_id + profile_model.team_id = profile.team_id + + profile_model = await self.profiles_repository.update(profile_model) + + return self.model_to_entity(profile_model) + + async def delete(self, profile_id: int, user: User): + user = await self.users_repository.get_by_id(user.id) + if user is None: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="The user with this ID was not found", + ) + + profile_model = await self.profiles_repository.get_by_id(profile_id) + if profile_model.id != user.profile_id and user.profile.role.title != 'Администратор': + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Permission denied", + ) + + result = await self.profiles_repository.delete(profile_model) + + return self.model_to_entity(result) + + @staticmethod + def model_to_entity(profile_model: Profile) -> ProfileEntity: + return ProfileEntity( + id=profile_model.id, + first_name=profile_model.first_name, + last_name=profile_model.last_name, + patronymic=profile_model.patronymic, + birthday=profile_model.birthday, + email=profile_model.email, + phone=profile_model.phone, + role_id=profile_model.role_id, + team_id=profile_model.team_id, + ) + + @staticmethod + def entity_to_model(profile_entity: ProfileEntity) -> Profile: + profile_model = Profile( + first_name=profile_entity.first_name, + last_name=profile_entity.last_name, + patronymic=profile_entity.patronymic, + birthday=profile_entity.birthday, + email=profile_entity.email, + phone=profile_entity.phone, + role_id=profile_entity.role_id, + team_id=profile_entity.team_id, + ) + + if profile_entity.id is not None: + profile_model.id = profile_entity.id + + return profile_model diff --git a/API/app/infrastructure/users_service.py b/API/app/infrastructure/users_service.py index ca494b2..9ec57ec 100644 --- a/API/app/infrastructure/users_service.py +++ b/API/app/infrastructure/users_service.py @@ -35,6 +35,12 @@ class UsersService: detail="The role with this ID was not found", ) + if not self.is_strong_password(register_entity.password): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="The password is too weak", + ) + user_model, profile_model = self.register_entity_to_models(register_entity) profile_model = await self.profiles_repository.create(profile_model) @@ -46,6 +52,27 @@ class UsersService: return user_entity + async def change_user_password(self, user_id: int, new_password: str) -> Optional[UserEntity]: + user_model = await self.users_repository.get_by_id(user_id) + + if user_model is None: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="The user with this ID was not found", + ) + + if not self.is_strong_password(new_password): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="The password is too weak", + ) + + user_model.set_password(new_password) + + user_model = await self.users_repository.update(user_model) + + return self.user_model_to_entity(user_model) + @staticmethod def is_strong_password(password): if len(password) < 8: diff --git a/API/app/main.py b/API/app/main.py index c39fefc..714157a 100644 --- a/API/app/main.py +++ b/API/app/main.py @@ -1,9 +1,11 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from app.contollers.register_router import router as register_router from app.contollers.auth_router import router as auth_router +from app.contollers.profiles_router import router as profiles_router +from app.contollers.register_router import router as register_router from app.contollers.teams_router import router as team_router +from app.contollers.users_router import router as users_router from app.settings import settings @@ -18,9 +20,11 @@ def start_app(): allow_headers=["*"], ) - api_app.include_router(register_router, prefix=f'{settings.PREFIX}/register', tags=['register']) api_app.include_router(auth_router, prefix=f'{settings.PREFIX}/auth', tags=['auth']) + api_app.include_router(profiles_router, prefix=f'{settings.PREFIX}/profiles', tags=['profiles']) + api_app.include_router(register_router, prefix=f'{settings.PREFIX}/register', tags=['register']) api_app.include_router(team_router, prefix=f'{settings.PREFIX}/teams', tags=['teams']) + api_app.include_router(users_router, prefix=f'{settings.PREFIX}/users', tags=['users']) return api_app