forked from HSE_team/BetterCallPraskovia
presentation layer
This commit is contained in:
parent
4a043f8e70
commit
036741c0bf
4
backend/src/application/services/__init__.py
Normal file
4
backend/src/application/services/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
"""
|
||||
Application services
|
||||
"""
|
||||
|
||||
72
backend/src/application/services/document_parser_service.py
Normal file
72
backend/src/application/services/document_parser_service.py
Normal file
@ -0,0 +1,72 @@
|
||||
"""
|
||||
Сервис парсинга документов
|
||||
"""
|
||||
from typing import BinaryIO
|
||||
from src.infrastructure.external.yandex_ocr import YandexOCRService, YandexOCRError
|
||||
|
||||
|
||||
class DocumentParserService:
|
||||
"""Сервис для парсинга документов"""
|
||||
|
||||
def __init__(self, ocr_service: YandexOCRService):
|
||||
self.ocr_service = ocr_service
|
||||
|
||||
async def parse_pdf(self, file: BinaryIO, filename: str) -> tuple[str, str]:
|
||||
"""
|
||||
Парсинг PDF файла
|
||||
|
||||
Args:
|
||||
file: Файловый объект
|
||||
filename: Имя файла
|
||||
|
||||
Returns:
|
||||
Кортеж (title, content)
|
||||
|
||||
Raises:
|
||||
YandexOCRError: При ошибке распознавания
|
||||
"""
|
||||
try:
|
||||
content = await self.ocr_service.parse_pdf(file)
|
||||
|
||||
title = filename.rsplit(".", 1)[0] if "." in filename else filename
|
||||
|
||||
if not content or not content.strip() or content.startswith("Ошибка распознавания:"):
|
||||
if not content or content.startswith("Ошибка распознавания:"):
|
||||
pass
|
||||
else:
|
||||
content = f"Документ {filename} загружен, но текст не был распознан."
|
||||
|
||||
return title, content
|
||||
except YandexOCRError as e:
|
||||
title = filename.rsplit(".", 1)[0] if "." in filename else filename
|
||||
content = f" Ошибка распознавания документа: {str(e)}"
|
||||
return title, content
|
||||
except Exception as e:
|
||||
title = filename.rsplit(".", 1)[0] if "." in filename else filename
|
||||
content = f" Ошибка при парсинге документа: {str(e)}"
|
||||
return title, content
|
||||
|
||||
async def parse_image(self, file: BinaryIO, filename: str) -> tuple[str, str]:
|
||||
"""
|
||||
Парсинг изображения
|
||||
|
||||
Args:
|
||||
file: Файловый объект изображения
|
||||
filename: Имя файла
|
||||
|
||||
Returns:
|
||||
Кортеж (title, content)
|
||||
"""
|
||||
try:
|
||||
content = await self.ocr_service.parse_image(file)
|
||||
title = filename.rsplit(".", 1)[0] if "." in filename else filename
|
||||
|
||||
if not content or not content.strip():
|
||||
content = f"Изображение {filename} загружено, но текст не был распознан."
|
||||
|
||||
return title, content
|
||||
except YandexOCRError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise YandexOCRError(f"Ошибка при парсинге изображения: {str(e)}") from e
|
||||
|
||||
4
backend/src/application/use_cases/__init__.py
Normal file
4
backend/src/application/use_cases/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
"""
|
||||
Application use cases
|
||||
"""
|
||||
|
||||
141
backend/src/application/use_cases/collection_use_cases.py
Normal file
141
backend/src/application/use_cases/collection_use_cases.py
Normal file
@ -0,0 +1,141 @@
|
||||
"""
|
||||
Use cases для работы с коллекциями
|
||||
"""
|
||||
from uuid import UUID
|
||||
from typing import Optional
|
||||
from src.domain.entities.collection import Collection
|
||||
from src.domain.entities.collection_access import CollectionAccess
|
||||
from src.domain.repositories.collection_repository import ICollectionRepository
|
||||
from src.domain.repositories.collection_access_repository import ICollectionAccessRepository
|
||||
from src.domain.repositories.user_repository import IUserRepository
|
||||
from src.shared.exceptions import NotFoundError, ForbiddenError
|
||||
|
||||
|
||||
class CollectionUseCases:
|
||||
"""Use cases для коллекций"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
collection_repository: ICollectionRepository,
|
||||
access_repository: ICollectionAccessRepository,
|
||||
user_repository: IUserRepository
|
||||
):
|
||||
self.collection_repository = collection_repository
|
||||
self.access_repository = access_repository
|
||||
self.user_repository = user_repository
|
||||
|
||||
async def create_collection(
|
||||
self,
|
||||
name: str,
|
||||
owner_id: UUID,
|
||||
description: str = "",
|
||||
is_public: bool = False
|
||||
) -> Collection:
|
||||
"""Создать коллекцию"""
|
||||
owner = await self.user_repository.get_by_id(owner_id)
|
||||
if not owner:
|
||||
raise NotFoundError(f"Пользователь {owner_id} не найден")
|
||||
|
||||
collection = Collection(
|
||||
name=name,
|
||||
owner_id=owner_id,
|
||||
description=description,
|
||||
is_public=is_public
|
||||
)
|
||||
return await self.collection_repository.create(collection)
|
||||
|
||||
async def get_collection(self, collection_id: UUID) -> Collection:
|
||||
"""Получить коллекцию по ID"""
|
||||
collection = await self.collection_repository.get_by_id(collection_id)
|
||||
if not collection:
|
||||
raise NotFoundError(f"Коллекция {collection_id} не найдена")
|
||||
return collection
|
||||
|
||||
async def update_collection(
|
||||
self,
|
||||
collection_id: UUID,
|
||||
user_id: UUID,
|
||||
name: str | None = None,
|
||||
description: str | None = None,
|
||||
is_public: bool | None = None
|
||||
) -> Collection:
|
||||
"""Обновить коллекцию"""
|
||||
collection = await self.get_collection(collection_id)
|
||||
|
||||
if collection.owner_id != user_id:
|
||||
raise ForbiddenError("Только владелец может изменять коллекцию")
|
||||
|
||||
if name is not None:
|
||||
collection.name = name
|
||||
if description is not None:
|
||||
collection.description = description
|
||||
if is_public is not None:
|
||||
collection.is_public = is_public
|
||||
|
||||
return await self.collection_repository.update(collection)
|
||||
|
||||
async def delete_collection(self, collection_id: UUID, user_id: UUID) -> bool:
|
||||
"""Удалить коллекцию"""
|
||||
collection = await self.get_collection(collection_id)
|
||||
|
||||
if collection.owner_id != user_id:
|
||||
raise ForbiddenError("Только владелец может удалять коллекцию")
|
||||
|
||||
return await self.collection_repository.delete(collection_id)
|
||||
|
||||
async def grant_access(self, collection_id: UUID, user_id: UUID, owner_id: UUID) -> CollectionAccess:
|
||||
"""Предоставить доступ пользователю к коллекции"""
|
||||
collection = await self.get_collection(collection_id)
|
||||
|
||||
if collection.owner_id != owner_id:
|
||||
raise ForbiddenError("Только владелец может предоставлять доступ")
|
||||
|
||||
user = await self.user_repository.get_by_id(user_id)
|
||||
if not user:
|
||||
raise NotFoundError(f"Пользователь {user_id} не найден")
|
||||
|
||||
existing_access = await self.access_repository.get_by_user_and_collection(user_id, collection_id)
|
||||
if existing_access:
|
||||
return existing_access
|
||||
|
||||
access = CollectionAccess(user_id=user_id, collection_id=collection_id)
|
||||
return await self.access_repository.create(access)
|
||||
|
||||
async def revoke_access(self, collection_id: UUID, user_id: UUID, owner_id: UUID) -> bool:
|
||||
"""Отозвать доступ пользователя к коллекции"""
|
||||
collection = await self.get_collection(collection_id)
|
||||
|
||||
if collection.owner_id != owner_id:
|
||||
raise ForbiddenError("Только владелец может отзывать доступ")
|
||||
|
||||
return await self.access_repository.delete_by_user_and_collection(user_id, collection_id)
|
||||
|
||||
async def check_access(self, collection_id: UUID, user_id: UUID) -> bool:
|
||||
"""Проверить доступ пользователя к коллекции"""
|
||||
collection = await self.get_collection(collection_id)
|
||||
|
||||
if collection.owner_id == user_id:
|
||||
return True
|
||||
|
||||
if collection.is_public:
|
||||
return True
|
||||
|
||||
access = await self.access_repository.get_by_user_and_collection(user_id, collection_id)
|
||||
return access is not None
|
||||
|
||||
async def list_user_collections(self, user_id: UUID, skip: int = 0, limit: int = 100) -> list[Collection]:
|
||||
"""Получить коллекции, доступные пользователю"""
|
||||
owned = await self.collection_repository.list_by_owner(user_id, skip=skip, limit=limit)
|
||||
|
||||
public = await self.collection_repository.list_public(skip=skip, limit=limit)
|
||||
|
||||
accesses = await self.access_repository.list_by_user(user_id)
|
||||
accessed_collections = []
|
||||
for access in accesses:
|
||||
collection = await self.collection_repository.get_by_id(access.collection_id)
|
||||
if collection:
|
||||
accessed_collections.append(collection)
|
||||
|
||||
all_collections = {c.collection_id: c for c in owned + public + accessed_collections}
|
||||
return list(all_collections.values())[skip:skip+limit]
|
||||
|
||||
68
backend/src/application/use_cases/conversation_use_cases.py
Normal file
68
backend/src/application/use_cases/conversation_use_cases.py
Normal file
@ -0,0 +1,68 @@
|
||||
"""
|
||||
Use cases для работы с беседами
|
||||
"""
|
||||
from uuid import UUID
|
||||
from src.domain.entities.conversation import Conversation
|
||||
from src.domain.repositories.conversation_repository import IConversationRepository
|
||||
from src.domain.repositories.collection_repository import ICollectionRepository
|
||||
from src.domain.repositories.collection_access_repository import ICollectionAccessRepository
|
||||
from src.shared.exceptions import NotFoundError, ForbiddenError
|
||||
|
||||
|
||||
class ConversationUseCases:
|
||||
"""Use cases для бесед"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
conversation_repository: IConversationRepository,
|
||||
collection_repository: ICollectionRepository,
|
||||
access_repository: ICollectionAccessRepository
|
||||
):
|
||||
self.conversation_repository = conversation_repository
|
||||
self.collection_repository = collection_repository
|
||||
self.access_repository = access_repository
|
||||
|
||||
async def create_conversation(self, user_id: UUID, collection_id: UUID) -> Conversation:
|
||||
"""Создать беседу"""
|
||||
collection = await self.collection_repository.get_by_id(collection_id)
|
||||
if not collection:
|
||||
raise NotFoundError(f"Коллекция {collection_id} не найдена")
|
||||
|
||||
has_access = await self._check_collection_access(user_id, collection)
|
||||
if not has_access:
|
||||
raise ForbiddenError("Нет доступа к коллекции")
|
||||
|
||||
conversation = Conversation(user_id=user_id, collection_id=collection_id)
|
||||
return await self.conversation_repository.create(conversation)
|
||||
|
||||
async def get_conversation(self, conversation_id: UUID, user_id: UUID) -> Conversation:
|
||||
"""Получить беседу по ID"""
|
||||
conversation = await self.conversation_repository.get_by_id(conversation_id)
|
||||
if not conversation:
|
||||
raise NotFoundError(f"Беседа {conversation_id} не найдена")
|
||||
|
||||
if conversation.user_id != user_id:
|
||||
raise ForbiddenError("Нет доступа к этой беседе")
|
||||
|
||||
return conversation
|
||||
|
||||
async def delete_conversation(self, conversation_id: UUID, user_id: UUID) -> bool:
|
||||
"""Удалить беседу"""
|
||||
conversation = await self.get_conversation(conversation_id, user_id)
|
||||
return await self.conversation_repository.delete(conversation_id)
|
||||
|
||||
async def list_user_conversations(self, user_id: UUID, skip: int = 0, limit: int = 100) -> list[Conversation]:
|
||||
"""Получить беседы пользователя"""
|
||||
return await self.conversation_repository.list_by_user(user_id, skip=skip, limit=limit)
|
||||
|
||||
async def _check_collection_access(self, user_id: UUID, collection) -> bool:
|
||||
"""Проверить доступ пользователя к коллекции"""
|
||||
if collection.owner_id == user_id:
|
||||
return True
|
||||
|
||||
if collection.is_public:
|
||||
return True
|
||||
|
||||
access = await self.access_repository.get_by_user_and_collection(user_id, collection.collection_id)
|
||||
return access is not None
|
||||
|
||||
119
backend/src/application/use_cases/document_use_cases.py
Normal file
119
backend/src/application/use_cases/document_use_cases.py
Normal file
@ -0,0 +1,119 @@
|
||||
"""
|
||||
Use cases для работы с документами
|
||||
"""
|
||||
from uuid import UUID
|
||||
from typing import BinaryIO, Optional
|
||||
from src.domain.entities.document import Document
|
||||
from src.domain.repositories.document_repository import IDocumentRepository
|
||||
from src.domain.repositories.collection_repository import ICollectionRepository
|
||||
from src.application.services.document_parser_service import DocumentParserService
|
||||
from src.shared.exceptions import NotFoundError, ForbiddenError
|
||||
|
||||
|
||||
class DocumentUseCases:
|
||||
"""Use cases для документов"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
document_repository: IDocumentRepository,
|
||||
collection_repository: ICollectionRepository,
|
||||
parser_service: DocumentParserService
|
||||
):
|
||||
self.document_repository = document_repository
|
||||
self.collection_repository = collection_repository
|
||||
self.parser_service = parser_service
|
||||
|
||||
async def create_document(
|
||||
self,
|
||||
collection_id: UUID,
|
||||
title: str,
|
||||
content: str,
|
||||
metadata: dict | None = None
|
||||
) -> Document:
|
||||
"""Создать документ"""
|
||||
collection = await self.collection_repository.get_by_id(collection_id)
|
||||
if not collection:
|
||||
raise NotFoundError(f"Коллекция {collection_id} не найдена")
|
||||
|
||||
document = Document(
|
||||
collection_id=collection_id,
|
||||
title=title,
|
||||
content=content,
|
||||
metadata=metadata or {}
|
||||
)
|
||||
return await self.document_repository.create(document)
|
||||
|
||||
async def upload_and_parse_document(
|
||||
self,
|
||||
collection_id: UUID,
|
||||
file: BinaryIO,
|
||||
filename: str,
|
||||
user_id: UUID
|
||||
) -> Document:
|
||||
"""Загрузить и распарсить документ"""
|
||||
collection = await self.collection_repository.get_by_id(collection_id)
|
||||
if not collection:
|
||||
raise NotFoundError(f"Коллекция {collection_id} не найдена")
|
||||
|
||||
if collection.owner_id != user_id:
|
||||
raise ForbiddenError("Только владелец может добавлять документы")
|
||||
|
||||
title, content = await self.parser_service.parse_pdf(file, filename)
|
||||
|
||||
document = Document(
|
||||
collection_id=collection_id,
|
||||
title=title,
|
||||
content=content,
|
||||
metadata={"filename": filename}
|
||||
)
|
||||
return await self.document_repository.create(document)
|
||||
|
||||
async def get_document(self, document_id: UUID) -> Document:
|
||||
"""Получить документ по ID"""
|
||||
document = await self.document_repository.get_by_id(document_id)
|
||||
if not document:
|
||||
raise NotFoundError(f"Документ {document_id} не найден")
|
||||
return document
|
||||
|
||||
async def update_document(
|
||||
self,
|
||||
document_id: UUID,
|
||||
user_id: UUID,
|
||||
title: str | None = None,
|
||||
content: str | None = None,
|
||||
metadata: dict | None = None
|
||||
) -> Document:
|
||||
"""Обновить документ"""
|
||||
document = await self.get_document(document_id)
|
||||
|
||||
collection = await self.collection_repository.get_by_id(document.collection_id)
|
||||
if not collection or collection.owner_id != user_id:
|
||||
raise ForbiddenError("Только владелец коллекции может изменять документы")
|
||||
|
||||
if title is not None:
|
||||
document.title = title
|
||||
if content is not None:
|
||||
document.content = content
|
||||
if metadata is not None:
|
||||
document.metadata = metadata
|
||||
|
||||
return await self.document_repository.update(document)
|
||||
|
||||
async def delete_document(self, document_id: UUID, user_id: UUID) -> bool:
|
||||
"""Удалить документ"""
|
||||
document = await self.get_document(document_id)
|
||||
|
||||
collection = await self.collection_repository.get_by_id(document.collection_id)
|
||||
if not collection or collection.owner_id != user_id:
|
||||
raise ForbiddenError("Только владелец коллекции может удалять документы")
|
||||
|
||||
return await self.document_repository.delete(document_id)
|
||||
|
||||
async def list_collection_documents(self, collection_id: UUID, skip: int = 0, limit: int = 100) -> list[Document]:
|
||||
"""Получить документы коллекции"""
|
||||
collection = await self.collection_repository.get_by_id(collection_id)
|
||||
if not collection:
|
||||
raise NotFoundError(f"Коллекция {collection_id} не найдена")
|
||||
|
||||
return await self.document_repository.list_by_collection(collection_id, skip=skip, limit=limit)
|
||||
|
||||
93
backend/src/application/use_cases/message_use_cases.py
Normal file
93
backend/src/application/use_cases/message_use_cases.py
Normal file
@ -0,0 +1,93 @@
|
||||
"""
|
||||
Use cases для работы с сообщениями
|
||||
"""
|
||||
from uuid import UUID
|
||||
from src.domain.entities.message import Message, MessageRole
|
||||
from src.domain.repositories.message_repository import IMessageRepository
|
||||
from src.domain.repositories.conversation_repository import IConversationRepository
|
||||
from src.shared.exceptions import NotFoundError, ForbiddenError
|
||||
|
||||
|
||||
class MessageUseCases:
|
||||
"""Use cases для сообщений"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message_repository: IMessageRepository,
|
||||
conversation_repository: IConversationRepository
|
||||
):
|
||||
self.message_repository = message_repository
|
||||
self.conversation_repository = conversation_repository
|
||||
|
||||
async def create_message(
|
||||
self,
|
||||
conversation_id: UUID,
|
||||
content: str,
|
||||
role: MessageRole,
|
||||
user_id: UUID,
|
||||
sources: dict | None = None
|
||||
) -> Message:
|
||||
"""Создать сообщение"""
|
||||
conversation = await self.conversation_repository.get_by_id(conversation_id)
|
||||
if not conversation:
|
||||
raise NotFoundError(f"Беседа {conversation_id} не найдена")
|
||||
|
||||
if conversation.user_id != user_id:
|
||||
raise ForbiddenError("Нет доступа к этой беседе")
|
||||
|
||||
message = Message(
|
||||
conversation_id=conversation_id,
|
||||
content=content,
|
||||
role=role,
|
||||
sources=sources or {}
|
||||
)
|
||||
|
||||
conversation.update_timestamp()
|
||||
await self.conversation_repository.update(conversation)
|
||||
|
||||
return await self.message_repository.create(message)
|
||||
|
||||
async def get_message(self, message_id: UUID) -> Message:
|
||||
"""Получить сообщение по ID"""
|
||||
message = await self.message_repository.get_by_id(message_id)
|
||||
if not message:
|
||||
raise NotFoundError(f"Сообщение {message_id} не найдено")
|
||||
return message
|
||||
|
||||
async def update_message(
|
||||
self,
|
||||
message_id: UUID,
|
||||
content: str | None = None,
|
||||
sources: dict | None = None
|
||||
) -> Message:
|
||||
"""Обновить сообщение"""
|
||||
message = await self.get_message(message_id)
|
||||
|
||||
if content is not None:
|
||||
message.content = content
|
||||
if sources is not None:
|
||||
message.sources = sources
|
||||
|
||||
return await self.message_repository.update(message)
|
||||
|
||||
async def delete_message(self, message_id: UUID) -> bool:
|
||||
"""Удалить сообщение"""
|
||||
return await self.message_repository.delete(message_id)
|
||||
|
||||
async def list_conversation_messages(
|
||||
self,
|
||||
conversation_id: UUID,
|
||||
user_id: UUID,
|
||||
skip: int = 0,
|
||||
limit: int = 100
|
||||
) -> list[Message]:
|
||||
"""Получить сообщения беседы"""
|
||||
conversation = await self.conversation_repository.get_by_id(conversation_id)
|
||||
if not conversation:
|
||||
raise NotFoundError(f"Беседа {conversation_id} не найдена")
|
||||
|
||||
if conversation.user_id != user_id:
|
||||
raise ForbiddenError("Нет доступа к этой беседе")
|
||||
|
||||
return await self.message_repository.list_by_conversation(conversation_id, skip=skip, limit=limit)
|
||||
|
||||
55
backend/src/application/use_cases/user_use_cases.py
Normal file
55
backend/src/application/use_cases/user_use_cases.py
Normal file
@ -0,0 +1,55 @@
|
||||
"""
|
||||
Use cases для работы с пользователями
|
||||
"""
|
||||
from uuid import UUID
|
||||
from typing import Optional
|
||||
from src.domain.entities.user import User, UserRole
|
||||
from src.domain.repositories.user_repository import IUserRepository
|
||||
from src.shared.exceptions import NotFoundError, ValidationError
|
||||
|
||||
|
||||
class UserUseCases:
|
||||
"""Use cases для пользователей"""
|
||||
|
||||
def __init__(self, user_repository: IUserRepository):
|
||||
self.user_repository = user_repository
|
||||
|
||||
async def create_user(self, telegram_id: str, role: UserRole = UserRole.USER) -> User:
|
||||
"""Создать пользователя"""
|
||||
existing_user = await self.user_repository.get_by_telegram_id(telegram_id)
|
||||
if existing_user:
|
||||
raise ValidationError(f"Пользователь с telegram_id {telegram_id} уже существует")
|
||||
|
||||
user = User(telegram_id=telegram_id, role=role)
|
||||
return await self.user_repository.create(user)
|
||||
|
||||
async def get_user(self, user_id: UUID) -> User:
|
||||
"""Получить пользователя по ID"""
|
||||
user = await self.user_repository.get_by_id(user_id)
|
||||
if not user:
|
||||
raise NotFoundError(f"Пользователь {user_id} не найден")
|
||||
return user
|
||||
|
||||
async def get_user_by_telegram_id(self, telegram_id: str) -> Optional[User]:
|
||||
"""Получить пользователя по Telegram ID"""
|
||||
return await self.user_repository.get_by_telegram_id(telegram_id)
|
||||
|
||||
async def update_user(self, user_id: UUID, telegram_id: str | None = None, role: UserRole | None = None) -> User:
|
||||
"""Обновить пользователя"""
|
||||
user = await self.get_user(user_id)
|
||||
|
||||
if telegram_id is not None:
|
||||
user.telegram_id = telegram_id
|
||||
if role is not None:
|
||||
user.role = role
|
||||
|
||||
return await self.user_repository.update(user)
|
||||
|
||||
async def delete_user(self, user_id: UUID) -> bool:
|
||||
"""Удалить пользователя"""
|
||||
return await self.user_repository.delete(user_id)
|
||||
|
||||
async def list_users(self, skip: int = 0, limit: int = 100) -> list[User]:
|
||||
"""Получить список пользователей"""
|
||||
return await self.user_repository.list_all(skip=skip, limit=limit)
|
||||
|
||||
4
backend/src/presentation/api/v1/__init__.py
Normal file
4
backend/src/presentation/api/v1/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
"""
|
||||
API v1 роутеры
|
||||
"""
|
||||
|
||||
56
backend/src/presentation/api/v1/admin.py
Normal file
56
backend/src/presentation/api/v1/admin.py
Normal file
@ -0,0 +1,56 @@
|
||||
"""
|
||||
Админ-панель - упрощенная версия через API эндпоинты
|
||||
В будущем можно интегрировать полноценную админ-панель
|
||||
"""
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from typing import List
|
||||
from uuid import UUID
|
||||
from dishka.integrations.fastapi import FromDishka
|
||||
from src.presentation.schemas.user_schemas import UserResponse
|
||||
from src.presentation.schemas.collection_schemas import CollectionResponse
|
||||
from src.presentation.schemas.document_schemas import DocumentResponse
|
||||
from src.presentation.schemas.conversation_schemas import ConversationResponse
|
||||
from src.presentation.schemas.message_schemas import MessageResponse
|
||||
from src.domain.entities.user import User, UserRole
|
||||
from src.application.use_cases.user_use_cases import UserUseCases
|
||||
from src.application.use_cases.collection_use_cases import CollectionUseCases
|
||||
|
||||
router = APIRouter(prefix="/admin", tags=["admin"])
|
||||
|
||||
|
||||
@router.get("/users", response_model=List[UserResponse])
|
||||
async def admin_list_users(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[UserUseCases] = FromDishka()
|
||||
):
|
||||
"""Получить список всех пользователей (только для админов)"""
|
||||
if not current_user.is_admin():
|
||||
raise HTTPException(status_code=403, detail="Требуются права администратора")
|
||||
users = await use_cases.list_users(skip=skip, limit=limit)
|
||||
return [UserResponse.from_entity(user) for user in users]
|
||||
|
||||
|
||||
@router.get("/collections", response_model=List[CollectionResponse])
|
||||
async def admin_list_collections(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[CollectionUseCases] = FromDishka()
|
||||
):
|
||||
"""Получить список всех коллекций (только для админов)"""
|
||||
from src.infrastructure.database.base import AsyncSessionLocal
|
||||
from src.infrastructure.repositories.postgresql.collection_repository import PostgreSQLCollectionRepository
|
||||
from sqlalchemy import select
|
||||
from src.infrastructure.database.models import CollectionModel
|
||||
|
||||
async with AsyncSessionLocal() as session:
|
||||
repo = PostgreSQLCollectionRepository(session)
|
||||
result = await session.execute(
|
||||
select(CollectionModel).offset(skip).limit(limit)
|
||||
)
|
||||
db_collections = result.scalars().all()
|
||||
collections = [repo._to_entity(c) for c in db_collections if c]
|
||||
return [CollectionResponse.from_entity(c) for c in collections if c]
|
||||
|
||||
120
backend/src/presentation/api/v1/collections.py
Normal file
120
backend/src/presentation/api/v1/collections.py
Normal file
@ -0,0 +1,120 @@
|
||||
"""
|
||||
API роутеры для работы с коллекциями
|
||||
"""
|
||||
from uuid import UUID
|
||||
from fastapi import APIRouter, status
|
||||
from fastapi.responses import JSONResponse
|
||||
from typing import List
|
||||
from dishka.integrations.fastapi import FromDishka
|
||||
from src.presentation.schemas.collection_schemas import (
|
||||
CollectionCreate,
|
||||
CollectionUpdate,
|
||||
CollectionResponse,
|
||||
CollectionAccessGrant,
|
||||
CollectionAccessResponse
|
||||
)
|
||||
from src.application.use_cases.collection_use_cases import CollectionUseCases
|
||||
from src.domain.entities.user import User
|
||||
from src.presentation.middleware.auth_middleware import get_current_user
|
||||
|
||||
router = APIRouter(prefix="/collections", tags=["collections"])
|
||||
|
||||
|
||||
@router.post("", response_model=CollectionResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def create_collection(
|
||||
collection_data: CollectionCreate,
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: FromDishka[CollectionUseCases] = FromDishka()
|
||||
):
|
||||
"""Создать коллекцию"""
|
||||
collection = await use_cases.create_collection(
|
||||
name=collection_data.name,
|
||||
owner_id=current_user.user_id,
|
||||
description=collection_data.description,
|
||||
is_public=collection_data.is_public
|
||||
)
|
||||
return CollectionResponse.from_entity(collection)
|
||||
|
||||
|
||||
@router.get("/{collection_id}", response_model=CollectionResponse)
|
||||
async def get_collection(
|
||||
collection_id: UUID,
|
||||
use_cases: FromDishka[CollectionUseCases] = FromDishka()
|
||||
):
|
||||
"""Получить коллекцию по ID"""
|
||||
collection = await use_cases.get_collection(collection_id)
|
||||
return CollectionResponse.from_entity(collection)
|
||||
|
||||
|
||||
@router.put("/{collection_id}", response_model=CollectionResponse)
|
||||
async def update_collection(
|
||||
collection_id: UUID,
|
||||
collection_data: CollectionUpdate,
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: FromDishka[CollectionUseCases] = FromDishka()
|
||||
):
|
||||
"""Обновить коллекцию"""
|
||||
collection = await use_cases.update_collection(
|
||||
collection_id=collection_id,
|
||||
user_id=current_user.user_id,
|
||||
name=collection_data.name,
|
||||
description=collection_data.description,
|
||||
is_public=collection_data.is_public
|
||||
)
|
||||
return CollectionResponse.from_entity(collection)
|
||||
|
||||
|
||||
@router.delete("/{collection_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_collection(
|
||||
collection_id: UUID,
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: FromDishka[CollectionUseCases] = FromDishka()
|
||||
):
|
||||
"""Удалить коллекцию"""
|
||||
await use_cases.delete_collection(collection_id, current_user.user_id)
|
||||
return JSONResponse(status_code=status.HTTP_204_NO_CONTENT, content=None)
|
||||
|
||||
|
||||
@router.get("", response_model=List[CollectionResponse])
|
||||
async def list_collections(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: FromDishka[CollectionUseCases] = FromDishka()
|
||||
):
|
||||
"""Получить список коллекций, доступных пользователю"""
|
||||
collections = await use_cases.list_user_collections(
|
||||
user_id=current_user.user_id,
|
||||
skip=skip,
|
||||
limit=limit
|
||||
)
|
||||
return [CollectionResponse.from_entity(c) for c in collections]
|
||||
|
||||
|
||||
@router.post("/{collection_id}/access", response_model=CollectionAccessResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def grant_access(
|
||||
collection_id: UUID,
|
||||
access_data: CollectionAccessGrant,
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: FromDishka[CollectionUseCases] = FromDishka()
|
||||
):
|
||||
"""Предоставить доступ пользователю к коллекции"""
|
||||
access = await use_cases.grant_access(
|
||||
collection_id=collection_id,
|
||||
user_id=access_data.user_id,
|
||||
owner_id=current_user.user_id
|
||||
)
|
||||
return CollectionAccessResponse.from_entity(access)
|
||||
|
||||
|
||||
@router.delete("/{collection_id}/access/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def revoke_access(
|
||||
collection_id: UUID,
|
||||
user_id: UUID,
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: FromDishka[CollectionUseCases] = FromDishka()
|
||||
):
|
||||
"""Отозвать доступ пользователя к коллекции"""
|
||||
await use_cases.revoke_access(collection_id, user_id, current_user.user_id)
|
||||
return JSONResponse(status_code=status.HTTP_204_NO_CONTENT, content=None)
|
||||
|
||||
69
backend/src/presentation/api/v1/conversations.py
Normal file
69
backend/src/presentation/api/v1/conversations.py
Normal file
@ -0,0 +1,69 @@
|
||||
"""
|
||||
API роутеры для работы с беседами
|
||||
"""
|
||||
from uuid import UUID
|
||||
from fastapi import APIRouter, status
|
||||
from fastapi.responses import JSONResponse
|
||||
from typing import List
|
||||
from dishka.integrations.fastapi import FromDishka
|
||||
from src.presentation.schemas.conversation_schemas import (
|
||||
ConversationCreate,
|
||||
ConversationResponse
|
||||
)
|
||||
from src.application.use_cases.conversation_use_cases import ConversationUseCases
|
||||
from src.domain.entities.user import User
|
||||
|
||||
router = APIRouter(prefix="/conversations", tags=["conversations"])
|
||||
|
||||
|
||||
@router.post("", response_model=ConversationResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def create_conversation(
|
||||
conversation_data: ConversationCreate,
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[ConversationUseCases] = FromDishka()
|
||||
):
|
||||
"""Создать беседу"""
|
||||
conversation = await use_cases.create_conversation(
|
||||
user_id=current_user.user_id,
|
||||
collection_id=conversation_data.collection_id
|
||||
)
|
||||
return ConversationResponse.from_entity(conversation)
|
||||
|
||||
|
||||
@router.get("/{conversation_id}", response_model=ConversationResponse)
|
||||
async def get_conversation(
|
||||
conversation_id: UUID,
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[ConversationUseCases] = FromDishka()
|
||||
):
|
||||
"""Получить беседу по ID"""
|
||||
conversation = await use_cases.get_conversation(conversation_id, current_user.user_id)
|
||||
return ConversationResponse.from_entity(conversation)
|
||||
|
||||
|
||||
@router.delete("/{conversation_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_conversation(
|
||||
conversation_id: UUID,
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[ConversationUseCases] = FromDishka()
|
||||
):
|
||||
"""Удалить беседу"""
|
||||
await use_cases.delete_conversation(conversation_id, current_user.user_id)
|
||||
return JSONResponse(status_code=status.HTTP_204_NO_CONTENT, content=None)
|
||||
|
||||
|
||||
@router.get("", response_model=List[ConversationResponse])
|
||||
async def list_conversations(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[ConversationUseCases] = FromDishka()
|
||||
):
|
||||
"""Получить список бесед пользователя"""
|
||||
conversations = await use_cases.list_user_conversations(
|
||||
user_id=current_user.user_id,
|
||||
skip=skip,
|
||||
limit=limit
|
||||
)
|
||||
return [ConversationResponse.from_entity(c) for c in conversations]
|
||||
|
||||
121
backend/src/presentation/api/v1/documents.py
Normal file
121
backend/src/presentation/api/v1/documents.py
Normal file
@ -0,0 +1,121 @@
|
||||
"""
|
||||
API роутеры для работы с документами
|
||||
"""
|
||||
from uuid import UUID
|
||||
from fastapi import APIRouter, status, UploadFile, File
|
||||
from fastapi.responses import JSONResponse
|
||||
from typing import List
|
||||
from dishka.integrations.fastapi import FromDishka
|
||||
from src.presentation.schemas.document_schemas import (
|
||||
DocumentCreate,
|
||||
DocumentUpdate,
|
||||
DocumentResponse
|
||||
)
|
||||
from src.application.use_cases.document_use_cases import DocumentUseCases
|
||||
from src.domain.entities.user import User
|
||||
|
||||
router = APIRouter(prefix="/documents", tags=["documents"])
|
||||
|
||||
|
||||
@router.post("", response_model=DocumentResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def create_document(
|
||||
document_data: DocumentCreate,
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[DocumentUseCases] = FromDishka()
|
||||
):
|
||||
"""Создать документ"""
|
||||
document = await use_cases.create_document(
|
||||
collection_id=document_data.collection_id,
|
||||
title=document_data.title,
|
||||
content=document_data.content,
|
||||
metadata=document_data.metadata
|
||||
)
|
||||
return DocumentResponse.from_entity(document)
|
||||
|
||||
|
||||
@router.post("/upload", response_model=DocumentResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def upload_document(
|
||||
collection_id: UUID,
|
||||
file: UploadFile = File(...),
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[DocumentUseCases] = FromDishka()
|
||||
):
|
||||
"""Загрузить и распарсить PDF документ или изображение"""
|
||||
if not file.filename:
|
||||
raise JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content={"detail": "Имя файла не указано"}
|
||||
)
|
||||
|
||||
supported_formats = ['.pdf', '.png', '.jpg', '.jpeg', '.tiff', '.bmp']
|
||||
file_ext = file.filename.lower().rsplit('.', 1)[-1] if '.' in file.filename else ''
|
||||
|
||||
if f'.{file_ext}' not in supported_formats:
|
||||
raise JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content={"detail": f"Неподдерживаемый формат файла. Поддерживаются: {', '.join(supported_formats)}"}
|
||||
)
|
||||
|
||||
document = await use_cases.upload_and_parse_document(
|
||||
collection_id=collection_id,
|
||||
file=file.file,
|
||||
filename=file.filename,
|
||||
user_id=current_user.user_id
|
||||
)
|
||||
return DocumentResponse.from_entity(document)
|
||||
|
||||
|
||||
@router.get("/{document_id}", response_model=DocumentResponse)
|
||||
async def get_document(
|
||||
document_id: UUID,
|
||||
use_cases: FromDishka[DocumentUseCases] = FromDishka()
|
||||
):
|
||||
"""Получить документ по ID"""
|
||||
document = await use_cases.get_document(document_id)
|
||||
return DocumentResponse.from_entity(document)
|
||||
|
||||
|
||||
@router.put("/{document_id}", response_model=DocumentResponse)
|
||||
async def update_document(
|
||||
document_id: UUID,
|
||||
document_data: DocumentUpdate,
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[DocumentUseCases] = FromDishka()
|
||||
):
|
||||
"""Обновить документ"""
|
||||
document = await use_cases.update_document(
|
||||
document_id=document_id,
|
||||
user_id=current_user.user_id,
|
||||
title=document_data.title,
|
||||
content=document_data.content,
|
||||
metadata=document_data.metadata
|
||||
)
|
||||
return DocumentResponse.from_entity(document)
|
||||
|
||||
|
||||
@router.delete("/{document_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_document(
|
||||
document_id: UUID,
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[DocumentUseCases] = FromDishka()
|
||||
):
|
||||
"""Удалить документ"""
|
||||
await use_cases.delete_document(document_id, current_user.user_id)
|
||||
return JSONResponse(status_code=status.HTTP_204_NO_CONTENT, content=None)
|
||||
|
||||
|
||||
@router.get("/collection/{collection_id}", response_model=List[DocumentResponse])
|
||||
async def list_collection_documents(
|
||||
collection_id: UUID,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
use_cases: FromDishka[DocumentUseCases] = FromDishka()
|
||||
):
|
||||
"""Получить документы коллекции"""
|
||||
documents = await use_cases.list_collection_documents(
|
||||
collection_id=collection_id,
|
||||
skip=skip,
|
||||
limit=limit
|
||||
)
|
||||
return [DocumentResponse.from_entity(d) for d in documents]
|
||||
|
||||
88
backend/src/presentation/api/v1/messages.py
Normal file
88
backend/src/presentation/api/v1/messages.py
Normal file
@ -0,0 +1,88 @@
|
||||
"""
|
||||
API роутеры для работы с сообщениями
|
||||
"""
|
||||
from uuid import UUID
|
||||
from fastapi import APIRouter, status
|
||||
from fastapi.responses import JSONResponse
|
||||
from typing import List
|
||||
from dishka.integrations.fastapi import FromDishka
|
||||
from src.presentation.schemas.message_schemas import (
|
||||
MessageCreate,
|
||||
MessageUpdate,
|
||||
MessageResponse
|
||||
)
|
||||
from src.application.use_cases.message_use_cases import MessageUseCases
|
||||
from src.domain.entities.user import User
|
||||
|
||||
router = APIRouter(prefix="/messages", tags=["messages"])
|
||||
|
||||
|
||||
@router.post("", response_model=MessageResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def create_message(
|
||||
message_data: MessageCreate,
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[MessageUseCases] = FromDishka()
|
||||
):
|
||||
"""Создать сообщение"""
|
||||
message = await use_cases.create_message(
|
||||
conversation_id=message_data.conversation_id,
|
||||
content=message_data.content,
|
||||
role=message_data.role,
|
||||
user_id=current_user.user_id,
|
||||
sources=message_data.sources
|
||||
)
|
||||
return MessageResponse.from_entity(message)
|
||||
|
||||
|
||||
@router.get("/{message_id}", response_model=MessageResponse)
|
||||
async def get_message(
|
||||
message_id: UUID,
|
||||
use_cases: FromDishka[MessageUseCases] = FromDishka()
|
||||
):
|
||||
"""Получить сообщение по ID"""
|
||||
message = await use_cases.get_message(message_id)
|
||||
return MessageResponse.from_entity(message)
|
||||
|
||||
|
||||
@router.put("/{message_id}", response_model=MessageResponse)
|
||||
async def update_message(
|
||||
message_id: UUID,
|
||||
message_data: MessageUpdate,
|
||||
use_cases: FromDishka[MessageUseCases] = FromDishka()
|
||||
):
|
||||
"""Обновить сообщение"""
|
||||
message = await use_cases.update_message(
|
||||
message_id=message_id,
|
||||
content=message_data.content,
|
||||
sources=message_data.sources
|
||||
)
|
||||
return MessageResponse.from_entity(message)
|
||||
|
||||
|
||||
@router.delete("/{message_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_message(
|
||||
message_id: UUID,
|
||||
use_cases: FromDishka[MessageUseCases] = FromDishka()
|
||||
):
|
||||
"""Удалить сообщение"""
|
||||
await use_cases.delete_message(message_id)
|
||||
return JSONResponse(status_code=status.HTTP_204_NO_CONTENT, content=None)
|
||||
|
||||
|
||||
@router.get("/conversation/{conversation_id}", response_model=List[MessageResponse])
|
||||
async def list_conversation_messages(
|
||||
conversation_id: UUID,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[MessageUseCases] = FromDishka()
|
||||
):
|
||||
"""Получить сообщения беседы"""
|
||||
messages = await use_cases.list_conversation_messages(
|
||||
conversation_id=conversation_id,
|
||||
user_id=current_user.user_id,
|
||||
skip=skip,
|
||||
limit=limit
|
||||
)
|
||||
return [MessageResponse.from_entity(m) for m in messages]
|
||||
|
||||
81
backend/src/presentation/api/v1/users.py
Normal file
81
backend/src/presentation/api/v1/users.py
Normal file
@ -0,0 +1,81 @@
|
||||
"""
|
||||
API роутеры для работы с пользователями
|
||||
"""
|
||||
from uuid import UUID
|
||||
from fastapi import APIRouter, status
|
||||
from fastapi.responses import JSONResponse
|
||||
from typing import List
|
||||
from dishka.integrations.fastapi import FromDishka
|
||||
from src.presentation.schemas.user_schemas import UserCreate, UserUpdate, UserResponse
|
||||
from src.application.use_cases.user_use_cases import UserUseCases
|
||||
from src.domain.entities.user import User
|
||||
|
||||
router = APIRouter(prefix="/users", tags=["users"])
|
||||
|
||||
|
||||
@router.post("", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def create_user(
|
||||
user_data: UserCreate,
|
||||
use_cases: FromDishka[UserUseCases] = FromDishka()
|
||||
):
|
||||
"""Создать пользователя"""
|
||||
user = await use_cases.create_user(
|
||||
telegram_id=user_data.telegram_id,
|
||||
role=user_data.role
|
||||
)
|
||||
return UserResponse.from_entity(user)
|
||||
|
||||
|
||||
@router.get("/me", response_model=UserResponse)
|
||||
async def get_current_user_info(
|
||||
current_user: FromDishka[User] = FromDishka()
|
||||
):
|
||||
"""Получить информацию о текущем пользователе"""
|
||||
return UserResponse.from_entity(current_user)
|
||||
|
||||
|
||||
@router.get("/{user_id}", response_model=UserResponse)
|
||||
async def get_user(
|
||||
user_id: UUID,
|
||||
use_cases: FromDishka[UserUseCases] = FromDishka()
|
||||
):
|
||||
"""Получить пользователя по ID"""
|
||||
user = await use_cases.get_user(user_id)
|
||||
return UserResponse.from_entity(user)
|
||||
|
||||
|
||||
@router.put("/{user_id}", response_model=UserResponse)
|
||||
async def update_user(
|
||||
user_id: UUID,
|
||||
user_data: UserUpdate,
|
||||
use_cases: FromDishka[UserUseCases] = FromDishka()
|
||||
):
|
||||
"""Обновить пользователя"""
|
||||
user = await use_cases.update_user(
|
||||
user_id=user_id,
|
||||
telegram_id=user_data.telegram_id,
|
||||
role=user_data.role
|
||||
)
|
||||
return UserResponse.from_entity(user)
|
||||
|
||||
|
||||
@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_user(
|
||||
user_id: UUID,
|
||||
use_cases: FromDishka[UserUseCases] = FromDishka()
|
||||
):
|
||||
"""Удалить пользователя"""
|
||||
await use_cases.delete_user(user_id)
|
||||
return JSONResponse(status_code=status.HTTP_204_NO_CONTENT, content=None)
|
||||
|
||||
|
||||
@router.get("", response_model=List[UserResponse])
|
||||
async def list_users(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
use_cases: FromDishka[UserUseCases] = FromDishka()
|
||||
):
|
||||
"""Получить список пользователей"""
|
||||
users = await use_cases.list_users(skip=skip, limit=limit)
|
||||
return [UserResponse.from_entity(user) for user in users]
|
||||
|
||||
4
backend/src/presentation/schemas/__init__.py
Normal file
4
backend/src/presentation/schemas/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
"""
|
||||
Pydantic schemas
|
||||
"""
|
||||
|
||||
77
backend/src/presentation/schemas/collection_schemas.py
Normal file
77
backend/src/presentation/schemas/collection_schemas.py
Normal file
@ -0,0 +1,77 @@
|
||||
"""
|
||||
Pydantic схемы для Collection
|
||||
"""
|
||||
from uuid import UUID
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class CollectionBase(BaseModel):
|
||||
"""Базовая схема коллекции"""
|
||||
name: str
|
||||
description: str = ""
|
||||
is_public: bool = False
|
||||
|
||||
|
||||
class CollectionCreate(CollectionBase):
|
||||
"""Схема создания коллекции"""
|
||||
pass
|
||||
|
||||
|
||||
class CollectionUpdate(BaseModel):
|
||||
"""Схема обновления коллекции"""
|
||||
name: str | None = None
|
||||
description: str | None = None
|
||||
is_public: bool | None = None
|
||||
|
||||
|
||||
class CollectionResponse(BaseModel):
|
||||
"""Схема ответа с коллекцией"""
|
||||
collection_id: UUID
|
||||
name: str
|
||||
description: str
|
||||
owner_id: UUID
|
||||
is_public: bool
|
||||
created_at: datetime
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, collection: "Collection") -> "CollectionResponse":
|
||||
"""Создать из доменной сущности"""
|
||||
return cls(
|
||||
collection_id=collection.collection_id,
|
||||
name=collection.name,
|
||||
description=collection.description,
|
||||
owner_id=collection.owner_id,
|
||||
is_public=collection.is_public,
|
||||
created_at=collection.created_at
|
||||
)
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class CollectionAccessGrant(BaseModel):
|
||||
"""Схема предоставления доступа"""
|
||||
user_id: UUID
|
||||
|
||||
|
||||
class CollectionAccessResponse(BaseModel):
|
||||
"""Схема ответа с доступом"""
|
||||
access_id: UUID
|
||||
user_id: UUID
|
||||
collection_id: UUID
|
||||
created_at: datetime
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, access: "CollectionAccess") -> "CollectionAccessResponse":
|
||||
"""Создать из доменной сущности"""
|
||||
return cls(
|
||||
access_id=access.access_id,
|
||||
user_id=access.user_id,
|
||||
collection_id=access.collection_id,
|
||||
created_at=access.created_at
|
||||
)
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
35
backend/src/presentation/schemas/conversation_schemas.py
Normal file
35
backend/src/presentation/schemas/conversation_schemas.py
Normal file
@ -0,0 +1,35 @@
|
||||
"""
|
||||
Pydantic схемы для Conversation
|
||||
"""
|
||||
from uuid import UUID
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ConversationCreate(BaseModel):
|
||||
"""Схема создания беседы"""
|
||||
collection_id: UUID
|
||||
|
||||
|
||||
class ConversationResponse(BaseModel):
|
||||
"""Схема ответа с беседой"""
|
||||
conversation_id: UUID
|
||||
user_id: UUID
|
||||
collection_id: UUID
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, conversation: "Conversation") -> "ConversationResponse":
|
||||
"""Создать из доменной сущности"""
|
||||
return cls(
|
||||
conversation_id=conversation.conversation_id,
|
||||
user_id=conversation.user_id,
|
||||
collection_id=conversation.collection_id,
|
||||
created_at=conversation.created_at,
|
||||
updated_at=conversation.updated_at
|
||||
)
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
52
backend/src/presentation/schemas/document_schemas.py
Normal file
52
backend/src/presentation/schemas/document_schemas.py
Normal file
@ -0,0 +1,52 @@
|
||||
"""
|
||||
Pydantic схемы для Document
|
||||
"""
|
||||
from uuid import UUID
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class DocumentBase(BaseModel):
|
||||
"""Базовая схема документа"""
|
||||
title: str
|
||||
content: str
|
||||
metadata: dict[str, Any] = {}
|
||||
|
||||
|
||||
class DocumentCreate(DocumentBase):
|
||||
"""Схема создания документа"""
|
||||
collection_id: UUID
|
||||
|
||||
|
||||
class DocumentUpdate(BaseModel):
|
||||
"""Схема обновления документа"""
|
||||
title: str | None = None
|
||||
content: str | None = None
|
||||
metadata: dict[str, Any] | None = None
|
||||
|
||||
|
||||
class DocumentResponse(BaseModel):
|
||||
"""Схема ответа с документом"""
|
||||
document_id: UUID
|
||||
collection_id: UUID
|
||||
title: str
|
||||
content: str
|
||||
metadata: dict[str, Any]
|
||||
created_at: datetime
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, document: "Document") -> "DocumentResponse":
|
||||
"""Создать из доменной сущности"""
|
||||
return cls(
|
||||
document_id=document.document_id,
|
||||
collection_id=document.collection_id,
|
||||
title=document.title,
|
||||
content=document.content,
|
||||
metadata=document.metadata,
|
||||
created_at=document.created_at
|
||||
)
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
52
backend/src/presentation/schemas/message_schemas.py
Normal file
52
backend/src/presentation/schemas/message_schemas.py
Normal file
@ -0,0 +1,52 @@
|
||||
"""
|
||||
Pydantic схемы для Message
|
||||
"""
|
||||
from uuid import UUID
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from pydantic import BaseModel
|
||||
from src.domain.entities.message import MessageRole
|
||||
|
||||
|
||||
class MessageBase(BaseModel):
|
||||
"""Базовая схема сообщения"""
|
||||
content: str
|
||||
role: MessageRole
|
||||
sources: dict[str, Any] = {}
|
||||
|
||||
|
||||
class MessageCreate(MessageBase):
|
||||
"""Схема создания сообщения"""
|
||||
conversation_id: UUID
|
||||
|
||||
|
||||
class MessageUpdate(BaseModel):
|
||||
"""Схема обновления сообщения"""
|
||||
content: str | None = None
|
||||
sources: dict[str, Any] | None = None
|
||||
|
||||
|
||||
class MessageResponse(BaseModel):
|
||||
"""Схема ответа с сообщением"""
|
||||
message_id: UUID
|
||||
conversation_id: UUID
|
||||
content: str
|
||||
role: MessageRole
|
||||
sources: dict[str, Any]
|
||||
created_at: datetime
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, message: "Message") -> "MessageResponse":
|
||||
"""Создать из доменной сущности"""
|
||||
return cls(
|
||||
message_id=message.message_id,
|
||||
conversation_id=message.conversation_id,
|
||||
content=message.content,
|
||||
role=message.role,
|
||||
sources=message.sources,
|
||||
created_at=message.created_at
|
||||
)
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
46
backend/src/presentation/schemas/user_schemas.py
Normal file
46
backend/src/presentation/schemas/user_schemas.py
Normal file
@ -0,0 +1,46 @@
|
||||
"""
|
||||
Pydantic схемы для User
|
||||
"""
|
||||
from uuid import UUID
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel
|
||||
from src.domain.entities.user import UserRole
|
||||
|
||||
|
||||
class UserBase(BaseModel):
|
||||
"""Базовая схема пользователя"""
|
||||
telegram_id: str
|
||||
role: UserRole
|
||||
|
||||
|
||||
class UserCreate(UserBase):
|
||||
"""Схема создания пользователя"""
|
||||
pass
|
||||
|
||||
|
||||
class UserUpdate(BaseModel):
|
||||
"""Схема обновления пользователя"""
|
||||
telegram_id: str | None = None
|
||||
role: UserRole | None = None
|
||||
|
||||
|
||||
class UserResponse(BaseModel):
|
||||
"""Схема ответа с пользователем"""
|
||||
user_id: UUID
|
||||
telegram_id: str
|
||||
role: UserRole
|
||||
created_at: datetime
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, user: "User") -> "UserResponse":
|
||||
"""Создать из доменной сущности"""
|
||||
return cls(
|
||||
user_id=user.user_id,
|
||||
telegram_id=user.telegram_id,
|
||||
role=user.role,
|
||||
created_at=user.created_at
|
||||
)
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user