from fastapi import APIRouter, Depends, status from app.config import settings from app.exceptions import UserNotFoundException from app.users.exceptions import ( UserAlreadyExistsException, IncorrectAuthDataException, PasswordsMismatchException, WrongCodeException, ) from app.services.redis_service import RedisService, get_redis_session from app.unit_of_work import UnitOfWork from app.utils.auth import ( get_password_hash, create_access_token, AuthService, verify_password, decode_confirmation_token ) from app.users.dependencies import get_current_user from app.users.schemas import ( SUserLogin, SUserRegister, SUserResponse, SEmailVerification, SUserAvatars, SUserFilter, SUser, SUserChangeData, SUserSendConfirmationCode, STokenLogin, SUsers, SConfirmationData, ) from app.tasks.tasks import ( send_registration_confirmation_email, send_data_change_confirmation_email, generate_confirmation_code, send_data_change_email ) router = APIRouter(prefix="/users", tags=["Пользователи"]) @router.get( "", status_code=status.HTTP_200_OK, response_model=SUsers, ) async def get_all_users(uow=Depends(UnitOfWork)): async with uow: users = await uow.user.find_all() return users @router.post( "/check_existing_user", status_code=status.HTTP_200_OK, response_model=None, ) async def check_existing_user(user_filter: SUserFilter, uow=Depends(UnitOfWork)): async with uow: try: await uow.user.find_one(**user_filter.model_dump(exclude_none=True)) raise UserAlreadyExistsException except UserNotFoundException: pass @router.post( "/register", status_code=status.HTTP_201_CREATED, response_model=STokenLogin, ) async def register_user(user_data: SUserRegister, uow=Depends(UnitOfWork)): if user_data.password != user_data.password2: raise PasswordsMismatchException hashed_password = get_password_hash(user_data.password) async with uow: user = await uow.user.add( email=user_data.email, hashed_password=hashed_password, username=user_data.username, date_of_birth=user_data.date_of_birth, ) await uow.user.add_user_avatar(user_id=user.id, avatar=str(user.avatar_image)) await uow.commit() user_code = generate_confirmation_code() user_mail_data = SConfirmationData.model_validate( {"user_id": user.id, "username": user_data.username, "email_to": user_data.email, "confirmation_code": user_code} ) send_registration_confirmation_email.delay(user_mail_data.model_dump()) redis_session = get_redis_session() await RedisService.set_verification_code(redis=redis_session, user_id=user.id, verification_code=user_code) access_token = create_access_token({"sub": str(user.id)}) return {"authorization": f"Bearer {access_token}"} @router.get( "/email_verification/{user_code}", status_code=status.HTTP_200_OK, response_model=SEmailVerification, ) async def email_verification(user_code: str, uow=Depends(UnitOfWork)): user_data = decode_confirmation_token(user_code) redis_session = get_redis_session() async with uow: verification_code = await RedisService.get_verification_code(redis=redis_session, user_id=user_data.user_id) if verification_code != user_data.confirmation_code: raise WrongCodeException await uow.user.change_data(user_id=user_data.user_id, role=settings.VERIFICATED_USER) await uow.commit() return {"email_verification": True} @router.post( "/login", status_code=status.HTTP_200_OK, response_model=STokenLogin, ) async def login_user(user_data: SUserLogin, uow=Depends(UnitOfWork)): user = await AuthService.authenticate_user(uow, user_data.email_or_username, user_data.password) access_token = create_access_token({"sub": str(user.id)}) return {"authorization": f"Bearer {access_token}"} @router.get( "/me", status_code=status.HTTP_200_OK, response_model=SUserResponse, ) async def get_user(current_user: SUser = Depends(get_current_user)): return current_user @router.get( "/avatars", status_code=status.HTTP_200_OK, response_model=SUserAvatars, ) async def get_user_avatars_history(user=Depends(get_current_user), uow=Depends(UnitOfWork)): async with uow: return await uow.user.get_user_avatars(user_id=user.id) @router.post( "/send_confirmation_code", status_code=status.HTTP_200_OK, response_model=None, ) async def send_confirmation_code(user_data: SUserSendConfirmationCode, user: SUser = Depends(get_current_user)): redis_session = get_redis_session() if not verify_password(user_data.current_password, user.hashed_password): raise IncorrectAuthDataException user_code = generate_confirmation_code() user_mail_data = SConfirmationData.model_validate( {"user_id": user.id, "username": user_data.username, "email_to": user_data.email, "confirmation_code": user_code} ) send_data_change_confirmation_email.delay(user_mail_data) await RedisService.set_verification_code(redis=redis_session, user_id=user.id, verification_code=user_code) @router.post( "/change_data", status_code=status.HTTP_200_OK, response_model=None, ) async def change_user_data(user_data: SUserChangeData, user=Depends(get_current_user), uow=Depends(UnitOfWork)): redis_session = get_redis_session() verification_code = await RedisService.get_verification_code(redis=redis_session, user_id=user.id) if verification_code != user_data.verification_code: raise WrongCodeException if user_data.new_password: hashed_password = get_password_hash(user_data.new_password) else: hashed_password = user.hashed_password async with uow: await uow.user.change_data( user_id=user.id, email=user_data.email, username=user_data.username, avatar_url=user_data.avatar_url, hashed_password=hashed_password ) await uow.user.add_user_avatar(user_id=user.id, avatar=user_data.avatar_url) await uow.commit() send_data_change_email.delay(user_data.username, user_data.email) await RedisService.delete_verification_code(redis=redis_session, user_id=user.id)