Files
freelancer-match/backend/src/api/routes/chats.py
T

122 lines
4.4 KiB
Python
Raw Normal View History

"""Чат — WebSocket + REST для сообщений"""
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, update
import uuid
from ...core.database import async_session_factory, Chat, ChatMessage, ContentType, ChatStatus
from ...utils.auth import get_current_user
router = APIRouter()
class SendMessageRequest(BaseModel):
chat_id: uuid.UUID
content_type: str = "text" # text | image | file | voice
content: str | None = None
media_url: str | None = None
reply_to_id: uuid.UUID | None = None
@router.post("/messages")
async def send_message(req: SendMessageRequest, session: AsyncSession = Depends(async_session_factory), user=Depends(get_current_user)):
"""Отправить сообщение в чат."""
chat = await session.get(Chat, req.chat_id)
if not chat or chat.status != ChatStatus.ACTIVE:
raise HTTPException(400, "Чат неактивен")
message = ChatMessage(
chat_id=req.chat_id,
sender_id=user.id,
content_type=ContentType(req.content_type),
content=req.content,
media_url=req.media_url,
reply_to_id=req.reply_to_id,
)
session.add(message)
from datetime import datetime
chat.last_message_at = datetime.utcnow()
await session.commit()
return {"id": str(message.id), "status": "sent"}
@router.get("/messages/{chat_id}")
async def get_messages(chat_id: uuid.UUID, limit: int = 50, before: uuid.UUID | None = Query(None), session: AsyncSession = Depends(async_session_factory)):
"""Получить историю сообщений чата."""
query = select(ChatMessage).where(ChatMessage.chat_id == chat_id)
if before:
query = query.where(ChatMessage.id < before)
query = query.order_by(ChatMessage.created_at.desc()).limit(limit)
result = await session.execute(query)
messages = [m.mapped() for m in result.scalars().all()]
return list(reversed(messages))
@router.get("/chats")
async def get_user_chats(user=Depends(get_current_user), session: AsyncSession = Depends(async_session_factory)):
"""Получить все чаты пользователя."""
query = select(Chat).where(
(Chat.client_id == user.id) | (Chat.master_id == user.id),
Chat.status.in_([ChatStatus.ACTIVE, ChatStatus.COMPLETED])
).order_by(Chat.last_message_at.desc())
result = await session.execute(query)
return [c.mapped() for c in result.scalars().all()]
@router.post("/chats/{chat_id}/mark-read")
async def mark_as_read(chat_id: uuid.UUID, user=Depends(get_current_user), session: AsyncSession = Depends(async_session_factory)):
"""Отметить сообщения как прочитанные."""
await session.execute(
update(ChatMessage)
.where((ChatMessage.chat_id == chat_id) & (ChatMessage.sender_id != user.id) & (ChatMessage.read_at.is_(None)))
.values(read_at=datetime.utcnow())
)
await session.commit()
@router.post("/chats/{chat_id}/archive")
async def archive_chat(chat_id: uuid.UUID, user=Depends(get_current_user), session: AsyncSession = Depends(async_session_factory)):
"""Заархивировать чат."""
chat = await session.get(Chat, chat_id)
if not chat or (chat.client_id != user.id and chat.master_id != user.id):
raise HTTPException(403)
chat.status = ChatStatus.ARCHIVED
await session.commit()
@router.delete("/messages/{message_id}")
async def delete_message(message_id: uuid.UUID, user=Depends(get_current_user), session: AsyncSession = Depends(async_session_factory)):
"""Удалить сообщение (только отправитель в течение 24ч)."""
message = await session.get(ChatMessage, message_id)
if not message or message.sender_id != user.id:
raise HTTPException(403)
await session.delete(message)
await session.commit()
@router.post("/chats/{chat_id}/search")
async def search_messages(chat_id: uuid.UUID, query: str = Query(...), limit: int = 20, session: AsyncSession = Depends(async_session_factory)):
"""Поиск по сообщениям чата."""
result = await session.execute(
select(ChatMessage)
.where((ChatMessage.chat_id == chat_id) & (ChatMessage.content.ilike(f"%{query}%")))
.order_by(ChatMessage.created_at.desc())
.limit(limit)
)
return [m.mapped() for m in result.scalars().all()]