Добавил эндпоинты для работы с чатом

This commit is contained in:
urec56 2024-02-01 09:58:37 +03:00
parent 7342415ad0
commit 3124b35f67
10 changed files with 202 additions and 12 deletions

View file

@ -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 = "Сообщение не найдено"

View file

@ -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")

View file

@ -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 ###

View file

@ -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 ###

View file

@ -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

View file

@ -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')

View file

@ -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}: себался")

0
app/users/chat/shemas.py Normal file
View file

View file

@ -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()

View file

@ -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}"