From 3124b35f67d1c3128da51ec465d9b51f6be62ce5 Mon Sep 17 00:00:00 2001 From: urec56 Date: Thu, 1 Feb 2024 09:58:37 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D1=8D=D0=BD=D0=B4=D0=BF=D0=BE=D0=B8=D0=BD=D1=82=D1=8B=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B=20=D1=81=20?= =?UTF-8?q?=D1=87=D0=B0=D1=82=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/exceptions.py | 10 +++ app/migrations/env.py | 5 +- .../37cf151ccb02_изменение_models_py.py | 32 +++++++++ .../5d84c98e0f22_изменение_models_py.py | 32 +++++++++ app/users/chat/dao.py | 58 +++++++++++++++- app/users/chat/models.py | 4 +- app/users/chat/router.py | 66 ++++++++++++++++--- app/users/chat/shemas.py | 0 app/users/dao.py | 6 ++ app/users/models.py | 1 + 10 files changed, 202 insertions(+), 12 deletions(-) create mode 100644 app/migrations/versions/37cf151ccb02_изменение_models_py.py create mode 100644 app/migrations/versions/5d84c98e0f22_изменение_models_py.py create mode 100644 app/users/chat/shemas.py diff --git a/app/exceptions.py b/app/exceptions.py index 53ec607..25b8074 100644 --- a/app/exceptions.py +++ b/app/exceptions.py @@ -54,3 +54,13 @@ class IncorrectLengthOfNicknameException(BlackPhoenixException): detail = "Ник должен быть не короче 2 и не длиннее 30 символов" +class UDontHavePermissionException(BlackPhoenixException): + status_code = status.HTTP_409_CONFLICT + detail = "У вас нет прав для этого действия" + + +class MessageNotFoundException(BlackPhoenixException): + status_code = status.HTTP_404_NOT_FOUND + detail = "Сообщение не найдено" + + diff --git a/app/migrations/env.py b/app/migrations/env.py index d4df8d2..0ff0215 100644 --- a/app/migrations/env.py +++ b/app/migrations/env.py @@ -5,6 +5,8 @@ from logging.config import fileConfig from sqlalchemy import engine_from_config, pool from alembic import context +sys.path.insert(0, dirname(dirname(abspath(__file__)))) + from app.database import DATABASE_URL, Base from app.users.models import Users # noqa from app.users.chat.models import Chats, Messages, UsersXChats # noqa @@ -12,8 +14,6 @@ from app.users.chat.models import Chats, Messages, UsersXChats # noqa # this is the Alembic Config object, which provides # access to the values within the .ini file in use. -sys.path.insert(0, dirname(dirname(abspath(__file__)))) - config = context.config config.set_main_option('sqlalchemy.url', f'{DATABASE_URL}?async_fallback=True') @@ -29,6 +29,7 @@ if config.config_file_name is not None: # target_metadata = mymodel.Base.metadata target_metadata = Base.metadata + # other values from the config, defined by the needs of env.py, # can be acquired: # my_important_option = config.get_main_option("my_important_option") diff --git a/app/migrations/versions/37cf151ccb02_изменение_models_py.py b/app/migrations/versions/37cf151ccb02_изменение_models_py.py new file mode 100644 index 0000000..32520d3 --- /dev/null +++ b/app/migrations/versions/37cf151ccb02_изменение_models_py.py @@ -0,0 +1,32 @@ +"""Изменение models.py + +Revision ID: 37cf151ccb02 +Revises: 5d84c98e0f22 +Create Date: 2024-01-31 20:52:41.569988 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '37cf151ccb02' +down_revision: Union[str, None] = '5d84c98e0f22' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('messages', sa.Column('message', sa.String(), nullable=False)) + op.drop_column('messages', 'text') + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('messages', sa.Column('text', sa.VARCHAR(), autoincrement=False, nullable=False)) + op.drop_column('messages', 'message') + # ### end Alembic commands ### diff --git a/app/migrations/versions/5d84c98e0f22_изменение_models_py.py b/app/migrations/versions/5d84c98e0f22_изменение_models_py.py new file mode 100644 index 0000000..6421aa6 --- /dev/null +++ b/app/migrations/versions/5d84c98e0f22_изменение_models_py.py @@ -0,0 +1,32 @@ +"""Изменение models.py + +Revision ID: 5d84c98e0f22 +Revises: ad488d81e7b5 +Create Date: 2024-01-31 20:35:24.317899 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '5d84c98e0f22' +down_revision: Union[str, None] = 'ad488d81e7b5' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('chats', sa.Column('chat_for', sa.Integer(), nullable=True)) + op.create_foreign_key(None, 'chats', 'users', ['chat_for'], ['id']) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'chats', type_='foreignkey') + op.drop_column('chats', 'chat_for') + # ### end Alembic commands ### diff --git a/app/users/chat/dao.py b/app/users/chat/dao.py index 854f600..8df4174 100644 --- a/app/users/chat/dao.py +++ b/app/users/chat/dao.py @@ -1,7 +1,63 @@ +from sqlalchemy import insert, select, update, and_ + from app.dao.base import BaseDAO +from app.database import async_session_maker from app.users.models import Users -from app.users.chat.models import Chats +from app.users.chat.models import Chats, Messages class ChatDAO(BaseDAO): model = Chats + + @classmethod + async def create(cls, user_id): + query = insert(Chats).values(chat_for=user_id) + async with async_session_maker() as session: + await session.execute(query) + await session.commit() + return True + + @classmethod + async def send_message(cls, user_id, chat_id, message, image_url): + query = insert(Messages).values(chat_id=chat_id, user_id=user_id, message=message, image_url=image_url) + async with async_session_maker() as session: + await session.execute(query) + await session.commit() + return True + + @classmethod + async def get_message_by_id(cls, message_id): + query = select(Messages.__table__.columns).where( + and_( + Messages.id == message_id, + Messages.visibility == True + ) + + ) + async with async_session_maker() as session: + result = await session.execute(query) + result = result.mappings().all() + if result: + return result[0] + + @classmethod + async def get_last_message(cls, chat_id): + query = select(Messages.__table__.columns).where( + and_( + Messages.chat_id == chat_id, + Messages.visibility == True + ) + ).order_by(Messages.created_at.desc()).limit(1) + async with async_session_maker() as session: + result = await session.execute(query) + result = result.mappings().all() + if result: + return result[0] + + @classmethod + async def delete_message(cls, message_id): + query = update(Messages).where(Messages.id == message_id).values(visibility=False) + async with async_session_maker() as session: + await session.execute(query) + await session.commit() + return True diff --git a/app/users/chat/models.py b/app/users/chat/models.py index 993342f..3e7cf95 100644 --- a/app/users/chat/models.py +++ b/app/users/chat/models.py @@ -11,9 +11,11 @@ class Chats(Base): __tablename__ = "chats" id: Mapped[int] = mapped_column(primary_key=True) + chat_for = mapped_column(ForeignKey("users.id")) message = relationship("Messages", back_populates="chat") usersxchats = relationship("UsersXChats", back_populates="chat") + user_to_exclude = relationship("Users", back_populates="chat") def __str__(self): return f"Чат #{self.id}." @@ -25,7 +27,7 @@ class Messages(Base): id: Mapped[int] = mapped_column(primary_key=True) chat_id = mapped_column(ForeignKey("chats.id")) user_id = mapped_column(ForeignKey("users.id")) - text: Mapped[str] + message: Mapped[str] image_url: Mapped[Optional[str]] created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) visibility: Mapped[bool] = mapped_column(server_default='true') diff --git a/app/users/chat/router.py b/app/users/chat/router.py index 6149121..97b5032 100644 --- a/app/users/chat/router.py +++ b/app/users/chat/router.py @@ -1,7 +1,12 @@ -from fastapi import APIRouter, WebSocket +from fastapi import APIRouter, WebSocket, Depends from starlette.websockets import WebSocketDisconnect +from app.exceptions import UDontHavePermissionException, MessageNotFoundException +from app.users.chat.dao import ChatDAO from app.users.chat.websocket import manager +from app.users.dao import UserDAO +from app.users.dependencies import get_current_user +from app.users.models import Users router = APIRouter( prefix="/chat", @@ -9,18 +14,63 @@ router = APIRouter( ) -@router.get("") -async def root(): - pass +@router.post("") +async def add_message_to_chat( + chat_id: int, + message: str, + image_url: str = None, + user: Users = Depends(get_current_user) +): + send_message_to_chat = await ChatDAO.send_message( + user_id=user.id, + chat_id=chat_id, + message=message, + image_url=image_url + ) + return send_message_to_chat -@router.websocket("/ws/{user_id}") -async def websocket_endpoint(websocket: WebSocket, user_id: int): +@router.delete("/delete_message") +async def delete_message_from_chat( + message_id: int, + user: Users = Depends(get_current_user) +): + get_message_sender = await ChatDAO.get_message_by_id(message_id=message_id) + if get_message_sender is None: + raise MessageNotFoundException + if get_message_sender["user_id"] != user.id: + get_user_role = await UserDAO.get_user_role(user_id=user.id) + if not get_user_role == 1: + raise UDontHavePermissionException + deleted_message = await ChatDAO.delete_message(message_id=message_id) + return deleted_message + + +@router.get("/get_last_message") +async def get_last_message(chat_id: int, user: Users = Depends(get_current_user)): + message = await ChatDAO.get_last_message(chat_id=chat_id) + if message is not None: + return message + raise MessageNotFoundException + + +@router.post("/create_chat") +async def create_chat( + user_to_exclude: int, + user: Users = Depends(get_current_user) +): + created_chat = await ChatDAO.create(user_id=user_to_exclude) + return created_chat + + +@router.websocket("/ws") +async def websocket_endpoint(websocket: WebSocket, user: Users = Depends(get_current_user)): await manager.connect(websocket) try: while True: data = await websocket.receive_text() - await manager.broadcast(f"User {user_id}: {data}") + + await manager.broadcast(f"User {user.id}: {data}") except WebSocketDisconnect: manager.disconnect(websocket) - await manager.broadcast(f"User {user_id}: себался") + await manager.broadcast(f"User {user.id}: себался") diff --git a/app/users/chat/shemas.py b/app/users/chat/shemas.py new file mode 100644 index 0000000..e69de29 diff --git a/app/users/dao.py b/app/users/dao.py index abdfdc2..9796ba3 100644 --- a/app/users/dao.py +++ b/app/users/dao.py @@ -19,3 +19,9 @@ class UserDAO(BaseDAO): result = await session.execute(query) return result.scalar() + @classmethod + async def get_user_role(cls, user_id: int): + query = select(Users.role).where(Users.id == user_id) + async with async_session_maker() as session: + result = await session.execute(query) + return result.scalar() diff --git a/app/users/models.py b/app/users/models.py index 3547613..23c77df 100644 --- a/app/users/models.py +++ b/app/users/models.py @@ -20,6 +20,7 @@ class Users(Base): message = relationship("Messages", back_populates="user") usersxchats = relationship("UsersXChats", back_populates="user") + chat = relationship("Chats", back_populates="user_to_exclude") def __str__(self): return f"Юзер {self.username}"