122 lines
4.4 KiB
Python
122 lines
4.4 KiB
Python
"""Чат — 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()]
|