From f4448a5402c0a3e2981e1e217c6e018a106e43fc Mon Sep 17 00:00:00 2001 From: urec56 Date: Tue, 11 Jun 2024 18:35:56 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D1=87=D0=B8=D1=81=D1=82=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BA=D0=BE=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/chat/router.py | 4 +- app/chat/shemas.py | 2 +- app/dao/chat.py | 2 +- app/dao/user.py | 137 ++++++++++++++++++-------------------- app/users/dependencies.py | 4 -- app/users/router.py | 16 +++-- app/users/schemas.py | 4 +- app/utils/auth.py | 41 ++++++------ 8 files changed, 99 insertions(+), 111 deletions(-) diff --git a/app/chat/router.py b/app/chat/router.py index a316feb..fcff0eb 100644 --- a/app/chat/router.py +++ b/app/chat/router.py @@ -29,8 +29,8 @@ router = APIRouter(prefix="/chat", tags=["Чат"]) ) async def get_all_chats(user: SUser = Depends(check_verificated_user_with_exc), uow=Depends(UnitOfWork)): async with uow: - result = await uow.user.get_user_allowed_chats(user.id) - return {"allowed_chats": result} + allowed_chats = await uow.user.get_user_allowed_chats(user.id) + return allowed_chats @router.post( diff --git a/app/chat/shemas.py b/app/chat/shemas.py index eea6624..fa30283 100644 --- a/app/chat/shemas.py +++ b/app/chat/shemas.py @@ -38,7 +38,7 @@ class SChat(BaseModel): class SAllowedChats(BaseModel): - allowed_chats: list[SChat | None] + allowed_chats: list[SChat] | None class SPinnedChats(BaseModel): diff --git a/app/dao/chat.py b/app/dao/chat.py index d693626..040fa30 100644 --- a/app/dao/chat.py +++ b/app/dao/chat.py @@ -95,7 +95,7 @@ class ChatDAO(BaseDAO): raise MessageNotFoundException async def delete_message(self, message_id: int) -> None: - stmt = update(Message).where(Message.id == message_id).values(visibility=False) + stmt = update(Message).values(visibility=False).where(Message.id == message_id) await self.session.execute(stmt) async def get_some_messages(self, chat_id: int, message_number_from: int, messages_to_get: int) -> SMessageList: diff --git a/app/dao/user.py b/app/dao/user.py index 4afb19a..a42b3dc 100644 --- a/app/dao/user.py +++ b/app/dao/user.py @@ -1,10 +1,11 @@ from pydantic import HttpUrl from sqlalchemy import update, select, insert, func -from sqlalchemy.exc import MultipleResultsFound, IntegrityError +from sqlalchemy.exc import MultipleResultsFound, IntegrityError, NoResultFound +from app.chat.shemas import SAllowedChats from app.dao.base import BaseDAO from app.database import engine # noqa -from app.exceptions import IncorrectDataException +from app.exceptions import IncorrectDataException, UserNotFoundException from app.users.exceptions import UserAlreadyExistsException from app.models.chat import Chat from app.models.user_avatar import UserAvatar @@ -16,96 +17,86 @@ from app.users.schemas import SUser, SUserAvatars, SUsers class UserDAO(BaseDAO): model = Users - async def add(self, **data) -> int: + @staticmethod + def check_query_compile(query): + print(query.compile(engine, compile_kwargs={"literal_binds": True})) # Проверка SQL запроса + + async def add(self, **data) -> SUser: try: - stmt = insert(self.model).values(**data).returning(Users.id) + stmt = insert(self.model).values(**data).returning(Users.__table__.columns) result = await self.session.execute(stmt) - return result.scalar() + result = result.mappings().one() + return SUser.model_validate(result) except IntegrityError: raise UserAlreadyExistsException - async def find_one_or_none(self, **filter_by) -> SUser | None: + async def find_one_or_none(self, **filter_by) -> SUser: try: query = select(Users.__table__.columns).filter_by(**filter_by) result = await self.session.execute(query) - result = result.mappings().one_or_none() - if result: - return SUser.model_validate(result) + result = result.mappings().one() + return SUser.model_validate(result) except MultipleResultsFound: raise IncorrectDataException + except NoResultFound: + raise UserNotFoundException async def find_all(self) -> SUsers: - query = select(Users.__table__.columns).where(Users.role != 100) - result = await self.session.execute(query) - return SUsers.model_validate({"users": result.mappings().all()}) - - async def change_data(self, user_id: int, **data_to_change) -> str: - query = update(Users).where(Users.id == user_id).values(**data_to_change).returning(Users.username) - result = await self.session.execute(query) - return result.scalar() - - async def get_user_role(self, user_id: int) -> int: - query = select(Users.role).where(Users.id == user_id) - result = await self.session.execute(query) - return result.scalar() - - async def get_user_allowed_chats(self, user_id: int): - """ - WITH chats_with_descriptions AS ( - SELECT * - FROM usersxchats - LEFT JOIN chats ON usersxchats.chat_id = chats.id - ), - - chats_with_avatars as ( - SELECT * - FROM chats_with_descriptions - LEFT JOIN users ON chats_with_descriptions.user_id = users.id - ) - - SELECT chat_id, chat_for, chat_name, avatar_image - FROM chats_with_avatars - WHERE user_id = 1 - """ - chats_with_descriptions = ( - select(UserChat.__table__.columns, Chat.__table__.columns) - .select_from(UserChat) - .join(Chat, UserChat.chat_id == Chat.id) - ).cte("chats_with_descriptions") - - chats_with_avatars = ( - select( - chats_with_descriptions.c.chat_id, - chats_with_descriptions.c.chat_for, - chats_with_descriptions.c.chat_name, - chats_with_descriptions.c.visibility, - Users.id, - Users.avatar_image, - ) - .select_from(chats_with_descriptions) - .join(Users, Users.id == chats_with_descriptions.c.user_id) - .cte("chats_with_avatars") - ) query = ( select( - chats_with_avatars.c.chat_id, - chats_with_avatars.c.chat_for, - chats_with_avatars.c.chat_name, - chats_with_avatars.c.avatar_image, + func.json_build_object( + "users", func.json_agg( + func.json_build_object( + "id", Users.id, + "username", Users.username, + "email", Users.email, + "black_phoenix", Users.black_phoenix, + "avatar_image", Users.avatar_image, + "date_of_birth", Users.date_of_birth, + "date_of_registration", Users.date_of_registration, + ) + ) + ) ) - .select_from(chats_with_avatars) - .where(chats_with_avatars.c.id == user_id, chats_with_avatars.c.visibility == True) # noqa: E712 + .select_from(Users) + .where(Users.role != 100) + ) + result = await self.session.execute(query) + result = result.scalar_one() + return SUsers.model_validate(result) + + async def change_data(self, user_id: int, **data_to_change) -> None: + stmt = update(Users).where(Users.id == user_id).values(**data_to_change) + await self.session.execute(stmt) + + async def get_user_allowed_chats(self, user_id: int) -> SAllowedChats: + + query = ( + select( + func.json_build_object( + "allowed_chats", func.json_agg( + func.json_build_object( + "chat_id", Chat.id, + "chat_for", Chat.chat_for, + "chat_name", Chat.chat_name, + "avatar_image", Users.avatar_image, + ) + ) + ) + ) + .select_from(UserChat) + .join(Chat, Chat.id == UserChat.chat_id) + .join(Users, Users.id == UserChat.user_id) + .where(UserChat.user_id == user_id, Chat.visibility == True) # noqa: E712 ) result = await self.session.execute(query) - result = result.mappings().all() - return result + result = result.scalar_one() + return SAllowedChats.model_validate(result) - async def add_user_avatar(self, user_id: int, avatar: HttpUrl) -> bool: - query = insert(UserAvatar).values(user_id=user_id, avatar_image=avatar) - await self.session.execute(query) - await self.session.commit() - return True + async def add_user_avatar(self, user_id: int, avatar: HttpUrl) -> None: + stmt = insert(UserAvatar).values(user_id=user_id, avatar_image=avatar) + await self.session.execute(stmt) async def get_user_avatars(self, user_id: int) -> SUserAvatars: query = select( diff --git a/app/users/dependencies.py b/app/users/dependencies.py index 2b3ae3e..6837d50 100644 --- a/app/users/dependencies.py +++ b/app/users/dependencies.py @@ -38,8 +38,6 @@ async def get_current_user(token: str = Depends(get_token), uow=Depends(UnitOfWo raise UserNotFoundException user = await UserService.find_one_or_none(uow=uow, user_id=int(user_id)) - if not user: - raise UserNotFoundException return user @@ -69,8 +67,6 @@ async def get_current_user_ws(token: str = Depends(get_token_ws), uow=Depends(Un raise UserNotFoundException user = await UserService.find_one_or_none(uow=uow, user_id=int(user_id)) - if not user: - raise UserNotFoundException return user diff --git a/app/users/router.py b/app/users/router.py index a72882d..1dd6746 100644 --- a/app/users/router.py +++ b/app/users/router.py @@ -1,6 +1,7 @@ from fastapi import APIRouter, Depends, status from app.config import settings +from app.exceptions import UserNotFoundException from app.users.exceptions import ( UserAlreadyExistsException, IncorrectAuthDataException, @@ -58,9 +59,11 @@ async def get_all_users(uow=Depends(UnitOfWork)): ) async def check_existing_user(user_filter: SUserFilter, uow=Depends(UnitOfWork)): async with uow: - user = await uow.user.find_one_or_none(**user_filter.model_dump(exclude_none=True)) - if user: + try: + await uow.user.find_one_or_none(**user_filter.model_dump(exclude_none=True)) raise UserAlreadyExistsException + except UserNotFoundException: + pass @router.post( @@ -74,22 +77,23 @@ async def register_user(user_data: SUserRegister, uow=Depends(UnitOfWork)): hashed_password = get_password_hash(user_data.password) async with uow: - user_id = await uow.user.add( + 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} + {"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)}) + 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}"} diff --git a/app/users/schemas.py b/app/users/schemas.py index 4e190e5..0b4d7bd 100644 --- a/app/users/schemas.py +++ b/app/users/schemas.py @@ -35,9 +35,9 @@ class SUserRegister(BaseModel): class SUserResponse(BaseModel): - email: EmailStr id: int username: str + email: EmailStr black_phoenix: bool avatar_image: HttpUrl date_of_birth: date @@ -45,7 +45,7 @@ class SUserResponse(BaseModel): class SUsers(BaseModel): - users: list[SUserResponse] + users: list[SUserResponse] | None class SUser(BaseModel): diff --git a/app/utils/auth.py b/app/utils/auth.py index 173c511..1a3fb8b 100644 --- a/app/utils/auth.py +++ b/app/utils/auth.py @@ -61,19 +61,25 @@ def decode_confirmation_token(invitation_token: str) -> SConfirmationData: class AuthService: @staticmethod async def authenticate_user_by_email(uow: UnitOfWork, email: EmailStr, password: str) -> SUser | None: - async with uow: - user = await uow.user.find_one_or_none(email=email) - if not user or not verify_password(password, user.hashed_password): - return None - return user + try: + async with uow: + user = await uow.user.find_one_or_none(email=email) + if not verify_password(password, user.hashed_password): + return + return user + except UserNotFoundException: + return @staticmethod async def authenticate_user_by_username(uow: UnitOfWork, username: str, password: str) -> SUser | None: - async with uow: - user = await uow.user.find_one_or_none(username=username) - if not user or not verify_password(password, user.hashed_password): - return None - return user + try: + async with uow: + user = await uow.user.find_one_or_none(username=username) + if not verify_password(password, user.hashed_password): + return + return user + except UserNotFoundException: + return @classmethod async def authenticate_user(cls, uow: UnitOfWork, email_or_username: str, password: str) -> SUser: @@ -88,20 +94,18 @@ class AuthService: async def check_verificated_user(uow: UnitOfWork, user_id: int) -> bool: async with uow: user = await uow.user.find_one_or_none(id=user_id) - if not user: - raise UserNotFoundException return user.role >= settings.VERIFICATED_USER @classmethod - async def check_verificated_user_with_exc(cls, uow: UnitOfWork, user_id: int): + async def check_verificated_user_with_exc(cls, uow: UnitOfWork, user_id: int) -> None: if not await cls.check_verificated_user(uow=uow, user_id=user_id): raise UserMustConfirmEmailException @staticmethod - async def get_user_allowed_chats_id(uow: UnitOfWork, user_id: int) -> list[int]: + async def get_user_allowed_chats_id(uow: UnitOfWork, user_id: int) -> set[int]: async with uow: user_allowed_chats = await uow.user.get_user_allowed_chats(user_id) - user_allowed_chats_id = [chat["chat_id"] for chat in user_allowed_chats] + user_allowed_chats_id = {chat["chat_id"] for chat in user_allowed_chats.allowed_chats} return user_allowed_chats_id @classmethod @@ -111,10 +115,3 @@ class AuthService: raise UserDontHavePermissionException return True - @staticmethod - async def validate_user_admin(uow: UnitOfWork, user_id: int) -> bool: - async with uow: - user_role = await uow.user.get_user_role(user_id=user_id) - if user_role == settings.ADMIN_USER: - return True - return False