andrewbokh #6
@ -139,3 +139,67 @@ class CollectionUseCases:
|
|||||||
all_collections = {c.collection_id: c for c in owned + public + accessed_collections}
|
all_collections = {c.collection_id: c for c in owned + public + accessed_collections}
|
||||||
return list(all_collections.values())[skip:skip+limit]
|
return list(all_collections.values())[skip:skip+limit]
|
||||||
|
|
||||||
|
async def list_collection_access(self, collection_id: UUID, user_id: UUID) -> list[CollectionAccess]:
|
||||||
|
"""Получить список доступа к коллекции"""
|
||||||
|
collection = await self.get_collection(collection_id)
|
||||||
|
|
||||||
|
has_access = await self.check_access(collection_id, user_id)
|
||||||
|
if not has_access:
|
||||||
|
raise ForbiddenError("У вас нет доступа к этой коллекции")
|
||||||
|
|
||||||
|
return await self.access_repository.list_by_collection(collection_id)
|
||||||
|
|
||||||
|
async def grant_access_by_telegram_id(
|
||||||
|
self,
|
||||||
|
collection_id: UUID,
|
||||||
|
telegram_id: str,
|
||||||
|
owner_id: UUID
|
||||||
|
) -> CollectionAccess:
|
||||||
|
"""Предоставить доступ пользователю к коллекции по Telegram ID"""
|
||||||
|
collection = await self.get_collection(collection_id)
|
||||||
|
|
||||||
|
if collection.owner_id != owner_id:
|
||||||
|
raise ForbiddenError("Только владелец может предоставлять доступ")
|
||||||
|
|
||||||
|
user = await self.user_repository.get_by_telegram_id(telegram_id)
|
||||||
|
if not user:
|
||||||
|
from src.domain.entities.user import User, UserRole
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.info(f"Creating new user with telegram_id: {telegram_id}")
|
||||||
|
user = User(telegram_id=telegram_id, role=UserRole.USER)
|
||||||
|
try:
|
||||||
|
user = await self.user_repository.create(user)
|
||||||
|
logger.info(f"User created successfully: user_id={user.user_id}, telegram_id={user.telegram_id}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error creating user: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
if user.user_id == owner_id:
|
||||||
|
raise ForbiddenError("Владелец уже имеет доступ к коллекции")
|
||||||
|
|
||||||
|
existing_access = await self.access_repository.get_by_user_and_collection(user.user_id, collection_id)
|
||||||
|
if existing_access:
|
||||||
|
return existing_access
|
||||||
|
|
||||||
|
access = CollectionAccess(user_id=user.user_id, collection_id=collection_id)
|
||||||
|
return await self.access_repository.create(access)
|
||||||
|
|
||||||
|
async def revoke_access_by_telegram_id(
|
||||||
|
self,
|
||||||
|
collection_id: UUID,
|
||||||
|
telegram_id: str,
|
||||||
|
owner_id: UUID
|
||||||
|
) -> bool:
|
||||||
|
"""Отозвать доступ пользователя к коллекции по Telegram ID"""
|
||||||
|
collection = await self.get_collection(collection_id)
|
||||||
|
|
||||||
|
if collection.owner_id != owner_id:
|
||||||
|
raise ForbiddenError("Только владелец может отзывать доступ")
|
||||||
|
|
||||||
|
user = await self.user_repository.get_by_telegram_id(telegram_id)
|
||||||
|
if not user:
|
||||||
|
raise NotFoundError(f"Пользователь с telegram_id {telegram_id} не найден")
|
||||||
|
|
||||||
|
return await self.access_repository.delete_by_user_and_collection(user.user_id, collection_id)
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ from typing import BinaryIO, Optional
|
|||||||
from src.domain.entities.document import Document
|
from src.domain.entities.document import Document
|
||||||
from src.domain.repositories.document_repository import IDocumentRepository
|
from src.domain.repositories.document_repository import IDocumentRepository
|
||||||
from src.domain.repositories.collection_repository import ICollectionRepository
|
from src.domain.repositories.collection_repository import ICollectionRepository
|
||||||
|
from src.domain.repositories.collection_access_repository import ICollectionAccessRepository
|
||||||
from src.application.services.document_parser_service import DocumentParserService
|
from src.application.services.document_parser_service import DocumentParserService
|
||||||
from src.shared.exceptions import NotFoundError, ForbiddenError
|
from src.shared.exceptions import NotFoundError, ForbiddenError
|
||||||
|
|
||||||
@ -17,12 +18,25 @@ class DocumentUseCases:
|
|||||||
self,
|
self,
|
||||||
document_repository: IDocumentRepository,
|
document_repository: IDocumentRepository,
|
||||||
collection_repository: ICollectionRepository,
|
collection_repository: ICollectionRepository,
|
||||||
|
access_repository: ICollectionAccessRepository,
|
||||||
parser_service: DocumentParserService
|
parser_service: DocumentParserService
|
||||||
):
|
):
|
||||||
self.document_repository = document_repository
|
self.document_repository = document_repository
|
||||||
self.collection_repository = collection_repository
|
self.collection_repository = collection_repository
|
||||||
|
self.access_repository = access_repository
|
||||||
self.parser_service = parser_service
|
self.parser_service = parser_service
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
async def create_document(
|
async def create_document(
|
||||||
self,
|
self,
|
||||||
collection_id: UUID,
|
collection_id: UUID,
|
||||||
@ -55,8 +69,9 @@ class DocumentUseCases:
|
|||||||
if not collection:
|
if not collection:
|
||||||
raise NotFoundError(f"Коллекция {collection_id} не найдена")
|
raise NotFoundError(f"Коллекция {collection_id} не найдена")
|
||||||
|
|
||||||
if collection.owner_id != user_id:
|
has_access = await self._check_collection_access(user_id, collection)
|
||||||
raise ForbiddenError("Только владелец может добавлять документы")
|
if not has_access:
|
||||||
|
raise ForbiddenError("У вас нет доступа к этой коллекции")
|
||||||
|
|
||||||
title, content = await self.parser_service.parse_pdf(file, filename)
|
title, content = await self.parser_service.parse_pdf(file, filename)
|
||||||
|
|
||||||
@ -87,8 +102,11 @@ class DocumentUseCases:
|
|||||||
document = await self.get_document(document_id)
|
document = await self.get_document(document_id)
|
||||||
|
|
||||||
collection = await self.collection_repository.get_by_id(document.collection_id)
|
collection = await self.collection_repository.get_by_id(document.collection_id)
|
||||||
if not collection or collection.owner_id != user_id:
|
if not collection:
|
||||||
raise ForbiddenError("Только владелец коллекции может изменять документы")
|
raise NotFoundError(f"Коллекция {document.collection_id} не найдена")
|
||||||
|
has_access = await self._check_collection_access(user_id, collection)
|
||||||
|
if not has_access:
|
||||||
|
raise ForbiddenError("У вас нет доступа к этой коллекции")
|
||||||
|
|
||||||
if title is not None:
|
if title is not None:
|
||||||
document.title = title
|
document.title = title
|
||||||
|
|||||||
@ -13,7 +13,9 @@ from src.presentation.schemas.collection_schemas import (
|
|||||||
CollectionUpdate,
|
CollectionUpdate,
|
||||||
CollectionResponse,
|
CollectionResponse,
|
||||||
CollectionAccessGrant,
|
CollectionAccessGrant,
|
||||||
CollectionAccessResponse
|
CollectionAccessResponse,
|
||||||
|
CollectionAccessListResponse,
|
||||||
|
CollectionAccessUserInfo
|
||||||
)
|
)
|
||||||
from src.application.use_cases.collection_use_cases import CollectionUseCases
|
from src.application.use_cases.collection_use_cases import CollectionUseCases
|
||||||
from src.domain.entities.user import User
|
from src.domain.entities.user import User
|
||||||
@ -44,10 +46,19 @@ async def create_collection(
|
|||||||
@inject
|
@inject
|
||||||
async def get_collection(
|
async def get_collection(
|
||||||
collection_id: UUID,
|
collection_id: UUID,
|
||||||
|
request: Request,
|
||||||
|
user_repo: Annotated[IUserRepository, FromDishka()],
|
||||||
use_cases: Annotated[CollectionUseCases, FromDishka()]
|
use_cases: Annotated[CollectionUseCases, FromDishka()]
|
||||||
):
|
):
|
||||||
"""Получить коллекцию по ID"""
|
"""Получить коллекцию по ID"""
|
||||||
|
current_user = await get_current_user(request, user_repo)
|
||||||
collection = await use_cases.get_collection(collection_id)
|
collection = await use_cases.get_collection(collection_id)
|
||||||
|
|
||||||
|
has_access = await use_cases.check_access(collection_id, current_user.user_id)
|
||||||
|
if not has_access:
|
||||||
|
from fastapi import HTTPException
|
||||||
|
raise HTTPException(status_code=403, detail="У вас нет доступа к этой коллекции")
|
||||||
|
|
||||||
return CollectionResponse.from_entity(collection)
|
return CollectionResponse.from_entity(collection)
|
||||||
|
|
||||||
|
|
||||||
@ -138,3 +149,78 @@ async def revoke_access(
|
|||||||
await use_cases.revoke_access(collection_id, user_id, current_user.user_id)
|
await use_cases.revoke_access(collection_id, user_id, current_user.user_id)
|
||||||
return JSONResponse(status_code=status.HTTP_204_NO_CONTENT, content=None)
|
return JSONResponse(status_code=status.HTTP_204_NO_CONTENT, content=None)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{collection_id}/access", response_model=List[CollectionAccessListResponse])
|
||||||
|
@inject
|
||||||
|
async def list_collection_access(
|
||||||
|
collection_id: UUID,
|
||||||
|
request: Request,
|
||||||
|
user_repo: Annotated[IUserRepository, FromDishka()],
|
||||||
|
use_cases: Annotated[CollectionUseCases, FromDishka()]
|
||||||
|
):
|
||||||
|
"""Получить список пользователей с доступом к коллекции"""
|
||||||
|
current_user = await get_current_user(request, user_repo)
|
||||||
|
accesses = await use_cases.list_collection_access(collection_id, current_user.user_id)
|
||||||
|
result = []
|
||||||
|
for access in accesses:
|
||||||
|
user = await user_repo.get_by_id(access.user_id)
|
||||||
|
if user:
|
||||||
|
user_info = CollectionAccessUserInfo(
|
||||||
|
user_id=user.user_id,
|
||||||
|
telegram_id=user.telegram_id,
|
||||||
|
role=user.role.value,
|
||||||
|
created_at=user.created_at
|
||||||
|
)
|
||||||
|
result.append(CollectionAccessListResponse(
|
||||||
|
access_id=access.access_id,
|
||||||
|
user=user_info,
|
||||||
|
collection_id=access.collection_id,
|
||||||
|
created_at=access.created_at
|
||||||
|
))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{collection_id}/access/telegram/{telegram_id}", response_model=CollectionAccessResponse, status_code=status.HTTP_201_CREATED)
|
||||||
|
@inject
|
||||||
|
async def grant_access_by_telegram_id(
|
||||||
|
collection_id: UUID,
|
||||||
|
telegram_id: str,
|
||||||
|
request: Request,
|
||||||
|
user_repo: Annotated[IUserRepository, FromDishka()],
|
||||||
|
use_cases: Annotated[CollectionUseCases, FromDishka()]
|
||||||
|
):
|
||||||
|
"""Предоставить доступ пользователю к коллекции по Telegram ID"""
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
current_user = await get_current_user(request, user_repo)
|
||||||
|
logger.info(f"Granting access: collection_id={collection_id}, target_telegram_id={telegram_id}, owner_id={current_user.user_id}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
access = await use_cases.grant_access_by_telegram_id(
|
||||||
|
collection_id=collection_id,
|
||||||
|
telegram_id=telegram_id,
|
||||||
|
owner_id=current_user.user_id
|
||||||
|
)
|
||||||
|
logger.info(f"Access granted successfully: access_id={access.access_id}")
|
||||||
|
return CollectionAccessResponse.from_entity(access)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error granting access: {e}", exc_info=True)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/{collection_id}/access/telegram/{telegram_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
@inject
|
||||||
|
async def revoke_access_by_telegram_id(
|
||||||
|
collection_id: UUID,
|
||||||
|
telegram_id: str,
|
||||||
|
request: Request,
|
||||||
|
user_repo: Annotated[IUserRepository, FromDishka()],
|
||||||
|
use_cases: Annotated[CollectionUseCases, FromDishka()]
|
||||||
|
):
|
||||||
|
"""Отозвать доступ пользователя к коллекции по Telegram ID"""
|
||||||
|
current_user = await get_current_user(request, user_repo)
|
||||||
|
await use_cases.revoke_access_by_telegram_id(collection_id, telegram_id, current_user.user_id)
|
||||||
|
return JSONResponse(status_code=status.HTTP_204_NO_CONTENT, content=None)
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
API роутеры для работы с документами
|
API роутеры для работы с документами
|
||||||
"""
|
"""
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from fastapi import APIRouter, status, UploadFile, File, Depends, Request
|
from fastapi import APIRouter, status, UploadFile, File, Depends, Request, Query
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
from typing import List, Annotated
|
from typing import List, Annotated
|
||||||
from dishka.integrations.fastapi import FromDishka, inject
|
from dishka.integrations.fastapi import FromDishka, inject
|
||||||
@ -14,6 +14,7 @@ from src.presentation.schemas.document_schemas import (
|
|||||||
DocumentResponse
|
DocumentResponse
|
||||||
)
|
)
|
||||||
from src.application.use_cases.document_use_cases import DocumentUseCases
|
from src.application.use_cases.document_use_cases import DocumentUseCases
|
||||||
|
from src.application.use_cases.collection_use_cases import CollectionUseCases
|
||||||
from src.domain.entities.user import User
|
from src.domain.entities.user import User
|
||||||
|
|
||||||
router = APIRouter(prefix="/documents", tags=["documents"])
|
router = APIRouter(prefix="/documents", tags=["documents"])
|
||||||
@ -41,10 +42,10 @@ async def create_document(
|
|||||||
@router.post("/upload", response_model=DocumentResponse, status_code=status.HTTP_201_CREATED)
|
@router.post("/upload", response_model=DocumentResponse, status_code=status.HTTP_201_CREATED)
|
||||||
@inject
|
@inject
|
||||||
async def upload_document(
|
async def upload_document(
|
||||||
collection_id: UUID,
|
collection_id: UUID = Query(...),
|
||||||
request: Request,
|
request: Request = None,
|
||||||
user_repo: Annotated[IUserRepository, FromDishka()],
|
user_repo: Annotated[IUserRepository, FromDishka()] = None,
|
||||||
use_cases: Annotated[DocumentUseCases, FromDishka()],
|
use_cases: Annotated[DocumentUseCases, FromDishka()] = None,
|
||||||
file: UploadFile = File(...)
|
file: UploadFile = File(...)
|
||||||
):
|
):
|
||||||
"""Загрузить и распарсить PDF документ или изображение"""
|
"""Загрузить и распарсить PDF документ или изображение"""
|
||||||
@ -123,11 +124,22 @@ async def delete_document(
|
|||||||
@inject
|
@inject
|
||||||
async def list_collection_documents(
|
async def list_collection_documents(
|
||||||
collection_id: UUID,
|
collection_id: UUID,
|
||||||
|
request: Request,
|
||||||
|
user_repo: Annotated[IUserRepository, FromDishka()],
|
||||||
use_cases: Annotated[DocumentUseCases, FromDishka()],
|
use_cases: Annotated[DocumentUseCases, FromDishka()],
|
||||||
|
collection_use_cases: Annotated[CollectionUseCases, FromDishka()],
|
||||||
skip: int = 0,
|
skip: int = 0,
|
||||||
limit: int = 100
|
limit: int = 100
|
||||||
):
|
):
|
||||||
"""Получить документы коллекции"""
|
"""Получить документы коллекции"""
|
||||||
|
current_user = await get_current_user(request, user_repo)
|
||||||
|
|
||||||
|
|
||||||
|
has_access = await collection_use_cases.check_access(collection_id, current_user.user_id)
|
||||||
|
if not has_access:
|
||||||
|
from fastapi import HTTPException
|
||||||
|
raise HTTPException(status_code=403, detail="У вас нет доступа к этой коллекции")
|
||||||
|
|
||||||
documents = await use_cases.list_collection_documents(
|
documents = await use_cases.list_collection_documents(
|
||||||
collection_id=collection_id,
|
collection_id=collection_id,
|
||||||
skip=skip,
|
skip=skip,
|
||||||
|
|||||||
@ -75,3 +75,22 @@ class CollectionAccessResponse(BaseModel):
|
|||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionAccessUserInfo(BaseModel):
|
||||||
|
"""Информация о пользователе с доступом"""
|
||||||
|
user_id: UUID
|
||||||
|
telegram_id: str
|
||||||
|
role: str
|
||||||
|
created_at: datetime
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionAccessListResponse(BaseModel):
|
||||||
|
"""Схема ответа со списком доступа"""
|
||||||
|
access_id: UUID
|
||||||
|
user: CollectionAccessUserInfo
|
||||||
|
collection_id: UUID
|
||||||
|
created_at: datetime
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|||||||
@ -151,9 +151,10 @@ class UseCaseProvider(Provider):
|
|||||||
self,
|
self,
|
||||||
document_repo: IDocumentRepository,
|
document_repo: IDocumentRepository,
|
||||||
collection_repo: ICollectionRepository,
|
collection_repo: ICollectionRepository,
|
||||||
|
access_repo: ICollectionAccessRepository,
|
||||||
parser_service: DocumentParserService
|
parser_service: DocumentParserService
|
||||||
) -> DocumentUseCases:
|
) -> DocumentUseCases:
|
||||||
return DocumentUseCases(document_repo, collection_repo, parser_service)
|
return DocumentUseCases(document_repo, collection_repo, access_repo, parser_service)
|
||||||
|
|
||||||
@provide(scope=Scope.REQUEST)
|
@provide(scope=Scope.REQUEST)
|
||||||
def get_conversation_use_cases(
|
def get_conversation_use_cases(
|
||||||
|
|||||||
@ -10,7 +10,8 @@ from tg_bot.infrastructure.telegram.handlers import (
|
|||||||
stats_handler,
|
stats_handler,
|
||||||
question_handler,
|
question_handler,
|
||||||
buy_handler,
|
buy_handler,
|
||||||
collection_handler
|
collection_handler,
|
||||||
|
document_handler
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -27,6 +28,7 @@ async def create_bot() -> tuple[Bot, Dispatcher]:
|
|||||||
dp.include_router(stats_handler.router)
|
dp.include_router(stats_handler.router)
|
||||||
dp.include_router(buy_handler.router)
|
dp.include_router(buy_handler.router)
|
||||||
dp.include_router(collection_handler.router)
|
dp.include_router(collection_handler.router)
|
||||||
|
dp.include_router(document_handler.router)
|
||||||
dp.include_router(question_handler.router)
|
dp.include_router(question_handler.router)
|
||||||
return bot, dp
|
return bot, dp
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,13 @@
|
|||||||
from aiogram import Router
|
from aiogram import Router, F
|
||||||
from aiogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
|
from aiogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
|
||||||
from aiogram.filters import Command
|
from aiogram.filters import Command, StateFilter
|
||||||
|
from aiogram.fsm.context import FSMContext
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from tg_bot.config.settings import settings
|
from tg_bot.config.settings import settings
|
||||||
|
from tg_bot.infrastructure.telegram.states.collection_states import (
|
||||||
|
CollectionAccessStates,
|
||||||
|
CollectionEditStates
|
||||||
|
)
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
@ -24,16 +29,29 @@ async def get_user_collections(telegram_id: str):
|
|||||||
|
|
||||||
async def get_collection_documents(collection_id: str, telegram_id: str):
|
async def get_collection_documents(collection_id: str, telegram_id: str):
|
||||||
try:
|
try:
|
||||||
|
collection_id = str(collection_id).strip()
|
||||||
|
url = f"{settings.BACKEND_URL}/documents/collection/{collection_id}"
|
||||||
|
print(f"DEBUG get_collection_documents: URL={url}, collection_id={collection_id}, telegram_id={telegram_id}")
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(
|
async with session.get(
|
||||||
f"{settings.BACKEND_URL}/documents/collection/{collection_id}",
|
url,
|
||||||
headers={"X-Telegram-ID": telegram_id}
|
headers={"X-Telegram-ID": telegram_id}
|
||||||
) as response:
|
) as response:
|
||||||
if response.status == 200:
|
if response.status == 200:
|
||||||
return await response.json()
|
return await response.json()
|
||||||
|
elif response.status == 422:
|
||||||
|
error_text = await response.text()
|
||||||
|
print(f"Validation error getting documents: {response.status} - {error_text}, collection_id: {collection_id}, URL: {url}")
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
error_text = await response.text()
|
||||||
|
print(f"Error getting documents: {response.status} - {error_text}, collection_id: {collection_id}, URL: {url}")
|
||||||
return []
|
return []
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error getting documents: {e}")
|
print(f"Exception getting documents: {e}, collection_id: {collection_id}, type: {type(collection_id)}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
@ -53,6 +71,91 @@ async def search_in_collection(collection_id: str, query: str, telegram_id: str)
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
async def get_collection_info(collection_id: str, telegram_id: str):
|
||||||
|
"""Получить информацию о коллекции"""
|
||||||
|
try:
|
||||||
|
collection_id = str(collection_id).strip()
|
||||||
|
url = f"{settings.BACKEND_URL}/collections/{collection_id}"
|
||||||
|
print(f"DEBUG get_collection_info: URL={url}, collection_id={collection_id}, telegram_id={telegram_id}")
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(
|
||||||
|
url,
|
||||||
|
headers={"X-Telegram-ID": telegram_id}
|
||||||
|
) as response:
|
||||||
|
if response.status == 200:
|
||||||
|
return await response.json()
|
||||||
|
elif response.status == 422:
|
||||||
|
error_text = await response.text()
|
||||||
|
print(f"Validation error getting collection info: {response.status} - {error_text}, collection_id: {collection_id}, URL: {url}")
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
error_text = await response.text()
|
||||||
|
print(f"Error getting collection info: {response.status} - {error_text}, collection_id: {collection_id}, URL: {url}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Exception getting collection info: {e}, collection_id: {collection_id}, type: {type(collection_id)}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def get_collection_access_list(collection_id: str, telegram_id: str):
|
||||||
|
"""Получить список пользователей с доступом к коллекции"""
|
||||||
|
try:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(
|
||||||
|
f"{settings.BACKEND_URL}/collections/{collection_id}/access",
|
||||||
|
headers={"X-Telegram-ID": telegram_id}
|
||||||
|
) as response:
|
||||||
|
if response.status == 200:
|
||||||
|
return await response.json()
|
||||||
|
return []
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting access list: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
async def grant_collection_access(collection_id: str, telegram_id: str, owner_telegram_id: str):
|
||||||
|
"""Предоставить доступ к коллекции"""
|
||||||
|
try:
|
||||||
|
url = f"{settings.BACKEND_URL}/collections/{collection_id}/access/telegram/{telegram_id}"
|
||||||
|
print(f"DEBUG grant_collection_access: URL={url}, target_telegram_id={telegram_id}, owner_telegram_id={owner_telegram_id}")
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.post(
|
||||||
|
url,
|
||||||
|
headers={"X-Telegram-ID": owner_telegram_id}
|
||||||
|
) as response:
|
||||||
|
if response.status == 201:
|
||||||
|
result = await response.json()
|
||||||
|
print(f"DEBUG: Access granted successfully: {result}")
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
error_text = await response.text()
|
||||||
|
print(f"ERROR granting access: status={response.status}, error={error_text}, target_telegram_id={telegram_id}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Exception granting access: {e}, target_telegram_id={telegram_id}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def revoke_collection_access(collection_id: str, telegram_id: str, owner_telegram_id: str):
|
||||||
|
"""Отозвать доступ к коллекции"""
|
||||||
|
try:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.delete(
|
||||||
|
f"{settings.BACKEND_URL}/collections/{collection_id}/access/telegram/{telegram_id}",
|
||||||
|
headers={"X-Telegram-ID": owner_telegram_id}
|
||||||
|
) as response:
|
||||||
|
return response.status == 204
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error revoking access: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
@router.message(Command("mycollections"))
|
@router.message(Command("mycollections"))
|
||||||
async def cmd_mycollections(message: Message):
|
async def cmd_mycollections(message: Message):
|
||||||
telegram_id = str(message.from_user.id)
|
telegram_id = str(message.from_user.id)
|
||||||
@ -147,26 +250,133 @@ async def cmd_search(message: Message):
|
|||||||
await message.answer(response, parse_mode="HTML")
|
await message.answer(response, parse_mode="HTML")
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data.startswith("collection:"))
|
@router.callback_query(lambda c: c.data.startswith("collection:") and not c.data.startswith("collection:documents:") and not c.data.startswith("collection:edit:") and not c.data.startswith("collection:access:") and not c.data.startswith("collection:view_access:"))
|
||||||
async def show_collection_documents(callback: CallbackQuery):
|
async def show_collection_menu(callback: CallbackQuery):
|
||||||
collection_id = callback.data.split(":")[1]
|
"""Показать меню коллекции с опциями в зависимости от прав"""
|
||||||
|
parts = callback.data.split(":", 1)
|
||||||
|
if len(parts) < 2:
|
||||||
|
await callback.message.answer(
|
||||||
|
"<b>Ошибка</b>\n\nНеверный формат данных.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
await callback.answer()
|
||||||
|
return
|
||||||
|
|
||||||
|
collection_id = parts[1]
|
||||||
telegram_id = str(callback.from_user.id)
|
telegram_id = str(callback.from_user.id)
|
||||||
|
|
||||||
|
print(f"DEBUG: collection_id from callback (menu): {collection_id}, callback_data: {callback.data}")
|
||||||
|
|
||||||
|
await callback.answer("Загружаю информацию...")
|
||||||
|
|
||||||
|
collection_info = await get_collection_info(collection_id, telegram_id)
|
||||||
|
if not collection_info:
|
||||||
|
await callback.message.answer(
|
||||||
|
"<b>Ошибка</b>\n\nНе удалось загрузить информацию о коллекции.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
owner_id = collection_info.get("owner_id")
|
||||||
|
collection_name = collection_info.get("name", "Коллекция")
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(
|
||||||
|
f"{settings.BACKEND_URL}/users/telegram/{telegram_id}"
|
||||||
|
) as response:
|
||||||
|
if response.status == 200:
|
||||||
|
user_info = await response.json()
|
||||||
|
current_user_id = user_info.get("user_id")
|
||||||
|
is_owner = str(owner_id) == str(current_user_id)
|
||||||
|
else:
|
||||||
|
is_owner = False
|
||||||
|
except:
|
||||||
|
is_owner = False
|
||||||
|
|
||||||
|
keyboard_buttons = []
|
||||||
|
|
||||||
|
collection_id_str = str(collection_id)
|
||||||
|
|
||||||
|
if is_owner:
|
||||||
|
keyboard_buttons = [
|
||||||
|
[InlineKeyboardButton(text="Просмотр документов", callback_data=f"collection:documents:{collection_id_str}")],
|
||||||
|
[InlineKeyboardButton(text="Редактировать коллекцию", callback_data=f"collection:edit:{collection_id_str}")],
|
||||||
|
[InlineKeyboardButton(text="Управление доступом", callback_data=f"collection:access:{collection_id_str}")],
|
||||||
|
[InlineKeyboardButton(text="Загрузить документ", callback_data=f"document:upload:{collection_id_str}")],
|
||||||
|
[InlineKeyboardButton(text="Назад к коллекциям", callback_data="collections:list")]
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
keyboard_buttons = [
|
||||||
|
[InlineKeyboardButton(text="Просмотр документов", callback_data=f"collection:documents:{collection_id_str}")],
|
||||||
|
[InlineKeyboardButton(text="Просмотр доступа", callback_data=f"collection:view_access:{collection_id_str}")],
|
||||||
|
[InlineKeyboardButton(text="Назад к коллекциям", callback_data="collections:list")]
|
||||||
|
]
|
||||||
|
|
||||||
|
keyboard = InlineKeyboardMarkup(inline_keyboard=keyboard_buttons)
|
||||||
|
|
||||||
|
role_text = "<b>Владелец</b>" if is_owner else "<b>Доступ</b>"
|
||||||
|
response = f"<b>{collection_name}</b>\n\n"
|
||||||
|
response += f"{role_text}\n\n"
|
||||||
|
response += f"ID: <code>{collection_id}</code>\n\n"
|
||||||
|
response += "Выберите действие:"
|
||||||
|
|
||||||
|
await callback.message.answer(response, parse_mode="HTML", reply_markup=keyboard)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("collection:documents:"))
|
||||||
|
async def show_collection_documents(callback: CallbackQuery):
|
||||||
|
"""Показать документы коллекции"""
|
||||||
|
try:
|
||||||
|
parts = callback.data.split(":", 2)
|
||||||
|
if len(parts) < 3:
|
||||||
|
raise ValueError("Неверный формат callback_data")
|
||||||
|
|
||||||
|
collection_id = parts[2]
|
||||||
|
telegram_id = str(callback.from_user.id)
|
||||||
|
|
||||||
|
print(f"DEBUG: collection_id from callback: {collection_id}, callback_data: {callback.data}")
|
||||||
|
|
||||||
await callback.answer("Загружаю документы...")
|
await callback.answer("Загружаю документы...")
|
||||||
|
|
||||||
|
collection_info = await get_collection_info(collection_id, telegram_id)
|
||||||
|
if not collection_info:
|
||||||
|
await callback.message.answer(
|
||||||
|
"<b>Ошибка</b>\n\nНе удалось загрузить информацию о коллекции. Проверьте, что у вас есть доступ к этой коллекции.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
documents = await get_collection_documents(collection_id, telegram_id)
|
documents = await get_collection_documents(collection_id, telegram_id)
|
||||||
|
|
||||||
if not documents:
|
if not documents:
|
||||||
await callback.message.answer(
|
await callback.message.answer(
|
||||||
f"<b>Коллекция пуста</b>\n\n"
|
f"<b>Коллекция пуста</b>\n\n"
|
||||||
f"В этой коллекции пока нет документов.\n"
|
f"В этой коллекции пока нет документов.",
|
||||||
f"Обратитесь к администратору для добавления документов.",
|
|
||||||
parse_mode="HTML"
|
parse_mode="HTML"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
except IndexError:
|
||||||
|
await callback.message.answer(
|
||||||
|
"<b>Ошибка</b>\n\nНеверный формат данных.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
await callback.answer()
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error in show_collection_documents: {e}")
|
||||||
|
await callback.message.answer(
|
||||||
|
f"<b>Ошибка</b>\n\nПроизошла ошибка при загрузке документов: {str(e)}",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
await callback.answer()
|
||||||
|
return
|
||||||
|
|
||||||
response = f"<b>Документы в коллекции:</b>\n\n"
|
response = f"<b>Документы в коллекции:</b>\n\n"
|
||||||
|
keyboard_buttons = []
|
||||||
|
|
||||||
for i, doc in enumerate(documents[:10], 1):
|
for i, doc in enumerate(documents[:10], 1):
|
||||||
|
doc_id = doc.get("document_id")
|
||||||
title = doc.get("title", "Без названия")
|
title = doc.get("title", "Без названия")
|
||||||
content_preview = doc.get("content", "")[:100]
|
content_preview = doc.get("content", "")[:100]
|
||||||
response += f"{i}. <b>{title}</b>\n"
|
response += f"{i}. <b>{title}</b>\n"
|
||||||
@ -174,9 +384,361 @@ async def show_collection_documents(callback: CallbackQuery):
|
|||||||
response += f" <i>{content_preview}...</i>\n"
|
response += f" <i>{content_preview}...</i>\n"
|
||||||
response += "\n"
|
response += "\n"
|
||||||
|
|
||||||
|
keyboard_buttons.append([
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=f"{title[:30]}",
|
||||||
|
callback_data=f"document:view:{doc_id}"
|
||||||
|
)
|
||||||
|
])
|
||||||
|
|
||||||
if len(documents) > 10:
|
if len(documents) > 10:
|
||||||
response += f"\n<i>Показано 10 из {len(documents)} документов</i>"
|
response += f"\n<i>Показано 10 из {len(documents)} документов</i>"
|
||||||
|
|
||||||
await callback.message.answer(response, parse_mode="HTML")
|
|
||||||
|
collection_id_for_back = str(collection_info.get("collection_id", collection_id))
|
||||||
|
keyboard_buttons.append([
|
||||||
|
InlineKeyboardButton(text="Назад", callback_data=f"collection:{collection_id_for_back}")
|
||||||
|
])
|
||||||
|
|
||||||
|
keyboard = InlineKeyboardMarkup(inline_keyboard=keyboard_buttons)
|
||||||
|
await callback.message.answer(response, parse_mode="HTML", reply_markup=keyboard)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("collection:access:"))
|
||||||
|
async def show_access_management(callback: CallbackQuery):
|
||||||
|
"""Показать меню управления доступом (только для владельца)"""
|
||||||
|
collection_id = callback.data.split(":")[2]
|
||||||
|
telegram_id = str(callback.from_user.id)
|
||||||
|
|
||||||
|
await callback.answer("Загружаю список доступа...")
|
||||||
|
|
||||||
|
access_list = await get_collection_access_list(collection_id, telegram_id)
|
||||||
|
|
||||||
|
response = "<b>Управление доступом</b>\n\n"
|
||||||
|
response += "<b>Пользователи с доступом:</b>\n\n"
|
||||||
|
|
||||||
|
keyboard_buttons = []
|
||||||
|
|
||||||
|
if access_list:
|
||||||
|
for i, access in enumerate(access_list[:10], 1):
|
||||||
|
user = access.get("user", {})
|
||||||
|
user_telegram_id = user.get("telegram_id", "N/A")
|
||||||
|
role = user.get("role", "user")
|
||||||
|
response += f"{i}. <code>{user_telegram_id}</code> ({role})\n"
|
||||||
|
|
||||||
|
keyboard_buttons.append([
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=f" Удалить {user_telegram_id}",
|
||||||
|
callback_data=f"access:remove:{collection_id}:{user_telegram_id}"
|
||||||
|
)
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
response += "<i>Нет пользователей с доступом</i>\n\n"
|
||||||
|
|
||||||
|
keyboard_buttons.extend([
|
||||||
|
[InlineKeyboardButton(text="Добавить доступ", callback_data=f"access:add:{collection_id}")],
|
||||||
|
[InlineKeyboardButton(text="Назад", callback_data=f"collection:{collection_id}")]
|
||||||
|
])
|
||||||
|
|
||||||
|
keyboard = InlineKeyboardMarkup(inline_keyboard=keyboard_buttons)
|
||||||
|
await callback.message.answer(response, parse_mode="HTML", reply_markup=keyboard)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("collection:view_access:"))
|
||||||
|
async def show_access_list(callback: CallbackQuery):
|
||||||
|
"""Показать список пользователей с доступом (read-only для пользователей с доступом)"""
|
||||||
|
collection_id = callback.data.split(":")[2]
|
||||||
|
telegram_id = str(callback.from_user.id)
|
||||||
|
|
||||||
|
await callback.answer("Загружаю список доступа...")
|
||||||
|
|
||||||
|
access_list = await get_collection_access_list(collection_id, telegram_id)
|
||||||
|
|
||||||
|
response = "<b>Пользователи с доступом</b>\n\n"
|
||||||
|
|
||||||
|
if access_list:
|
||||||
|
for i, access in enumerate(access_list[:20], 1):
|
||||||
|
user = access.get("user", {})
|
||||||
|
user_telegram_id = user.get("telegram_id", "N/A")
|
||||||
|
role = user.get("role", "user")
|
||||||
|
response += f"{i}. <code>{user_telegram_id}</code> ({role})\n"
|
||||||
|
else:
|
||||||
|
response += "<i>Нет пользователей с доступом</i>\n"
|
||||||
|
|
||||||
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[[
|
||||||
|
InlineKeyboardButton(text="Назад", callback_data=f"collection:{collection_id}")
|
||||||
|
]])
|
||||||
|
await callback.message.answer(response, parse_mode="HTML", reply_markup=keyboard)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("access:add:"))
|
||||||
|
async def add_access_prompt(callback: CallbackQuery, state: FSMContext):
|
||||||
|
"""Запросить пересылку сообщения для добавления доступа"""
|
||||||
|
collection_id = callback.data.split(":")[2]
|
||||||
|
telegram_id = str(callback.from_user.id)
|
||||||
|
|
||||||
|
await state.update_data(collection_id=collection_id)
|
||||||
|
await state.set_state(CollectionAccessStates.waiting_for_username)
|
||||||
|
|
||||||
|
await callback.message.answer(
|
||||||
|
"<b>Добавить доступ</b>\n\n"
|
||||||
|
"Перешлите любое сообщение от пользователя, которому нужно предоставить доступ.\n\n"
|
||||||
|
"<i>Просто перешлите сообщение от нужного пользователя.</i>",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(StateFilter(CollectionAccessStates.waiting_for_username))
|
||||||
|
async def process_add_access(message: Message, state: FSMContext):
|
||||||
|
"""Обработать добавление доступа через пересылку сообщения"""
|
||||||
|
telegram_id = str(message.from_user.id)
|
||||||
|
data = await state.get_data()
|
||||||
|
collection_id = data.get("collection_id")
|
||||||
|
|
||||||
|
if not collection_id:
|
||||||
|
await message.answer("Ошибка: не указана коллекция")
|
||||||
|
await state.clear()
|
||||||
|
return
|
||||||
|
|
||||||
|
target_telegram_id = None
|
||||||
|
|
||||||
|
if message.forward_from:
|
||||||
|
target_telegram_id = str(message.forward_from.id)
|
||||||
|
elif message.forward_from_chat:
|
||||||
|
await message.answer(
|
||||||
|
"<b>Ошибка</b>\n\n"
|
||||||
|
"Пожалуйста, перешлите сообщение от пользователя, а не из группы или канала.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
await state.clear()
|
||||||
|
return
|
||||||
|
elif message.forward_date:
|
||||||
|
await message.answer(
|
||||||
|
"<b>Информация о пересылке скрыта</b>\n\n"
|
||||||
|
"Пользователь скрыл информацию о пересылке в настройках приватности Telegram.\n\n"
|
||||||
|
"Попросите пользователя временно разрешить пересылку сообщений.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
await state.clear()
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
await message.answer(
|
||||||
|
"<b>Ошибка</b>\n\n"
|
||||||
|
"Пожалуйста, перешлите сообщение от пользователя, которому нужно предоставить доступ.\n\n"
|
||||||
|
"<i>Просто перешлите любое сообщение от нужного пользователя.</i>",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
await state.clear()
|
||||||
|
return
|
||||||
|
|
||||||
|
if not target_telegram_id:
|
||||||
|
await message.answer(
|
||||||
|
"<b>Ошибка</b>\n\n"
|
||||||
|
"Не удалось определить Telegram ID пользователя.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
await state.clear()
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"DEBUG: Attempting to grant access: collection_id={collection_id}, target_telegram_id={target_telegram_id}, owner_telegram_id={telegram_id}")
|
||||||
|
result = await grant_collection_access(collection_id, target_telegram_id, telegram_id)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
user_info = ""
|
||||||
|
if message.forward_from:
|
||||||
|
user_name = message.forward_from.first_name or ""
|
||||||
|
user_username = f"@{message.forward_from.username}" if message.forward_from.username else ""
|
||||||
|
user_info = f"{user_name} {user_username}".strip() or target_telegram_id
|
||||||
|
else:
|
||||||
|
user_info = target_telegram_id
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
f"<b>Доступ предоставлен</b>\n\n"
|
||||||
|
f"Пользователю <code>{target_telegram_id}</code> предоставлен доступ к коллекции.\n\n"
|
||||||
|
f"Пользователь: {user_info}\n\n"
|
||||||
|
f"<i>Примечание: Если пользователь еще не взаимодействовал с ботом, он был автоматически создан в системе.</i>",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await message.answer(
|
||||||
|
"<b>Ошибка</b>\n\n"
|
||||||
|
"Не удалось предоставить доступ. Возможно:\n"
|
||||||
|
"• Доступ уже предоставлен\n"
|
||||||
|
"• Произошла ошибка на сервере\n"
|
||||||
|
"• Вы не являетесь владельцем коллекции\n\n"
|
||||||
|
"Проверьте логи сервера для получения подробной информации.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("access:remove:"))
|
||||||
|
async def remove_access(callback: CallbackQuery):
|
||||||
|
"""Удалить доступ пользователя"""
|
||||||
|
parts = callback.data.split(":")
|
||||||
|
collection_id = parts[2]
|
||||||
|
target_telegram_id = parts[3]
|
||||||
|
owner_telegram_id = str(callback.from_user.id)
|
||||||
|
|
||||||
|
await callback.answer("Удаляю доступ...")
|
||||||
|
|
||||||
|
result = await revoke_collection_access(collection_id, target_telegram_id, owner_telegram_id)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
await callback.message.answer(
|
||||||
|
f"<b>Доступ отозван</b>\n\n"
|
||||||
|
f"Доступ пользователя <code>{target_telegram_id}</code> отозван.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await callback.message.answer(
|
||||||
|
"<b>Ошибка</b>\n\n"
|
||||||
|
"Не удалось отозвать доступ.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("collection:edit:"))
|
||||||
|
async def edit_collection_prompt(callback: CallbackQuery, state: FSMContext):
|
||||||
|
"""Запросить данные для редактирования коллекции"""
|
||||||
|
collection_id = callback.data.split(":")[2]
|
||||||
|
telegram_id = str(callback.from_user.id)
|
||||||
|
|
||||||
|
collection_info = await get_collection_info(collection_id, telegram_id)
|
||||||
|
if not collection_info:
|
||||||
|
await callback.message.answer(
|
||||||
|
"<b>Ошибка</b>\n\nНе удалось загрузить информацию о коллекции.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
await callback.answer()
|
||||||
|
return
|
||||||
|
|
||||||
|
await state.update_data(collection_id=collection_id)
|
||||||
|
await state.set_state(CollectionEditStates.waiting_for_name)
|
||||||
|
|
||||||
|
await callback.message.answer(
|
||||||
|
"<b>Редактирование коллекции</b>\n\n"
|
||||||
|
"Отправьте новое название коллекции или /skip чтобы оставить текущее.\n\n"
|
||||||
|
f"Текущее название: <b>{collection_info.get('name', 'Без названия')}</b>",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(StateFilter(CollectionEditStates.waiting_for_name))
|
||||||
|
async def process_edit_collection_name(message: Message, state: FSMContext):
|
||||||
|
"""Обработать новое название коллекции"""
|
||||||
|
telegram_id = str(message.from_user.id)
|
||||||
|
data = await state.get_data()
|
||||||
|
collection_id = data.get("collection_id")
|
||||||
|
|
||||||
|
if message.text and message.text.strip() == "/skip":
|
||||||
|
new_name = None
|
||||||
|
else:
|
||||||
|
new_name = message.text.strip() if message.text else None
|
||||||
|
|
||||||
|
await state.update_data(name=new_name)
|
||||||
|
await state.set_state(CollectionEditStates.waiting_for_description)
|
||||||
|
|
||||||
|
collection_info = await get_collection_info(collection_id, telegram_id)
|
||||||
|
current_description = collection_info.get("description", "") if collection_info else ""
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
"<b>Описание коллекции</b>\n\n"
|
||||||
|
"Отправьте новое описание коллекции или /skip чтобы оставить текущее.\n\n"
|
||||||
|
f"Текущее описание: <i>{current_description[:100] if current_description else 'Нет описания'}...</i>",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(StateFilter(CollectionEditStates.waiting_for_description))
|
||||||
|
async def process_edit_collection_description(message: Message, state: FSMContext):
|
||||||
|
"""Обработать новое описание коллекции"""
|
||||||
|
telegram_id = str(message.from_user.id)
|
||||||
|
data = await state.get_data()
|
||||||
|
collection_id = data.get("collection_id")
|
||||||
|
name = data.get("name")
|
||||||
|
|
||||||
|
if message.text and message.text.strip() == "/skip":
|
||||||
|
new_description = None
|
||||||
|
else:
|
||||||
|
new_description = message.text.strip() if message.text else None
|
||||||
|
|
||||||
|
try:
|
||||||
|
update_data = {}
|
||||||
|
if name:
|
||||||
|
update_data["name"] = name
|
||||||
|
if new_description:
|
||||||
|
update_data["description"] = new_description
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.put(
|
||||||
|
f"{settings.BACKEND_URL}/collections/{collection_id}",
|
||||||
|
json=update_data,
|
||||||
|
headers={"X-Telegram-ID": telegram_id}
|
||||||
|
) as response:
|
||||||
|
if response.status == 200:
|
||||||
|
await message.answer(
|
||||||
|
"<b>Коллекция обновлена</b>\n\n"
|
||||||
|
"Изменения сохранены.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
error_text = await response.text()
|
||||||
|
await message.answer(
|
||||||
|
f"<b>Ошибка</b>\n\n"
|
||||||
|
f"Не удалось обновить коллекцию: {error_text}",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
await message.answer(
|
||||||
|
f"<b>Ошибка</b>\n\n"
|
||||||
|
f"Произошла ошибка: {str(e)}",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data == "collections:list")
|
||||||
|
async def back_to_collections(callback: CallbackQuery):
|
||||||
|
"""Вернуться к списку коллекций"""
|
||||||
|
telegram_id = str(callback.from_user.id)
|
||||||
|
collections = await get_user_collections(telegram_id)
|
||||||
|
|
||||||
|
if not collections:
|
||||||
|
await callback.message.answer(
|
||||||
|
"<b>У вас пока нет коллекций</b>\n\n"
|
||||||
|
"Обратитесь к администратору для создания коллекций и добавления документов.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
response = "<b>Ваши коллекции документов:</b>\n\n"
|
||||||
|
keyboard_buttons = []
|
||||||
|
|
||||||
|
for i, collection in enumerate(collections[:10], 1):
|
||||||
|
name = collection.get("name", "Без названия")
|
||||||
|
description = collection.get("description", "")
|
||||||
|
collection_id = collection.get("collection_id")
|
||||||
|
|
||||||
|
response += f"{i}. <b>{name}</b>\n"
|
||||||
|
if description:
|
||||||
|
response += f" <i>{description[:50]}...</i>\n"
|
||||||
|
response += f" ID: <code>{collection_id}</code>\n\n"
|
||||||
|
|
||||||
|
keyboard_buttons.append([
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=f"{name}",
|
||||||
|
callback_data=f"collection:{collection_id}"
|
||||||
|
)
|
||||||
|
])
|
||||||
|
|
||||||
|
keyboard = InlineKeyboardMarkup(inline_keyboard=keyboard_buttons)
|
||||||
|
response += "<i>Нажмите на коллекцию, чтобы посмотреть документы</i>"
|
||||||
|
|
||||||
|
await callback.message.answer(response, parse_mode="HTML", reply_markup=keyboard)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
381
tg_bot/infrastructure/telegram/handlers/document_handler.py
Normal file
381
tg_bot/infrastructure/telegram/handlers/document_handler.py
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
"""
|
||||||
|
Обработчики для работы с документами
|
||||||
|
"""
|
||||||
|
from aiogram import Router, F
|
||||||
|
from aiogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
|
||||||
|
from aiogram.filters import StateFilter
|
||||||
|
from aiogram.fsm.context import FSMContext
|
||||||
|
import aiohttp
|
||||||
|
from tg_bot.config.settings import settings
|
||||||
|
from tg_bot.infrastructure.telegram.states.collection_states import (
|
||||||
|
DocumentEditStates,
|
||||||
|
DocumentUploadStates
|
||||||
|
)
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
async def get_document_info(document_id: str, telegram_id: str):
|
||||||
|
"""Получить информацию о документе"""
|
||||||
|
try:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(
|
||||||
|
f"{settings.BACKEND_URL}/documents/{document_id}",
|
||||||
|
headers={"X-Telegram-ID": telegram_id}
|
||||||
|
) as response:
|
||||||
|
if response.status == 200:
|
||||||
|
return await response.json()
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting document info: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_document(document_id: str, telegram_id: str):
|
||||||
|
"""Удалить документ"""
|
||||||
|
try:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.delete(
|
||||||
|
f"{settings.BACKEND_URL}/documents/{document_id}",
|
||||||
|
headers={"X-Telegram-ID": telegram_id}
|
||||||
|
) as response:
|
||||||
|
return response.status == 204
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error deleting document: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def update_document(document_id: str, telegram_id: str, title: str = None, content: str = None):
|
||||||
|
"""Обновить документ"""
|
||||||
|
try:
|
||||||
|
update_data = {}
|
||||||
|
if title:
|
||||||
|
update_data["title"] = title
|
||||||
|
if content:
|
||||||
|
update_data["content"] = content
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.put(
|
||||||
|
f"{settings.BACKEND_URL}/documents/{document_id}",
|
||||||
|
json=update_data,
|
||||||
|
headers={"X-Telegram-ID": telegram_id}
|
||||||
|
) as response:
|
||||||
|
if response.status == 200:
|
||||||
|
return await response.json()
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error updating document: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def upload_document_to_collection(collection_id: str, file_data: bytes, filename: str, telegram_id: str):
|
||||||
|
"""Загрузить документ в коллекцию"""
|
||||||
|
try:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
form_data = aiohttp.FormData()
|
||||||
|
form_data.add_field('file', file_data, filename=filename, content_type='application/octet-stream')
|
||||||
|
|
||||||
|
async with session.post(
|
||||||
|
f"{settings.BACKEND_URL}/documents/upload?collection_id={collection_id}",
|
||||||
|
data=form_data,
|
||||||
|
headers={"X-Telegram-ID": telegram_id}
|
||||||
|
) as response:
|
||||||
|
if response.status == 201:
|
||||||
|
return await response.json()
|
||||||
|
else:
|
||||||
|
error_text = await response.text()
|
||||||
|
print(f"Upload error: {response.status} - {error_text}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error uploading document: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("document:view:"))
|
||||||
|
async def view_document(callback: CallbackQuery):
|
||||||
|
"""Просмотр документа"""
|
||||||
|
document_id = callback.data.split(":")[2]
|
||||||
|
telegram_id = str(callback.from_user.id)
|
||||||
|
|
||||||
|
await callback.answer("Загружаю документ...")
|
||||||
|
|
||||||
|
document = await get_document_info(document_id, telegram_id)
|
||||||
|
if not document:
|
||||||
|
await callback.message.answer(
|
||||||
|
"<b>Ошибка</b>\n\nНе удалось загрузить документ.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
title = document.get("title", "Без названия")
|
||||||
|
content = document.get("content", "")
|
||||||
|
collection_id = document.get("collection_id")
|
||||||
|
|
||||||
|
content_preview = content[:2000] if len(content) > 2000 else content
|
||||||
|
has_more = len(content) > 2000
|
||||||
|
|
||||||
|
response = f"<b>{title}</b>\n\n"
|
||||||
|
response += f"<i>{content_preview}</i>"
|
||||||
|
if has_more:
|
||||||
|
response += "\n\n<i>...</i>"
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(
|
||||||
|
f"{settings.BACKEND_URL}/collections/{collection_id}",
|
||||||
|
headers={"X-Telegram-ID": telegram_id}
|
||||||
|
) as response_collection:
|
||||||
|
if response_collection.status == 200:
|
||||||
|
collection_info = await response_collection.json()
|
||||||
|
owner_id = collection_info.get("owner_id")
|
||||||
|
|
||||||
|
async with session.get(
|
||||||
|
f"{settings.BACKEND_URL}/users/telegram/{telegram_id}"
|
||||||
|
) as response_user:
|
||||||
|
if response_user.status == 200:
|
||||||
|
user_info = await response_user.json()
|
||||||
|
current_user_id = user_info.get("user_id")
|
||||||
|
is_owner = str(owner_id) == str(current_user_id)
|
||||||
|
|
||||||
|
keyboard_buttons = []
|
||||||
|
if is_owner:
|
||||||
|
keyboard_buttons = [
|
||||||
|
[InlineKeyboardButton(text="Редактировать", callback_data=f"document:edit:{document_id}")],
|
||||||
|
[InlineKeyboardButton(text="Удалить", callback_data=f"document:delete:{document_id}")],
|
||||||
|
[InlineKeyboardButton(text="Назад", callback_data=f"collection:documents:{collection_id}")]
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
keyboard_buttons = [
|
||||||
|
[InlineKeyboardButton(text="Редактировать", callback_data=f"document:edit:{document_id}")],
|
||||||
|
[InlineKeyboardButton(text="Назад", callback_data=f"collection:documents:{collection_id}")]
|
||||||
|
]
|
||||||
|
|
||||||
|
keyboard = InlineKeyboardMarkup(inline_keyboard=keyboard_buttons)
|
||||||
|
await callback.message.answer(response, parse_mode="HTML", reply_markup=keyboard)
|
||||||
|
return
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[[
|
||||||
|
InlineKeyboardButton(text="Назад", callback_data=f"collection:documents:{collection_id}")
|
||||||
|
]])
|
||||||
|
await callback.message.answer(response, parse_mode="HTML", reply_markup=keyboard)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("document:edit:"))
|
||||||
|
async def edit_document_prompt(callback: CallbackQuery, state: FSMContext):
|
||||||
|
"""Запросить данные для редактирования документа"""
|
||||||
|
document_id = callback.data.split(":")[2]
|
||||||
|
telegram_id = str(callback.from_user.id)
|
||||||
|
|
||||||
|
document = await get_document_info(document_id, telegram_id)
|
||||||
|
if not document:
|
||||||
|
await callback.message.answer(
|
||||||
|
"<b>Ошибка</b>\n\nНе удалось загрузить документ.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
await callback.answer()
|
||||||
|
return
|
||||||
|
|
||||||
|
await state.update_data(document_id=document_id)
|
||||||
|
await state.set_state(DocumentEditStates.waiting_for_title)
|
||||||
|
|
||||||
|
await callback.message.answer(
|
||||||
|
"<b>Редактирование документа</b>\n\n"
|
||||||
|
"Отправьте новое название документа или /skip чтобы оставить текущее.\n\n"
|
||||||
|
f"Текущее название: <b>{document.get('title', 'Без названия')}</b>",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(StateFilter(DocumentEditStates.waiting_for_title))
|
||||||
|
async def process_edit_title(message: Message, state: FSMContext):
|
||||||
|
"""Обработать новое название документа"""
|
||||||
|
telegram_id = str(message.from_user.id)
|
||||||
|
data = await state.get_data()
|
||||||
|
document_id = data.get("document_id")
|
||||||
|
|
||||||
|
if message.text and message.text.strip() == "/skip":
|
||||||
|
new_title = None
|
||||||
|
else:
|
||||||
|
new_title = message.text.strip() if message.text else None
|
||||||
|
|
||||||
|
await state.update_data(title=new_title)
|
||||||
|
await state.set_state(DocumentEditStates.waiting_for_content)
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
"<b>Содержимое документа</b>\n\n"
|
||||||
|
"Отправьте новое содержимое документа или /skip чтобы оставить текущее.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(StateFilter(DocumentEditStates.waiting_for_content))
|
||||||
|
async def process_edit_content(message: Message, state: FSMContext):
|
||||||
|
"""Обработать новое содержимое документа"""
|
||||||
|
telegram_id = str(message.from_user.id)
|
||||||
|
data = await state.get_data()
|
||||||
|
document_id = data.get("document_id")
|
||||||
|
title = data.get("title")
|
||||||
|
|
||||||
|
if message.text and message.text.strip() == "/skip":
|
||||||
|
new_content = None
|
||||||
|
else:
|
||||||
|
new_content = message.text.strip() if message.text else None
|
||||||
|
|
||||||
|
result = await update_document(document_id, telegram_id, title=title, content=new_content)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
await message.answer(
|
||||||
|
"<b>Документ обновлен</b>\n\n"
|
||||||
|
"Изменения сохранены.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await message.answer(
|
||||||
|
"<b>Ошибка</b>\n\n"
|
||||||
|
"Не удалось обновить документ.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("document:delete:"))
|
||||||
|
async def delete_document_confirm(callback: CallbackQuery):
|
||||||
|
"""Подтверждение удаления документа"""
|
||||||
|
document_id = callback.data.split(":")[2]
|
||||||
|
|
||||||
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
|
[InlineKeyboardButton(text="Да, удалить", callback_data=f"document:delete_confirm:{document_id}")],
|
||||||
|
[InlineKeyboardButton(text="Отмена", callback_data=f"document:view:{document_id}")]
|
||||||
|
])
|
||||||
|
|
||||||
|
await callback.message.answer(
|
||||||
|
"<b>Подтверждение удаления</b>\n\n"
|
||||||
|
"Вы уверены, что хотите удалить этот документ?",
|
||||||
|
parse_mode="HTML",
|
||||||
|
reply_markup=keyboard
|
||||||
|
)
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("document:delete_confirm:"))
|
||||||
|
async def delete_document_execute(callback: CallbackQuery):
|
||||||
|
"""Выполнить удаление документа"""
|
||||||
|
document_id = callback.data.split(":")[2]
|
||||||
|
telegram_id = str(callback.from_user.id)
|
||||||
|
|
||||||
|
await callback.answer("Удаляю документ...")
|
||||||
|
|
||||||
|
# Получаем информацию о документе для возврата к коллекции
|
||||||
|
document = await get_document_info(document_id, telegram_id)
|
||||||
|
collection_id = document.get("collection_id") if document else None
|
||||||
|
|
||||||
|
result = await delete_document(document_id, telegram_id)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
await callback.message.answer(
|
||||||
|
"<b>Документ удален</b>",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await callback.message.answer(
|
||||||
|
"<b>Ошибка</b>\n\n"
|
||||||
|
"Не удалось удалить документ.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("document:upload:"))
|
||||||
|
async def upload_document_prompt(callback: CallbackQuery, state: FSMContext):
|
||||||
|
"""Запросить файл для загрузки"""
|
||||||
|
collection_id = callback.data.split(":")[2]
|
||||||
|
telegram_id = str(callback.from_user.id)
|
||||||
|
|
||||||
|
await state.update_data(collection_id=collection_id)
|
||||||
|
await state.set_state(DocumentUploadStates.waiting_for_file)
|
||||||
|
|
||||||
|
await callback.message.answer(
|
||||||
|
"<b>Загрузка документа</b>\n\n"
|
||||||
|
"Отправьте файл (PDF, PNG, JPG, JPEG, TIFF, BMP).\n\n"
|
||||||
|
"Поддерживаемые форматы:\n"
|
||||||
|
"• PDF\n"
|
||||||
|
"• Изображения: PNG, JPG, JPEG, TIFF, BMP",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(StateFilter(DocumentUploadStates.waiting_for_file), F.document | F.photo)
|
||||||
|
async def process_upload_document(message: Message, state: FSMContext):
|
||||||
|
"""Обработать загрузку документа"""
|
||||||
|
telegram_id = str(message.from_user.id)
|
||||||
|
data = await state.get_data()
|
||||||
|
collection_id = data.get("collection_id")
|
||||||
|
|
||||||
|
if not collection_id:
|
||||||
|
await message.answer("Ошибка: не указана коллекция")
|
||||||
|
await state.clear()
|
||||||
|
return
|
||||||
|
|
||||||
|
file_id = None
|
||||||
|
filename = None
|
||||||
|
|
||||||
|
if message.document:
|
||||||
|
file_id = message.document.file_id
|
||||||
|
filename = message.document.file_name or "document.pdf"
|
||||||
|
|
||||||
|
supported_extensions = ['.pdf', '.png', '.jpg', '.jpeg', '.tiff', '.bmp']
|
||||||
|
file_ext = filename.lower().rsplit('.', 1)[-1] if '.' in filename else ''
|
||||||
|
if f'.{file_ext}' not in supported_extensions:
|
||||||
|
await message.answer(
|
||||||
|
"<b>Ошибка</b>\n\n"
|
||||||
|
f"Неподдерживаемый формат файла: {file_ext}\n\n"
|
||||||
|
"Поддерживаются: PDF, PNG, JPG, JPEG, TIFF, BMP",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
await state.clear()
|
||||||
|
return
|
||||||
|
elif message.photo:
|
||||||
|
file_id = message.photo[-1].file_id
|
||||||
|
filename = "photo.jpg"
|
||||||
|
else:
|
||||||
|
await message.answer(
|
||||||
|
"<b>Ошибка</b>\n\n"
|
||||||
|
"Пожалуйста, отправьте файл (PDF или изображение).",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
await state.clear()
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
file = await message.bot.get_file(file_id)
|
||||||
|
file_data = await message.bot.download_file(file.file_path)
|
||||||
|
file_bytes = file_data.read()
|
||||||
|
|
||||||
|
result = await upload_document_to_collection(collection_id, file_bytes, filename, telegram_id)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
await message.answer(
|
||||||
|
f"<b>Документ загружен</b>\n\n"
|
||||||
|
f"Название: <b>{result.get('title', filename)}</b>",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await message.answer(
|
||||||
|
"<b>Ошибка</b>\n\n"
|
||||||
|
"Не удалось загрузить документ.",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error uploading document: {e}")
|
||||||
|
await message.answer(
|
||||||
|
"<b>Ошибка</b>\n\n"
|
||||||
|
f"Произошла ошибка при загрузке: {str(e)}",
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
27
tg_bot/infrastructure/telegram/states/collection_states.py
Normal file
27
tg_bot/infrastructure/telegram/states/collection_states.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
"""
|
||||||
|
FSM состояния для работы с коллекциями
|
||||||
|
"""
|
||||||
|
from aiogram.fsm.state import State, StatesGroup
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionAccessStates(StatesGroup):
|
||||||
|
"""Состояния для управления доступом к коллекции"""
|
||||||
|
waiting_for_username = State()
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionEditStates(StatesGroup):
|
||||||
|
"""Состояния для редактирования коллекции"""
|
||||||
|
waiting_for_name = State()
|
||||||
|
waiting_for_description = State()
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentEditStates(StatesGroup):
|
||||||
|
"""Состояния для редактирования документа"""
|
||||||
|
waiting_for_title = State()
|
||||||
|
waiting_for_content = State()
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentUploadStates(StatesGroup):
|
||||||
|
"""Состояния для загрузки документа"""
|
||||||
|
waiting_for_file = State()
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user