diff --git a/app/chat/exceptions.py b/app/chat/exceptions.py new file mode 100644 index 0000000..94589c6 --- /dev/null +++ b/app/chat/exceptions.py @@ -0,0 +1,33 @@ +from fastapi import status + +from app.exceptions import BlackPhoenixException + + +class UseWSException(BlackPhoenixException): + status_code = status.HTTP_403_FORBIDDEN + detail = "Це для теста, не трожь сцука!!!" + + +class UserDontHavePermissionException(BlackPhoenixException): + status_code = status.HTTP_409_CONFLICT + detail = "У вас нет прав для этого действия" + + +class MessageNotFoundException(BlackPhoenixException): + status_code = status.HTTP_404_NOT_FOUND + detail = "Сообщение не найдено" + + +class UserCanNotReadThisChatException(BlackPhoenixException): + status_code = status.HTTP_409_CONFLICT + detail = "Юзер не может читать этот чат" + + +class UserAlreadyInChatException(BlackPhoenixException): + status_code = status.HTTP_409_CONFLICT + detail = "Юзер уже добавлен в чат" + + +class UserAlreadyPinnedChatException(BlackPhoenixException): + status_code = status.HTTP_409_CONFLICT + detail = "Юзер уже закрепил чат" diff --git a/app/chat/router.py b/app/chat/router.py index ffe0ebc..6cadcd0 100644 --- a/app/chat/router.py +++ b/app/chat/router.py @@ -1,7 +1,11 @@ from fastapi import APIRouter, Depends, status from app.config import settings -from app.exceptions import UserDontHavePermissionException, MessageNotFoundException, UserCanNotReadThisChatException +from app.chat.exceptions import ( + UserDontHavePermissionException, + MessageNotFoundException, + UserCanNotReadThisChatException, +) from app.chat.shemas import ( SMessage, SLastMessages, @@ -11,7 +15,7 @@ from app.chat.shemas import ( SAllowedChats, SMessageList, SPinnedChats, - SPinnedMessages + SPinnedMessages, ) from app.unit_of_work import UnitOfWork from app.users.dependencies import check_verificated_user_with_exc diff --git a/app/chat/websocket.py b/app/chat/websocket.py index 18593a1..448fdf3 100644 --- a/app/chat/websocket.py +++ b/app/chat/websocket.py @@ -1,9 +1,9 @@ -import logging - import websockets from fastapi import WebSocket, WebSocketDisconnect, Depends -from app.exceptions import IncorrectDataException, UserDontHavePermissionException +from app.chat.exceptions import UseWSException +from app.exceptions import IncorrectDataException +from app.chat.exceptions import UserDontHavePermissionException from app.services.message_service import MessageService from app.unit_of_work import UnitOfWork from app.utils.auth import AuthService @@ -97,10 +97,8 @@ class ConnectionManager: @staticmethod async def add_message_to_database(uow: UnitOfWork, user_id: int, chat_id: int, message: SSendMessage) -> SMessage: new_message = await MessageService.send_message( - uow=uow, user_id=user_id, chat_id=chat_id, message=message.message, image_url=message.image_url + uow=uow, user_id=user_id, chat_id=chat_id, message=message, image_url=message.image_url ) - if message.answer: - new_message = await MessageService.add_answer(uow=uow, self_id=new_message.id, answer_id=message.answer) return new_message @staticmethod @@ -152,13 +150,13 @@ async def websocket_endpoint( @router.post( - "/ws/{chat_id}" + "/ws/{chat_id}", ) -async def websocket_endpoint( +async def chat_ws( chat_id: int, token: str = Depends(get_token), ): - logging.critical("Это сообщение уровня INFO") + raise UseWSException # noqa url = f"ws://localhost:8000/api/chat/ws/{chat_id}" async with websockets.connect(url, extra_headers={"Authorization": f"Bearer {token}"}) as websocket: print(await websocket.recv()) diff --git a/app/dao/chat.py b/app/dao/chat.py index e881fcd..436a2b7 100644 --- a/app/dao/chat.py +++ b/app/dao/chat.py @@ -1,9 +1,11 @@ -from sqlalchemy import insert, select, update, delete +import logging + +from sqlalchemy import insert, select, update, delete, func from sqlalchemy.exc import IntegrityError from app.dao.base import BaseDAO from app.database import engine # noqa -from app.exceptions import UserAlreadyInChatException, UserAlreadyPinnedChatException +from app.chat.exceptions import UserAlreadyInChatException, UserAlreadyPinnedChatException from app.chat.shemas import SMessage from app.models.users import Users from app.models.message_answer import MessageAnswer @@ -37,38 +39,15 @@ class ChatDAO(BaseDAO): except IntegrityError: raise UserAlreadyInChatException - async def send_message(self, user_id: int, chat_id: int, message: str, image_url: str | None = None) -> SMessage: - inserted_image = ( + async def send_message(self, user_id: int, chat_id: int, message: str, image_url: str | None = None) -> int: + stmt = ( insert(Message) .values(chat_id=chat_id, user_id=user_id, message=message, image_url=image_url) - .returning( - Message.id, Message.message, Message.image_url, Message.chat_id, Message.user_id, Message.created_at - ) - .cte("inserted_image") + .returning(Message.id) ) - query = ( - select( - inserted_image.c.id, - inserted_image.c.message, - inserted_image.c.image_url, - inserted_image.c.chat_id, - inserted_image.c.user_id, - inserted_image.c.created_at, - Users.avatar_image, - Users.username, - MessageAnswer.self_id, - MessageAnswer.answer_id, - ) - .select_from(inserted_image) - .join(Users, Users.id == inserted_image.c.user_id) - .join(MessageAnswer, MessageAnswer.self_id == inserted_image.c.id, isouter=True) - ) - - result = await self.session.execute(query) - await self.session.commit() - result = result.mappings().one() - return SMessage.model_validate(result, from_attributes=True) + result = await self.session.execute(stmt) + return result.scalar() async def get_message_by_id(self, message_id: int): query = ( @@ -152,36 +131,37 @@ class ChatDAO(BaseDAO): await self.session.commit() return True - async def add_answer(self, self_id: int, answer_id: int) -> SMessage: - answer = ( - insert(MessageAnswer) - .values(self_id=self_id, answer_id=answer_id) - .returning(MessageAnswer.self_id, MessageAnswer.answer_id) - .cte("answer") - ) + async def add_answer(self, self_id: int, answer_id: int | None) -> SMessage: + if answer_id: + stmt = ( + insert(MessageAnswer) + .values(self_id=self_id, answer_id=answer_id) + ) + await self.session.execute(stmt) query = ( select( - Message.id, - Message.message, - Message.image_url, - Message.chat_id, - Message.user_id, - Message.created_at, - Users.avatar_image, - Users.username, - answer.c.self_id, - answer.c.answer_id, + func.json_build_object( + "id", Message.id, + "message", Message.message, + "image_url", Message.image_url, + "chat_id", Message.chat_id, + "user_id", Message.user_id, + "created_at", Message.created_at, + "avatar_image", Users.avatar_image, + "username", Users.username, + "answer_id", MessageAnswer.answer_id, + ) + .select_from(Message) + .join(Users, Users.id == Message.user_id) + .join(MessageAnswer, MessageAnswer.self_id == Message.id, isouter=True) + .where(Message.id == self_id) ) - .select_from(Message) - .join(Users, Users.id == Message.user_id) - .join(answer, answer.c.self_id == Message.id, isouter=True) - .where(Message.id == self_id) ) result = await self.session.execute(query) - await self.session.commit() - result = result.mappings().one() + result = result.scalar_one() + logging.critical(result) return SMessage.model_validate(result) async def delete_chat(self, chat_id: int) -> bool: diff --git a/app/dao/user.py b/app/dao/user.py index e0b846a..713b62f 100644 --- a/app/dao/user.py +++ b/app/dao/user.py @@ -4,7 +4,8 @@ from sqlalchemy.exc import MultipleResultsFound, IntegrityError from app.dao.base import BaseDAO from app.database import engine # noqa -from app.exceptions import IncorrectDataException, UserAlreadyExistsException +from app.exceptions import IncorrectDataException +from app.users.exceptions import UserAlreadyExistsException from app.models.chat import Chats from app.models.user_avatar import UserAvatar from app.models.users import Users diff --git a/app/exceptions.py b/app/exceptions.py index 79c8f0e..94c37dd 100644 --- a/app/exceptions.py +++ b/app/exceptions.py @@ -9,16 +9,6 @@ class BlackPhoenixException(HTTPException): super().__init__(status_code=self.status_code, detail=self.detail) -class UserAlreadyExistsException(BlackPhoenixException): - status_code = status.HTTP_409_CONFLICT - detail = "Пользователь с таким ником или почтой уже существует" - - -class IncorrectAuthDataException(BlackPhoenixException): - status_code = status.HTTP_401_UNAUTHORIZED - detail = "Введены не верные данные" - - class IncorrectTokenFormatException(BlackPhoenixException): status_code = status.HTTP_401_UNAUTHORIZED detail = "Некорректный формат токена" @@ -34,50 +24,11 @@ class TokenExpiredException(BlackPhoenixException): detail = "Токен истёк" -class UserIsNotPresentException(BlackPhoenixException): - status_code = status.HTTP_401_UNAUTHORIZED - - -class UserDontHavePermissionException(BlackPhoenixException): - status_code = status.HTTP_409_CONFLICT - detail = "У вас нет прав для этого действия" - - -class MessageNotFoundException(BlackPhoenixException): - status_code = status.HTTP_404_NOT_FOUND - detail = "Сообщение не найдено" - - -class PasswordsMismatchException(BlackPhoenixException): - status_code = status.HTTP_409_CONFLICT - detail = "Пароли не совпадают" - - -class UserCanNotReadThisChatException(BlackPhoenixException): - status_code = status.HTTP_409_CONFLICT - detail = "Юзер не может читать этот чат" - - -class WrongCodeException(BlackPhoenixException): - status_code = status.HTTP_409_CONFLICT - detail = "Введён не верный код подтверждения" - - class UserNotFoundException(BlackPhoenixException): status_code = status.HTTP_404_NOT_FOUND detail = "Юзер не найден" -class UserAlreadyInChatException(BlackPhoenixException): - status_code = status.HTTP_409_CONFLICT - detail = "Юзер уже добавлен в чат" - - -class UserAlreadyPinnedChatException(BlackPhoenixException): - status_code = status.HTTP_409_CONFLICT - detail = "Юзер уже закрепил чат" - - class UserMustConfirmEmailException(BlackPhoenixException): status_code = status.HTTP_409_CONFLICT detail = "Сначала подтвердите почту" diff --git a/app/main.py b/app/main.py index 5e223fc..613c214 100644 --- a/app/main.py +++ b/app/main.py @@ -1,9 +1,6 @@ -import logging - -from fastapi import FastAPI, Request +from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from fastapi.middleware.cors import CORSMiddleware -from starlette.middleware.base import BaseHTTPMiddleware from app.users.router import router as user_router from app.chat.websocket import router as websocket_router @@ -29,8 +26,6 @@ headers = [ "Access-Control-Allow-Headers", "Authorization", "Accept", - "Access-Control-Allow-Origin", - "Sec-WebSocket-Protocol", ] app.add_middleware( @@ -43,17 +38,3 @@ app.add_middleware( app.mount("/static", StaticFiles(directory="app/static"), name="static") - -class AddHeaderMiddleware(BaseHTTPMiddleware): - async def dispatch(self, request: Request, call_next): - logging.critical(f"Запрос \n{request.headers.get("Sec-Websocket-Protocol", "НИХУЯ")}") - logging.critical(f"Запрос {request.headers}") - logging.critical("Sec-Websocket-Protocol".lower() in request.headers) - response = await call_next(request) - response.headers["Sec-Websocket-Protocol"] = "" - logging.critical(f"Ответ \n {response.headers}") - return response - - -# app.add_middleware(AddHeaderMiddleware) - diff --git a/app/services/message_service.py b/app/services/message_service.py index edb98b9..880b0e9 100644 --- a/app/services/message_service.py +++ b/app/services/message_service.py @@ -1,20 +1,19 @@ -from app.chat.shemas import SMessage +from app.chat.shemas import SMessage, SSendMessage from app.unit_of_work import UnitOfWork class MessageService: @staticmethod async def send_message( - uow: UnitOfWork, user_id: int, chat_id: int, message: str, image_url: str | None = None + uow: UnitOfWork, user_id: int, chat_id: int, message: SSendMessage, image_url: str | None = None ) -> SMessage: async with uow: - new_message = await uow.chat.send_message(user_id=user_id, chat_id=chat_id, message=message, image_url=image_url) - return new_message + message_id = await uow.chat.send_message( + user_id=user_id, chat_id=chat_id, message=message.message, image_url=image_url + ) + new_message = await uow.chat.add_answer(self_id=message_id, answer_id=message.id) + await uow.commit() - @staticmethod - async def add_answer(uow: UnitOfWork, self_id: int, answer_id: int) -> SMessage: - async with uow: - new_message = await uow.chat.add_answer(self_id=self_id, answer_id=answer_id) return new_message @staticmethod diff --git a/app/users/dependencies.py b/app/users/dependencies.py index 29a6ca4..2b3ae3e 100644 --- a/app/users/dependencies.py +++ b/app/users/dependencies.py @@ -1,4 +1,3 @@ -import logging from typing import Annotated from fastapi import Depends, Header @@ -10,7 +9,7 @@ from app.exceptions import ( IncorrectTokenFormatException, TokenAbsentException, TokenExpiredException, - UserIsNotPresentException, + UserNotFoundException, UserMustConfirmEmailException, ) from app.services.user_service import UserService @@ -36,11 +35,11 @@ async def get_current_user(token: str = Depends(get_token), uow=Depends(UnitOfWo user_id: str = payload.get("sub") if not user_id: - raise UserIsNotPresentException + raise UserNotFoundException user = await UserService.find_one_or_none(uow=uow, user_id=int(user_id)) if not user: - raise UserIsNotPresentException + raise UserNotFoundException return user @@ -67,11 +66,11 @@ async def get_current_user_ws(token: str = Depends(get_token_ws), uow=Depends(Un user_id: str = payload.get("sub") if not user_id: - raise UserIsNotPresentException + raise UserNotFoundException user = await UserService.find_one_or_none(uow=uow, user_id=int(user_id)) if not user: - raise UserIsNotPresentException + raise UserNotFoundException return user diff --git a/app/users/exceptions.py b/app/users/exceptions.py new file mode 100644 index 0000000..8536cf1 --- /dev/null +++ b/app/users/exceptions.py @@ -0,0 +1,25 @@ +from fastapi import status + +from app.exceptions import BlackPhoenixException + + +class UserAlreadyExistsException(BlackPhoenixException): + status_code = status.HTTP_409_CONFLICT + detail = "Пользователь с таким ником или почтой уже существует" + + +class IncorrectAuthDataException(BlackPhoenixException): + status_code = status.HTTP_401_UNAUTHORIZED + detail = "Введены не верные данные" + + +class PasswordsMismatchException(BlackPhoenixException): + status_code = status.HTTP_409_CONFLICT + detail = "Пароли не совпадают" + + +class WrongCodeException(BlackPhoenixException): + status_code = status.HTTP_409_CONFLICT + detail = "Введён не верный код подтверждения" + + diff --git a/app/users/router.py b/app/users/router.py index f2207ae..a72882d 100644 --- a/app/users/router.py +++ b/app/users/router.py @@ -1,15 +1,21 @@ from fastapi import APIRouter, Depends, status from app.config import settings -from app.exceptions import ( - PasswordsMismatchException, - WrongCodeException, +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.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, @@ -27,7 +33,8 @@ from app.users.schemas import ( ) from app.tasks.tasks import ( send_registration_confirmation_email, - send_data_change_confirmation_email, generate_confirmation_code + send_data_change_confirmation_email, + generate_confirmation_code ) router = APIRouter(prefix="/users", tags=["Пользователи"]) diff --git a/app/utils/auth.py b/app/utils/auth.py index eee29fd..173c511 100644 --- a/app/utils/auth.py +++ b/app/utils/auth.py @@ -7,11 +7,11 @@ from pydantic import EmailStr from app.config import settings from app.exceptions import ( - UserDontHavePermissionException, - IncorrectAuthDataException, UserNotFoundException, UserMustConfirmEmailException, ) +from app.users.exceptions import IncorrectAuthDataException +from app.chat.exceptions import UserDontHavePermissionException from app.unit_of_work import UnitOfWork from app.users.schemas import SUser, SConfirmationData, SInvitationData diff --git a/tests/integration_tests/conftest.py b/tests/integration_tests/conftest.py index 634c368..33ff549 100644 --- a/tests/integration_tests/conftest.py +++ b/tests/integration_tests/conftest.py @@ -28,7 +28,6 @@ async def prepare_database(): return json.load(file) users = open_mock_json("users") - users_verification_codes = open_mock_json("verification_codes") chats = open_mock_json("chats") users_x_chats = open_mock_json("x_chats") messages = open_mock_json("messages") @@ -46,7 +45,6 @@ async def prepare_database(): set_verified_user = update(Users).values(role=1).where(Users.id == 3) await session.execute(add_users) - await session.execute(add_users_verification_codes) await session.execute(add_chats) await session.execute(add_users_x_chats) await session.execute(add_messages)