Files
Fulfillment-Backend/services/chat.py

299 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import requests
from aiohttp import ClientConnectorError
from fastapi import HTTPException, UploadFile
from sqlalchemy import select
from sqlalchemy.orm import joinedload
from starlette.responses import StreamingResponse
from backend.config import CHAT_CONNECTOR_API_KEY, CHAT_TELEGRAM_BOT_TOKEN
from external.chat.chat_client import ChatClient
from external.chat.schemas import *
from external.kafka.services.producer_service import ProducerService
from models import Message, Chat, MessageStatus, TgGroup, Client, Card, MessageFile
from schemas.chat import *
from services.base import BaseService
class ChatService(BaseService):
async def _get_chat(self, client_id: int, card_id: Optional[int]) -> Optional[Chat]:
stmt = (
select(Chat)
.where(
Chat.card_id == card_id,
TgGroup.client_id == client_id,
)
.join(TgGroup)
)
return (await self.session.scalars(stmt)).one_or_none()
async def get_chat(self, request: GetChatRequest) -> GetChatResponse:
chat = await self._get_chat(request.client_id, request.card_id)
return GetChatResponse(chat=chat)
async def _get_group(self, client_id: int) -> Optional[TgGroup]:
stmt = (
select(TgGroup)
.where(TgGroup.client_id == client_id)
)
return (await self.session.scalars(stmt)).one_or_none()
async def _create_group_for_client(self, client_id: int, title: str) -> Optional[TgGroup]:
chat_client = ChatClient(api_key=CHAT_CONNECTOR_API_KEY)
request = ExternalCreateGroupRequest(title=title)
response = await chat_client.create_group(request)
group = TgGroup(
id=response.group_id,
client_id=client_id,
tg_group_id=response.tg_group_id,
tg_invite_link=response.tg_invite_link,
)
chat = Chat(
tg_topic_id=1,
card_id=None,
client_id=client_id,
tg_group_id=response.group_id,
)
self.session.add_all([group, chat])
await self.session.commit()
return group
@staticmethod
def _get_title_for_client_chat(client: Client) -> str:
return f'[{client.id}] {client.name}'
@staticmethod
def _get_title_for_card_chat(card: Card) -> str:
return f'[{card.id}] {card.name}'
async def _create_topic_for_card(
self,
group_id: UUID,
card: Card,
) -> Chat:
card_chat_icon_id = 5348227245599105972 # 💼
chat_client = ChatClient(api_key=CHAT_CONNECTOR_API_KEY)
request = ExternalCreateTopicRequest(
group_id=str(group_id),
title=self._get_title_for_card_chat(card),
icon_emoji_id=card_chat_icon_id,
)
response = await chat_client.create_topic(request)
chat = Chat(
tg_topic_id=response.tg_topic_id,
card_id=card.id,
client_id=None,
tg_group_id=group_id,
)
self.session.add(chat)
await self.session.commit()
return chat
async def _create_chat_for_card(self, request: CreateChatRequest, card: Card, client: Client) -> CreateChatResponse:
existing_chat = await self._get_chat(request.client_id, request.card_id)
if existing_chat:
return CreateChatResponse(
ok=False,
message=f'Чат для клиента ID {request.client_id} и карточки ID {request.card_id} уже существует'
)
group: Optional[TgGroup] = await self._get_group(request.client_id)
if not group:
group: TgGroup = await self._create_group_for_client(
request.client_id,
self._get_title_for_client_chat(client)
)
await self._create_topic_for_card(group.id, card)
return CreateChatResponse(ok=True, message='Чат успешно создан')
async def _create_chat_for_client(self, request: CreateChatRequest, client: Client) -> CreateChatResponse:
existing_chat = await self._get_chat(client.id, request.card_id)
if existing_chat:
return CreateChatResponse(ok=False, message=f'Чат для клиента ID {client.id} уже существует')
group = await self._get_group(client.id)
if group:
return CreateChatResponse(ok=False, message=f'Группа для клиента ID {client.id} уже существует')
client_chat_title = self._get_title_for_client_chat(client)
await self._create_group_for_client(request.client_id, client_chat_title)
return CreateChatResponse(ok=True, message='Чат успешно создан')
async def create_chat(self, request: CreateChatRequest) -> CreateChatResponse:
client: Optional[Client] = await self.session.get(Client, request.client_id)
if not client:
return CreateChatResponse(ok=False, message=f'Клиент с ID {request.client_id} не найден')
try:
if not request.card_id:
return await self._create_chat_for_client(request, client)
card: Optional[Card] = await self.session.get(Card, request.card_id)
if not card:
return CreateChatResponse(ok=False, message=f'Карточка с ID {request.card_id} не найдена')
return await self._create_chat_for_card(request, card, client)
except ClientConnectorError:
return CreateChatResponse(ok=False, message=f'Ошибка подключения к сервису')
except Exception as e:
return CreateChatResponse(ok=False, message=str(e))
async def get_messages(self, request: GetMessagesRequest) -> GetMessagesResponse:
stmt = (
select(Message)
.where(
Message.chat_id == request.chat_id,
Message.is_deleted == False,
)
.order_by(Message.created_at.desc())
.offset(request.offset)
.limit(request.limit)
)
messages = (await self.session.scalars(stmt)).all()
return GetMessagesResponse(messages=messages)
async def send_message(self, request: SendTextMessageRequest) -> SendTextMessageResponse:
chat: Optional[Chat] = await self.session.get(Chat, request.message.chat_id)
if not chat:
return SendTextMessageResponse(ok=False, message=f'Чат с ID: {request.message.chat_id} не найден')
message = Message(
text=request.message.text,
created_at=datetime.now(),
chat_id=request.message.chat_id,
status=MessageStatus.sending,
)
self.session.add(message)
await self.session.commit()
ok, message = await ProducerService.send_message_to_connector(
request.message.text,
chat.tg_group_id,
chat.tg_topic_id,
message.id,
)
return SendTextMessageResponse(ok=ok, message=message)
async def repeat_sending_message(
self,
request: RepeatSendingTextMessageRequest,
) -> RepeatSendingTextMessageResponse:
message: Optional[Message] = await self._get_message_by_id(request.message.id)
if not message:
return RepeatSendingTextMessageResponse(ok=False, message=f'Сообщение с ID: {request.message.id} не найдено')
ok, msg = await ProducerService.send_message_to_connector(
request.message.text,
message.chat.tg_group_id,
message.chat.tg_topic_id,
message.id,
)
if ok:
message.status = MessageStatus.sending
await self.session.commit()
return RepeatSendingTextMessageResponse(ok=ok, message=msg)
async def send_messages_with_files(
self,
files: list[UploadFile],
chat_id: int,
caption: str,
) -> LoadMessagesResponse:
chat: Optional[Chat] = await self.session.get(Chat, chat_id)
if not chat:
return SendTextMessageResponse(ok=False, message=f'Чат с ID: {chat_id} не найден')
chat_client = ChatClient(api_key=CHAT_CONNECTOR_API_KEY)
response = await chat_client.send_messages_with_files(
str(chat.tg_group_id),
chat.tg_topic_id,
caption,
files
)
last_message = None
for file_schema in response.files:
file = MessageFile(**file_schema.model_dump())
self.session.add(file)
message = Message(
text='',
created_at=datetime.now(),
chat_id=chat_id,
status=MessageStatus.success,
file=file,
)
last_message = message
self.session.add(message)
if last_message:
last_message.text = caption
await self.session.commit()
return LoadMessagesResponse(ok=response.ok, message=response.message)
async def _get_message_by_id(self, message_id: int) -> Optional[Message]:
stmt = (
select(Message)
.where(Message.id == message_id)
.options(joinedload(Message.chat))
)
return (await self.session.scalars(stmt)).one_or_none()
async def delete_message(self, message_id: int) -> DeleteMessageResponse:
message: Optional[Message] = await self._get_message_by_id(message_id)
if not message:
return DeleteMessageResponse(ok=False, message=f'Сообщение с ID: {message_id} не найдено')
ok, msg = await ProducerService.send_message_deleting_to_connector(
message_id,
message.tg_message_id,
message.chat.tg_group_id,
)
return DeleteMessageResponse(ok=ok, message=msg)
async def edit_message(self, request: EditMessageRequest) -> EditMessageResponse:
message: Optional[Message] = await self._get_message_by_id(request.message.id)
if not message:
return EditMessageResponse(ok=False, message=f'Сообщение с ID: {request.message.id} не найдено')
ok, msg = await ProducerService.send_message_editing_to_connector(
message.id,
message.tg_message_id,
message.chat.tg_group_id,
request.message.text,
)
return EditMessageResponse(ok=ok, message=msg)
async def get_tg_file(self, file_id: int) -> StreamingResponse:
file: Optional[MessageFile] = await self.session.get(MessageFile, file_id)
if not file:
raise HTTPException(status_code=404, detail=f'Файл с ID {file_id} не найден')
url: str = f'https://api.telegram.org/file/bot{CHAT_TELEGRAM_BOT_TOKEN}/{file.file_path}'
print(f'URL = {url}')
response = requests.get(url, stream=True)
if response.status_code != 200:
raise HTTPException(status_code=response.status_code, detail="Error fetching file")
content_type = response.headers.get("Content-Type", "application/octet-stream")
return StreamingResponse(response.iter_content(chunk_size=8192), media_type=content_type)