andrewbokh #6
28
backend/alembic/versions/002_add_premium_fields.py
Normal file
28
backend/alembic/versions/002_add_premium_fields.py
Normal file
@ -0,0 +1,28 @@
|
||||
"""Add premium fields to users
|
||||
|
||||
Revision ID: 002
|
||||
Revises: 001
|
||||
Create Date: 2024-01-02 00:00:00.000000
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision = '002'
|
||||
down_revision = '001'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column('users', sa.Column('is_premium', sa.Boolean(), nullable=False, server_default='false'))
|
||||
op.add_column('users', sa.Column('premium_until', sa.DateTime(), nullable=True))
|
||||
op.add_column('users', sa.Column('questions_used', sa.Integer(), nullable=False, server_default='0'))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column('users', 'questions_used')
|
||||
op.drop_column('users', 'premium_until')
|
||||
op.drop_column('users', 'is_premium')
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
fastapi==0.104.1
|
||||
fastapi==0.100.1
|
||||
uvicorn[standard]==0.24.0
|
||||
sqlalchemy[asyncio]==2.0.23
|
||||
asyncpg==0.29.0
|
||||
|
||||
23
backend/run.py
Normal file
23
backend/run.py
Normal file
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
backend_dir = Path(__file__).parent
|
||||
sys.path.insert(0, str(backend_dir))
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
|
||||
uvicorn.run(
|
||||
"src.presentation.main:app",
|
||||
host="0.0.0.0",
|
||||
port=8000,
|
||||
reload=True,
|
||||
log_level="info"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ Use cases для работы с пользователями
|
||||
"""
|
||||
from uuid import UUID
|
||||
from typing import Optional
|
||||
from datetime import datetime, timedelta
|
||||
from src.domain.entities.user import User, UserRole
|
||||
from src.domain.repositories.user_repository import IUserRepository
|
||||
from src.shared.exceptions import NotFoundError, ValidationError
|
||||
@ -52,4 +53,27 @@ class UserUseCases:
|
||||
async def list_users(self, skip: int = 0, limit: int = 100) -> list[User]:
|
||||
"""Получить список пользователей"""
|
||||
return await self.user_repository.list_all(skip=skip, limit=limit)
|
||||
|
||||
async def increment_questions_used(self, telegram_id: str) -> User:
|
||||
"""Увеличить счетчик использованных вопросов"""
|
||||
user = await self.user_repository.get_by_telegram_id(telegram_id)
|
||||
if not user:
|
||||
raise NotFoundError(f"Пользователь с telegram_id {telegram_id} не найден")
|
||||
|
||||
user.questions_used += 1
|
||||
return await self.user_repository.update(user)
|
||||
|
||||
async def activate_premium(self, telegram_id: str, days: int = 30) -> User:
|
||||
"""Активировать premium статус"""
|
||||
user = await self.user_repository.get_by_telegram_id(telegram_id)
|
||||
if not user:
|
||||
raise NotFoundError(f"Пользователь с telegram_id {telegram_id} не найден")
|
||||
|
||||
user.is_premium = True
|
||||
if user.premium_until and user.premium_until > datetime.utcnow():
|
||||
user.premium_until = user.premium_until + timedelta(days=days)
|
||||
else:
|
||||
user.premium_until = datetime.utcnow() + timedelta(days=days)
|
||||
|
||||
return await self.user_repository.update(user)
|
||||
|
||||
|
||||
@ -20,12 +20,18 @@ class User:
|
||||
telegram_id: str,
|
||||
role: UserRole = UserRole.USER,
|
||||
user_id: UUID | None = None,
|
||||
created_at: datetime | None = None
|
||||
created_at: datetime | None = None,
|
||||
is_premium: bool = False,
|
||||
premium_until: datetime | None = None,
|
||||
questions_used: int = 0
|
||||
):
|
||||
self.user_id = user_id or uuid4()
|
||||
self.telegram_id = telegram_id
|
||||
self.role = role
|
||||
self.created_at = created_at or datetime.utcnow()
|
||||
self.is_premium = is_premium
|
||||
self.premium_until = premium_until
|
||||
self.questions_used = questions_used
|
||||
|
||||
def is_admin(self) -> bool:
|
||||
"""проверка, является ли пользователь администратором"""
|
||||
|
||||
@ -17,6 +17,10 @@ class UserModel(Base):
|
||||
telegram_id = Column(String, unique=True, nullable=False, index=True)
|
||||
role = Column(String, nullable=False, default="user")
|
||||
created_at = Column(DateTime, nullable=False, default=datetime.utcnow)
|
||||
is_premium = Column(Boolean, default=False, nullable=False)
|
||||
premium_until = Column(DateTime, nullable=True)
|
||||
questions_used = Column(Integer, default=0, nullable=False)
|
||||
|
||||
collections = relationship("CollectionModel", back_populates="owner", cascade="all, delete-orphan")
|
||||
conversations = relationship("ConversationModel", back_populates="user", cascade="all, delete-orphan")
|
||||
collection_accesses = relationship("CollectionAccessModel", back_populates="user", cascade="all, delete-orphan")
|
||||
|
||||
@ -23,7 +23,10 @@ class PostgreSQLUserRepository(IUserRepository):
|
||||
user_id=user.user_id,
|
||||
telegram_id=user.telegram_id,
|
||||
role=user.role.value,
|
||||
created_at=user.created_at
|
||||
created_at=user.created_at,
|
||||
is_premium=user.is_premium,
|
||||
premium_until=user.premium_until,
|
||||
questions_used=user.questions_used
|
||||
)
|
||||
self.session.add(db_user)
|
||||
await self.session.commit()
|
||||
@ -57,6 +60,9 @@ class PostgreSQLUserRepository(IUserRepository):
|
||||
|
||||
db_user.telegram_id = user.telegram_id
|
||||
db_user.role = user.role.value
|
||||
db_user.is_premium = user.is_premium
|
||||
db_user.premium_until = user.premium_until
|
||||
db_user.questions_used = user.questions_used
|
||||
await self.session.commit()
|
||||
await self.session.refresh(db_user)
|
||||
return self._to_entity(db_user)
|
||||
@ -90,6 +96,9 @@ class PostgreSQLUserRepository(IUserRepository):
|
||||
user_id=db_user.user_id,
|
||||
telegram_id=db_user.telegram_id,
|
||||
role=UserRole(db_user.role),
|
||||
created_at=db_user.created_at
|
||||
created_at=db_user.created_at,
|
||||
is_premium=db_user.is_premium,
|
||||
premium_until=db_user.premium_until,
|
||||
questions_used=db_user.questions_used
|
||||
)
|
||||
|
||||
|
||||
@ -24,8 +24,8 @@ router = APIRouter(prefix="/admin", tags=["admin"])
|
||||
async def admin_list_users(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[UserUseCases] = FromDishka()
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: UserUseCases = FromDishka()
|
||||
):
|
||||
"""Получить список всех пользователей (только для админов)"""
|
||||
if not current_user.is_admin():
|
||||
@ -38,8 +38,8 @@ async def admin_list_users(
|
||||
async def admin_list_collections(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[CollectionUseCases] = FromDishka()
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: CollectionUseCases = FromDishka()
|
||||
):
|
||||
"""Получить список всех коллекций (только для админов)"""
|
||||
from src.infrastructure.database.base import AsyncSessionLocal
|
||||
|
||||
@ -24,8 +24,8 @@ 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: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[CollectionUseCases] = FromDishka()
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: CollectionUseCases = FromDishka()
|
||||
):
|
||||
"""Создать коллекцию"""
|
||||
collection = await use_cases.create_collection(
|
||||
@ -40,7 +40,7 @@ async def create_collection(
|
||||
@router.get("/{collection_id}", response_model=CollectionResponse)
|
||||
async def get_collection(
|
||||
collection_id: UUID,
|
||||
use_cases: FromDishka[CollectionUseCases] = FromDishka()
|
||||
use_cases: CollectionUseCases = FromDishka()
|
||||
):
|
||||
"""Получить коллекцию по ID"""
|
||||
collection = await use_cases.get_collection(collection_id)
|
||||
@ -51,8 +51,8 @@ async def get_collection(
|
||||
async def update_collection(
|
||||
collection_id: UUID,
|
||||
collection_data: CollectionUpdate,
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[CollectionUseCases] = FromDishka()
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: CollectionUseCases = FromDishka()
|
||||
):
|
||||
"""Обновить коллекцию"""
|
||||
collection = await use_cases.update_collection(
|
||||
@ -68,8 +68,8 @@ async def update_collection(
|
||||
@router.delete("/{collection_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_collection(
|
||||
collection_id: UUID,
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[CollectionUseCases] = FromDishka()
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: CollectionUseCases = FromDishka()
|
||||
):
|
||||
"""Удалить коллекцию"""
|
||||
await use_cases.delete_collection(collection_id, current_user.user_id)
|
||||
@ -80,8 +80,8 @@ async def delete_collection(
|
||||
async def list_collections(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[CollectionUseCases] = FromDishka()
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: CollectionUseCases = FromDishka()
|
||||
):
|
||||
"""Получить список коллекций, доступных пользователю"""
|
||||
collections = await use_cases.list_user_collections(
|
||||
@ -96,8 +96,8 @@ async def list_collections(
|
||||
async def grant_access(
|
||||
collection_id: UUID,
|
||||
access_data: CollectionAccessGrant,
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[CollectionUseCases] = FromDishka()
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: CollectionUseCases = FromDishka()
|
||||
):
|
||||
"""Предоставить доступ пользователю к коллекции"""
|
||||
access = await use_cases.grant_access(
|
||||
@ -112,8 +112,8 @@ async def grant_access(
|
||||
async def revoke_access(
|
||||
collection_id: UUID,
|
||||
user_id: UUID,
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[CollectionUseCases] = FromDishka()
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: CollectionUseCases = FromDishka()
|
||||
):
|
||||
"""Отозвать доступ пользователя к коллекции"""
|
||||
await use_cases.revoke_access(collection_id, user_id, current_user.user_id)
|
||||
|
||||
@ -21,8 +21,8 @@ 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()
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: ConversationUseCases = FromDishka()
|
||||
):
|
||||
"""Создать беседу"""
|
||||
conversation = await use_cases.create_conversation(
|
||||
@ -35,8 +35,8 @@ async def create_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()
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: ConversationUseCases = FromDishka()
|
||||
):
|
||||
"""Получить беседу по ID"""
|
||||
conversation = await use_cases.get_conversation(conversation_id, current_user.user_id)
|
||||
@ -46,8 +46,8 @@ async def get_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()
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: ConversationUseCases = FromDishka()
|
||||
):
|
||||
"""Удалить беседу"""
|
||||
await use_cases.delete_conversation(conversation_id, current_user.user_id)
|
||||
@ -58,8 +58,8 @@ async def delete_conversation(
|
||||
async def list_conversations(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[ConversationUseCases] = FromDishka()
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: ConversationUseCases = FromDishka()
|
||||
):
|
||||
"""Получить список бесед пользователя"""
|
||||
conversations = await use_cases.list_user_conversations(
|
||||
|
||||
@ -22,8 +22,8 @@ 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()
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: DocumentUseCases = FromDishka()
|
||||
):
|
||||
"""Создать документ"""
|
||||
document = await use_cases.create_document(
|
||||
@ -39,8 +39,8 @@ async def create_document(
|
||||
async def upload_document(
|
||||
collection_id: UUID,
|
||||
file: UploadFile = File(...),
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[DocumentUseCases] = FromDishka()
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: DocumentUseCases = FromDishka()
|
||||
):
|
||||
"""Загрузить и распарсить PDF документ или изображение"""
|
||||
if not file.filename:
|
||||
@ -70,7 +70,7 @@ async def upload_document(
|
||||
@router.get("/{document_id}", response_model=DocumentResponse)
|
||||
async def get_document(
|
||||
document_id: UUID,
|
||||
use_cases: FromDishka[DocumentUseCases] = FromDishka()
|
||||
use_cases: DocumentUseCases = FromDishka()
|
||||
):
|
||||
"""Получить документ по ID"""
|
||||
document = await use_cases.get_document(document_id)
|
||||
@ -81,8 +81,8 @@ async def get_document(
|
||||
async def update_document(
|
||||
document_id: UUID,
|
||||
document_data: DocumentUpdate,
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[DocumentUseCases] = FromDishka()
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: DocumentUseCases = FromDishka()
|
||||
):
|
||||
"""Обновить документ"""
|
||||
document = await use_cases.update_document(
|
||||
@ -98,8 +98,8 @@ async def update_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()
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: DocumentUseCases = FromDishka()
|
||||
):
|
||||
"""Удалить документ"""
|
||||
await use_cases.delete_document(document_id, current_user.user_id)
|
||||
@ -111,7 +111,7 @@ async def list_collection_documents(
|
||||
collection_id: UUID,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
use_cases: FromDishka[DocumentUseCases] = FromDishka()
|
||||
use_cases: DocumentUseCases = FromDishka()
|
||||
):
|
||||
"""Получить документы коллекции"""
|
||||
documents = await use_cases.list_collection_documents(
|
||||
|
||||
@ -22,8 +22,8 @@ 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()
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: MessageUseCases = FromDishka()
|
||||
):
|
||||
"""Создать сообщение"""
|
||||
message = await use_cases.create_message(
|
||||
@ -39,7 +39,7 @@ async def create_message(
|
||||
@router.get("/{message_id}", response_model=MessageResponse)
|
||||
async def get_message(
|
||||
message_id: UUID,
|
||||
use_cases: FromDishka[MessageUseCases] = FromDishka()
|
||||
use_cases: MessageUseCases = FromDishka()
|
||||
):
|
||||
"""Получить сообщение по ID"""
|
||||
message = await use_cases.get_message(message_id)
|
||||
@ -50,7 +50,7 @@ async def get_message(
|
||||
async def update_message(
|
||||
message_id: UUID,
|
||||
message_data: MessageUpdate,
|
||||
use_cases: FromDishka[MessageUseCases] = FromDishka()
|
||||
use_cases: MessageUseCases = FromDishka()
|
||||
):
|
||||
"""Обновить сообщение"""
|
||||
message = await use_cases.update_message(
|
||||
@ -64,7 +64,7 @@ async def update_message(
|
||||
@router.delete("/{message_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_message(
|
||||
message_id: UUID,
|
||||
use_cases: FromDishka[MessageUseCases] = FromDishka()
|
||||
use_cases: MessageUseCases = FromDishka()
|
||||
):
|
||||
"""Удалить сообщение"""
|
||||
await use_cases.delete_message(message_id)
|
||||
@ -76,8 +76,8 @@ async def list_conversation_messages(
|
||||
conversation_id: UUID,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: FromDishka[MessageUseCases] = FromDishka()
|
||||
current_user: User = FromDishka(),
|
||||
use_cases: MessageUseCases = FromDishka()
|
||||
):
|
||||
"""Получить сообщения беседы"""
|
||||
messages = await use_cases.list_conversation_messages(
|
||||
|
||||
@ -21,8 +21,8 @@ router = APIRouter(prefix="/rag", tags=["rag"])
|
||||
@router.post("/index", response_model=IndexDocumentResponse, status_code=status.HTTP_200_OK)
|
||||
async def index_document(
|
||||
body: IndexDocumentRequest,
|
||||
use_cases: FromDishka[RAGUseCases] = FromDishka(),
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: RAGUseCases = FromDishka(),
|
||||
current_user: User = FromDishka(),
|
||||
):
|
||||
"""Индексирование идет через чанкирование, далее эмбеддинг и загрузка в векторную бд"""
|
||||
result = await use_cases.index_document(body.document_id)
|
||||
@ -32,8 +32,8 @@ async def index_document(
|
||||
@router.post("/question", response_model=RAGAnswer, status_code=status.HTTP_200_OK)
|
||||
async def ask_question(
|
||||
body: QuestionRequest,
|
||||
use_cases: FromDishka[RAGUseCases] = FromDishka(),
|
||||
current_user: FromDishka[User] = FromDishka(),
|
||||
use_cases: RAGUseCases = FromDishka(),
|
||||
current_user: User = FromDishka(),
|
||||
):
|
||||
"""Отвечает на вопрос, используя RAG в рамках беседы"""
|
||||
result = await use_cases.ask_question(
|
||||
|
||||
@ -1,83 +1,126 @@
|
||||
"""
|
||||
API роутеры для работы с пользователями
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
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]
|
||||
|
||||
"""
|
||||
API роутеры для работы с пользователями
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from uuid import UUID
|
||||
from fastapi import APIRouter, status, Depends
|
||||
from fastapi.responses import JSONResponse
|
||||
from typing import List, Annotated
|
||||
from dishka.integrations.fastapi import FromDishka, inject
|
||||
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)
|
||||
@inject
|
||||
async def create_user(
|
||||
user_data: UserCreate,
|
||||
use_cases: UserUseCases = FromDishka()
|
||||
) -> UserResponse:
|
||||
"""Создать пользователя"""
|
||||
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)
|
||||
@inject
|
||||
async def get_current_user_info(
|
||||
current_user: User = FromDishka()
|
||||
):
|
||||
"""Получить информацию о текущем пользователе"""
|
||||
return UserResponse.from_entity(current_user)
|
||||
|
||||
|
||||
@router.get("/telegram/{telegram_id}", response_model=UserResponse)
|
||||
@inject
|
||||
async def get_user_by_telegram_id(
|
||||
telegram_id: str,
|
||||
use_cases: UserUseCases = FromDishka()
|
||||
):
|
||||
"""Получить пользователя по Telegram ID"""
|
||||
user = await use_cases.get_user_by_telegram_id(telegram_id)
|
||||
if not user:
|
||||
from fastapi import HTTPException
|
||||
raise HTTPException(status_code=404, detail=f"Пользователь с telegram_id {telegram_id} не найден")
|
||||
return UserResponse.from_entity(user)
|
||||
|
||||
|
||||
@router.post("/telegram/{telegram_id}/increment-questions", response_model=UserResponse)
|
||||
@inject
|
||||
async def increment_questions(
|
||||
telegram_id: str,
|
||||
use_cases: UserUseCases = FromDishka()
|
||||
):
|
||||
"""Увеличить счетчик использованных вопросов"""
|
||||
user = await use_cases.increment_questions_used(telegram_id)
|
||||
return UserResponse.from_entity(user)
|
||||
|
||||
|
||||
@router.post("/telegram/{telegram_id}/activate-premium", response_model=UserResponse)
|
||||
@inject
|
||||
async def activate_premium(
|
||||
telegram_id: str,
|
||||
days: int = 30,
|
||||
use_cases: UserUseCases = FromDishka()
|
||||
):
|
||||
"""Активировать premium статус"""
|
||||
user = await use_cases.activate_premium(telegram_id, days=days)
|
||||
return UserResponse.from_entity(user)
|
||||
|
||||
|
||||
@router.get("/{user_id}", response_model=UserResponse)
|
||||
@inject
|
||||
async def get_user(
|
||||
user_id: UUID,
|
||||
use_cases: UserUseCases = FromDishka()
|
||||
):
|
||||
"""Получить пользователя по ID"""
|
||||
user = await use_cases.get_user(user_id)
|
||||
return UserResponse.from_entity(user)
|
||||
|
||||
|
||||
@router.put("/{user_id}", response_model=UserResponse)
|
||||
@inject
|
||||
async def update_user(
|
||||
user_id: UUID,
|
||||
user_data: UserUpdate,
|
||||
use_cases: 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)
|
||||
@inject
|
||||
async def delete_user(
|
||||
user_id: UUID,
|
||||
use_cases: 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])
|
||||
@inject
|
||||
async def list_users(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
use_cases: UserUseCases = FromDishka()
|
||||
):
|
||||
"""Получить список пользователей"""
|
||||
users = await use_cases.list_users(skip=skip, limit=limit)
|
||||
return [UserResponse.from_entity(user) for user in users]
|
||||
|
||||
|
||||
@ -2,9 +2,11 @@ from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
if '/app' not in sys.path:
|
||||
sys.path.insert(0, '/app')
|
||||
backend_dir = Path(__file__).parent.parent.parent
|
||||
if str(backend_dir) not in sys.path:
|
||||
sys.path.insert(0, str(backend_dir))
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
@ -30,6 +30,9 @@ class UserResponse(BaseModel):
|
||||
telegram_id: str
|
||||
role: UserRole
|
||||
created_at: datetime
|
||||
is_premium: bool = False
|
||||
premium_until: datetime | None = None
|
||||
questions_used: int = 0
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, user: "User") -> "UserResponse":
|
||||
@ -38,7 +41,10 @@ class UserResponse(BaseModel):
|
||||
user_id=user.user_id,
|
||||
telegram_id=user.telegram_id,
|
||||
role=user.role,
|
||||
created_at=user.created_at
|
||||
created_at=user.created_at,
|
||||
is_premium=user.is_premium,
|
||||
premium_until=user.premium_until,
|
||||
questions_used=user.questions_used
|
||||
)
|
||||
|
||||
class Config:
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
from dishka import Container, Provider, Scope, provide
|
||||
from fastapi import Request
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from contextlib import asynccontextmanager
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
from src.infrastructure.database.base import AsyncSessionLocal
|
||||
from src.infrastructure.repositories.postgresql.user_repository import PostgreSQLUserRepository
|
||||
@ -39,8 +39,7 @@ from src.application.use_cases.rag_use_cases import RAGUseCases
|
||||
|
||||
class DatabaseProvider(Provider):
|
||||
@provide(scope=Scope.REQUEST)
|
||||
@asynccontextmanager
|
||||
async def get_db(self) -> AsyncSession:
|
||||
async def get_db(self) -> AsyncIterator[AsyncSession]:
|
||||
async with AsyncSessionLocal() as session:
|
||||
try:
|
||||
yield session
|
||||
@ -77,7 +76,7 @@ class RepositoryProvider(Provider):
|
||||
class ServiceProvider(Provider):
|
||||
@provide(scope=Scope.APP)
|
||||
def get_redis_client(self) -> RedisClient:
|
||||
return RedisClient()
|
||||
return RedisClient(host=settings.REDIS_HOST, port=settings.REDIS_PORT)
|
||||
|
||||
@provide(scope=Scope.APP)
|
||||
def get_cache_service(self, redis_client: RedisClient) -> CacheService:
|
||||
|
||||
@ -2,8 +2,6 @@ import aiohttp
|
||||
from tg_bot.infrastructure.external.deepseek_client import DeepSeekClient
|
||||
from tg_bot.config.settings import settings
|
||||
|
||||
BACKEND_URL = "http://localhost:8001/api/v1"
|
||||
|
||||
|
||||
class RAGService:
|
||||
|
||||
@ -19,7 +17,7 @@ class RAGService:
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
f"{BACKEND_URL}/users/telegram/{user_telegram_id}"
|
||||
f"{settings.BACKEND_URL}/users/telegram/{user_telegram_id}"
|
||||
) as user_response:
|
||||
if user_response.status != 200:
|
||||
return []
|
||||
@ -31,7 +29,7 @@ class RAGService:
|
||||
return []
|
||||
|
||||
async with session.get(
|
||||
f"{BACKEND_URL}/collections/",
|
||||
f"{settings.BACKEND_URL}/collections/",
|
||||
headers={"X-Telegram-ID": user_telegram_id}
|
||||
) as collections_response:
|
||||
if collections_response.status != 200:
|
||||
@ -48,7 +46,7 @@ class RAGService:
|
||||
try:
|
||||
async with aiohttp.ClientSession() as search_session:
|
||||
async with search_session.get(
|
||||
f"{BACKEND_URL}/documents/collection/{collection_id}",
|
||||
f"{settings.BACKEND_URL}/documents/collection/{collection_id}",
|
||||
params={"search": query, "limit": limit_per_collection},
|
||||
headers={"X-Telegram-ID": user_telegram_id}
|
||||
) as search_response:
|
||||
|
||||
@ -17,7 +17,6 @@ class Settings(BaseSettings):
|
||||
TELEGRAM_BOT_TOKEN: str = ""
|
||||
FREE_QUESTIONS_LIMIT: int = 5
|
||||
PAYMENT_AMOUNT: float = 500.0
|
||||
DATABASE_URL: str = "sqlite:///data/bot.db"
|
||||
LOG_LEVEL: str = "INFO"
|
||||
LOG_FILE: str = "logs/bot.log"
|
||||
|
||||
@ -28,6 +27,8 @@ class Settings(BaseSettings):
|
||||
|
||||
DEEPSEEK_API_KEY: Optional[str] = None
|
||||
DEEPSEEK_API_URL: str = "https://api.deepseek.com/v1/chat/completions"
|
||||
|
||||
BACKEND_URL: str = "http://localhost:8001/api/v1"
|
||||
|
||||
ADMIN_IDS_STR: str = ""
|
||||
|
||||
|
||||
@ -1,20 +1,60 @@
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import aiohttp
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from tg_bot.infrastructure.database.models import UserModel
|
||||
from tg_bot.config.settings import settings
|
||||
|
||||
|
||||
class User:
|
||||
"""Модель пользователя для телеграм-бота"""
|
||||
def __init__(self, data: dict):
|
||||
self.user_id = data.get("user_id")
|
||||
self.telegram_id = data.get("telegram_id")
|
||||
self.role = data.get("role")
|
||||
created_at_str = data.get("created_at")
|
||||
if created_at_str:
|
||||
try:
|
||||
created_at_str = created_at_str.replace("Z", "+00:00")
|
||||
self.created_at = datetime.fromisoformat(created_at_str)
|
||||
except (ValueError, AttributeError):
|
||||
self.created_at = None
|
||||
else:
|
||||
self.created_at = None
|
||||
|
||||
premium_until_str = data.get("premium_until")
|
||||
if premium_until_str:
|
||||
try:
|
||||
premium_until_str = premium_until_str.replace("Z", "+00:00")
|
||||
self.premium_until = datetime.fromisoformat(premium_until_str)
|
||||
except (ValueError, AttributeError):
|
||||
self.premium_until = None
|
||||
else:
|
||||
self.premium_until = None
|
||||
|
||||
self.is_premium = data.get("is_premium", False)
|
||||
self.questions_used = data.get("questions_used", 0)
|
||||
|
||||
|
||||
class UserService:
|
||||
"""Сервис для работы с пользователями через API бэкенда"""
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.session = session
|
||||
def __init__(self):
|
||||
self.backend_url = settings.BACKEND_URL
|
||||
|
||||
async def get_user_by_telegram_id(self, telegram_id: int) -> Optional[UserModel]:
|
||||
result = await self.session.execute(
|
||||
select(UserModel).filter_by(telegram_id=str(telegram_id))
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
async def get_user_by_telegram_id(self, telegram_id: int) -> Optional[User]:
|
||||
"""Получить пользователя по Telegram ID"""
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
f"{self.backend_url}/users/telegram/{telegram_id}"
|
||||
) as response:
|
||||
if response.status == 200:
|
||||
data = await response.json()
|
||||
return User(data)
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Error getting user: {e}")
|
||||
return None
|
||||
|
||||
async def get_or_create_user(
|
||||
self,
|
||||
@ -22,46 +62,45 @@ class UserService:
|
||||
username: str = "",
|
||||
first_name: str = "",
|
||||
last_name: str = ""
|
||||
) -> UserModel:
|
||||
) -> User:
|
||||
"""Получить или создать пользователя"""
|
||||
user = await self.get_user_by_telegram_id(telegram_id)
|
||||
if not user:
|
||||
user = UserModel(
|
||||
telegram_id=str(telegram_id),
|
||||
username=username,
|
||||
first_name=first_name,
|
||||
last_name=last_name
|
||||
)
|
||||
self.session.add(user)
|
||||
await self.session.commit()
|
||||
else:
|
||||
user.username = username
|
||||
user.first_name = first_name
|
||||
user.last_name = last_name
|
||||
await self.session.commit()
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
f"{self.backend_url}/users",
|
||||
json={"telegram_id": str(telegram_id), "role": "user"}
|
||||
) as response:
|
||||
if response.status in [200, 201]:
|
||||
data = await response.json()
|
||||
return User(data)
|
||||
except Exception as e:
|
||||
print(f"Error creating user: {e}")
|
||||
raise
|
||||
return user
|
||||
|
||||
async def update_user_questions(self, telegram_id: int) -> bool:
|
||||
user = await self.get_user_by_telegram_id(telegram_id)
|
||||
if user:
|
||||
user.questions_used += 1
|
||||
await self.session.commit()
|
||||
return True
|
||||
return False
|
||||
|
||||
async def activate_premium(self, telegram_id: int) -> bool:
|
||||
"""Увеличить счетчик использованных вопросов"""
|
||||
try:
|
||||
user = await self.get_user_by_telegram_id(telegram_id)
|
||||
if user:
|
||||
user.is_premium = True
|
||||
if user.premium_until and user.premium_until > datetime.now():
|
||||
user.premium_until = user.premium_until + timedelta(days=30)
|
||||
else:
|
||||
user.premium_until = datetime.now() + timedelta(days=30)
|
||||
await self.session.commit()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
f"{self.backend_url}/users/telegram/{telegram_id}/increment-questions"
|
||||
) as response:
|
||||
return response.status == 200
|
||||
except Exception as e:
|
||||
print(f"Error updating questions: {e}")
|
||||
return False
|
||||
|
||||
async def activate_premium(self, telegram_id: int, days: int = 30) -> bool:
|
||||
"""Активировать premium статус"""
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
f"{self.backend_url}/users/telegram/{telegram_id}/activate-premium",
|
||||
params={"days": days}
|
||||
) as response:
|
||||
return response.status == 200
|
||||
except Exception as e:
|
||||
print(f"Error activating premium: {e}")
|
||||
await self.session.rollback()
|
||||
return False
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
|
||||
from tg_bot.config.settings import settings
|
||||
|
||||
database_url = settings.DATABASE_URL
|
||||
if database_url.startswith("sqlite:///"):
|
||||
database_url = database_url.replace("sqlite:///", "sqlite+aiosqlite:///")
|
||||
|
||||
engine = create_async_engine(
|
||||
database_url,
|
||||
echo=settings.DEBUG
|
||||
)
|
||||
|
||||
AsyncSessionLocal = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||
|
||||
async def create_tables():
|
||||
from .models import Base
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
print(f"Таблицы созданы: {settings.DATABASE_URL}")
|
||||
@ -1,39 +0,0 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, String, DateTime, Boolean, Integer, Text
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
class UserModel(Base):
|
||||
__tablename__ = "users"
|
||||
user_id = Column("user_id", String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
telegram_id = Column("telegram_id", String(100), nullable=False, unique=True)
|
||||
created_at = Column("created_at", DateTime, default=datetime.utcnow, nullable=False)
|
||||
role = Column("role", String(20), default="user", nullable=False)
|
||||
|
||||
is_premium = Column(Boolean, default=False, nullable=False)
|
||||
premium_until = Column(DateTime, nullable=True)
|
||||
questions_used = Column(Integer, default=0, nullable=False)
|
||||
|
||||
username = Column(String(100), nullable=True)
|
||||
first_name = Column(String(100), nullable=True)
|
||||
last_name = Column(String(100), nullable=True)
|
||||
|
||||
|
||||
class PaymentModel(Base):
|
||||
__tablename__ = "payments"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
payment_id = Column(String(36), default=lambda: str(uuid.uuid4()), nullable=False, unique=True)
|
||||
user_id = Column(Integer, nullable=False)
|
||||
amount = Column(String(20), nullable=False)
|
||||
currency = Column(String(3), default="RUB", nullable=False)
|
||||
status = Column(String(20), default="pending", nullable=False)
|
||||
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
yookassa_payment_id = Column(String(100), unique=True, nullable=True)
|
||||
description = Column(Text, nullable=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Payment(user_id={self.user_id}, amount={self.amount}, status={self.status})>"
|
||||
@ -34,8 +34,18 @@ async def create_bot() -> tuple[Bot, Dispatcher]:
|
||||
async def start_bot():
|
||||
bot = None
|
||||
try:
|
||||
if not settings.TELEGRAM_BOT_TOKEN or not settings.TELEGRAM_BOT_TOKEN.strip():
|
||||
raise ValueError("TELEGRAM_BOT_TOKEN не установлен в переменных окружения или файле .env")
|
||||
|
||||
bot, dp = await create_bot()
|
||||
|
||||
try:
|
||||
bot_info = await bot.get_me()
|
||||
username = bot_info.username if bot_info.username else f"ID: {bot_info.id}"
|
||||
logger.info(f"Бот успешно подключен: @{username}")
|
||||
except Exception as e:
|
||||
raise ValueError(f"Неверный токен Telegram бота: {e}")
|
||||
|
||||
try:
|
||||
webhook_info = await bot.get_webhook_info()
|
||||
if webhook_info.url:
|
||||
|
||||
@ -4,14 +4,11 @@ from aiogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from decimal import Decimal
|
||||
from tg_bot.config.settings import settings
|
||||
from tg_bot.payment.yookassa.client import yookassa_client
|
||||
from tg_bot.infrastructure.database.database import AsyncSessionLocal
|
||||
from tg_bot.infrastructure.database.models import PaymentModel
|
||||
from tg_bot.domain.services.user_service import UserService
|
||||
from sqlalchemy import select
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
|
||||
router = Router()
|
||||
user_service = UserService()
|
||||
|
||||
|
||||
@router.message(Command("buy"))
|
||||
@ -19,23 +16,21 @@ async def cmd_buy(message: Message):
|
||||
user_id = message.from_user.id
|
||||
username = message.from_user.username or f"user_{user_id}"
|
||||
|
||||
async with AsyncSessionLocal() as session:
|
||||
try:
|
||||
user_service = UserService(session)
|
||||
user = await user_service.get_user_by_telegram_id(user_id)
|
||||
try:
|
||||
user = await user_service.get_user_by_telegram_id(user_id)
|
||||
|
||||
if user and user.is_premium and user.premium_until and user.premium_until > datetime.now():
|
||||
days_left = (user.premium_until - datetime.now()).days
|
||||
await message.answer(
|
||||
f"<b>У вас уже есть активная подписка!</b>\n\n"
|
||||
f"• Статус: Premium активен\n"
|
||||
f"• Действует до: {user.premium_until.strftime('%d.%m.%Y')}\n"
|
||||
f"• Осталось дней: {days_left}\n\n"
|
||||
f"Новая подписка будет добавлена к текущей.",
|
||||
parse_mode="HTML"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
if user and user.is_premium and user.premium_until and user.premium_until > datetime.now():
|
||||
days_left = (user.premium_until - datetime.now()).days
|
||||
await message.answer(
|
||||
f"<b>У вас уже есть активная подписка!</b>\n\n"
|
||||
f"• Статус: Premium активен\n"
|
||||
f"• Действует до: {user.premium_until.strftime('%d.%m.%Y')}\n"
|
||||
f"• Осталось дней: {days_left}\n\n"
|
||||
f"Новая подписка будет добавлена к текущей.",
|
||||
parse_mode="HTML"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
await message.answer(
|
||||
"*Создаю ссылку для оплаты...*\n\n"
|
||||
@ -49,24 +44,8 @@ async def cmd_buy(message: Message):
|
||||
description=f"Подписка VibeLawyerBot для @{username}",
|
||||
user_id=user_id
|
||||
)
|
||||
|
||||
async with AsyncSessionLocal() as session:
|
||||
try:
|
||||
payment = PaymentModel(
|
||||
payment_id=str(uuid.uuid4()),
|
||||
user_id=user_id,
|
||||
amount=str(settings.PAYMENT_AMOUNT),
|
||||
currency="RUB",
|
||||
status="pending",
|
||||
yookassa_payment_id=payment_data["id"],
|
||||
description="Оплата подписки VibeLawyerBot"
|
||||
)
|
||||
session.add(payment)
|
||||
await session.commit()
|
||||
print(f"Платёж сохранён в БД: {payment.payment_id}")
|
||||
except Exception as e:
|
||||
print(f"Ошибка сохранения платежа в БД: {e}")
|
||||
await session.rollback()
|
||||
|
||||
print(f"Платёж создан в ЮKассе: {payment_data['id']}")
|
||||
|
||||
keyboard = InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
@ -139,27 +118,15 @@ async def check_payment_status(callback_query: types.CallbackQuery):
|
||||
payment = YooPayment.find_one(yookassa_id)
|
||||
|
||||
if payment.status == "succeeded":
|
||||
async with AsyncSessionLocal() as session:
|
||||
try:
|
||||
result = await session.execute(
|
||||
select(PaymentModel).filter_by(yookassa_payment_id=yookassa_id)
|
||||
)
|
||||
db_payment = result.scalar_one_or_none()
|
||||
|
||||
if db_payment:
|
||||
db_payment.status = "succeeded"
|
||||
user_service = UserService(session)
|
||||
success = await user_service.activate_premium(user_id)
|
||||
if success:
|
||||
user = await user_service.get_user_by_telegram_id(user_id)
|
||||
await session.commit()
|
||||
if not user:
|
||||
user = await user_service.get_user_by_telegram_id(user_id)
|
||||
|
||||
try:
|
||||
success = await user_service.activate_premium(user_id)
|
||||
if success:
|
||||
user = await user_service.get_user_by_telegram_id(user_id)
|
||||
if user:
|
||||
await callback_query.message.answer(
|
||||
"<b>Оплата подтверждена!</b>\n\n"
|
||||
f"Ваш premium-доступ активирован до: "
|
||||
f"<b>{user.premium_until.strftime('%d.%m.%Y')}</b>\n\n"
|
||||
f"<b>{user.premium_until.strftime('%d.%m.%Y') if user.premium_until else 'Не указано'}</b>\n\n"
|
||||
"Теперь вы можете:\n"
|
||||
"• Задавать неограниченное количество вопросов\n"
|
||||
"• Получать приоритетные ответы\n"
|
||||
@ -169,12 +136,23 @@ async def check_payment_status(callback_query: types.CallbackQuery):
|
||||
)
|
||||
else:
|
||||
await callback_query.message.answer(
|
||||
"<b>Платёж найден в ЮKассе, но не в нашей БД</b>\n\n"
|
||||
"<b>Оплата подтверждена, но не удалось активировать premium</b>\n\n"
|
||||
"Пожалуйста, обратитесь к администратору.",
|
||||
parse_mode="HTML"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Ошибка обработки платежа: {e}")
|
||||
else:
|
||||
await callback_query.message.answer(
|
||||
"<b>Оплата подтверждена, но не удалось активировать premium</b>\n\n"
|
||||
"Пожалуйста, обратитесь к администратору.",
|
||||
parse_mode="HTML"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Ошибка обработки платежа: {e}")
|
||||
await callback_query.message.answer(
|
||||
"<b>Ошибка активации premium</b>\n\n"
|
||||
"Пожалуйста, обратитесь к администратору.",
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
elif payment.status == "pending":
|
||||
await callback_query.message.answer(
|
||||
@ -206,42 +184,13 @@ async def check_payment_status(callback_query: types.CallbackQuery):
|
||||
|
||||
@router.message(Command("mypayments"))
|
||||
async def cmd_my_payments(message: Message):
|
||||
user_id = message.from_user.id
|
||||
|
||||
async with AsyncSessionLocal() as session:
|
||||
try:
|
||||
result = await session.execute(
|
||||
select(PaymentModel).filter_by(user_id=user_id).order_by(PaymentModel.created_at.desc()).limit(10)
|
||||
)
|
||||
payments = result.scalars().all()
|
||||
|
||||
if not payments:
|
||||
await message.answer(
|
||||
"<b>У вас пока нет платежей</b>\n\n"
|
||||
"Используйте команду /buy чтобы оформить подписку.",
|
||||
parse_mode="HTML"
|
||||
)
|
||||
return
|
||||
|
||||
response = ["<b>Ваши последние платежи:</b>\n"]
|
||||
|
||||
for i, payment in enumerate(payments, 1):
|
||||
status_text = "Успешно" if payment.status == "succeeded" else "Ожидание" if payment.status == "pending" else "Ошибка"
|
||||
response.append(
|
||||
f"\n<b>{i}. {payment.amount} руб. ({status_text})</b>\n"
|
||||
f"Статус: {payment.status}\n"
|
||||
f"Дата: {payment.created_at.strftime('%d.%m.%Y %H:%M')}\n"
|
||||
f"ID: <code>{payment.payment_id[:8]}...</code>"
|
||||
)
|
||||
|
||||
response.append("\n\n<i>Полный доступ открывается после успешной оплаты</i>")
|
||||
|
||||
await message.answer(
|
||||
"\n".join(response),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Ошибка получения платежей: {e}")
|
||||
await message.answer(
|
||||
"<b>История платежей</b>\n\n"
|
||||
"История платежей хранится в системе оплаты ЮKassa.\n"
|
||||
"Для проверки статуса подписки используйте команду /stats.\n\n"
|
||||
"Для оформления новой подписки используйте команду /buy",
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
|
||||
@router.message(Command("testcards"))
|
||||
|
||||
@ -2,17 +2,16 @@ from aiogram import Router
|
||||
from aiogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
|
||||
from aiogram.filters import Command
|
||||
import aiohttp
|
||||
from tg_bot.config.settings import settings
|
||||
|
||||
router = Router()
|
||||
|
||||
BACKEND_URL = "http://localhost:8001/api/v1"
|
||||
|
||||
|
||||
async def get_user_collections(telegram_id: str):
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
f"{BACKEND_URL}/collections/",
|
||||
f"{settings.BACKEND_URL}/collections/",
|
||||
headers={"X-Telegram-ID": telegram_id}
|
||||
) as response:
|
||||
if response.status == 200:
|
||||
@ -27,7 +26,7 @@ async def get_collection_documents(collection_id: str, telegram_id: str):
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
f"{BACKEND_URL}/documents/collection/{collection_id}",
|
||||
f"{settings.BACKEND_URL}/documents/collection/{collection_id}",
|
||||
headers={"X-Telegram-ID": telegram_id}
|
||||
) as response:
|
||||
if response.status == 200:
|
||||
@ -42,7 +41,7 @@ async def search_in_collection(collection_id: str, query: str, telegram_id: str)
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
f"{BACKEND_URL}/documents/collection/{collection_id}",
|
||||
f"{settings.BACKEND_URL}/documents/collection/{collection_id}",
|
||||
params={"search": query},
|
||||
headers={"X-Telegram-ID": telegram_id}
|
||||
) as response:
|
||||
|
||||
@ -3,14 +3,12 @@ from aiogram.types import Message
|
||||
from datetime import datetime
|
||||
import aiohttp
|
||||
from tg_bot.config.settings import settings
|
||||
from tg_bot.infrastructure.database.database import AsyncSessionLocal
|
||||
from tg_bot.infrastructure.database.models import UserModel
|
||||
from tg_bot.domain.services.user_service import UserService
|
||||
from tg_bot.domain.services.user_service import UserService, User
|
||||
from tg_bot.application.services.rag_service import RAGService
|
||||
|
||||
router = Router()
|
||||
BACKEND_URL = "http://localhost:8001/api/v1"
|
||||
rag_service = RAGService()
|
||||
user_service = UserService()
|
||||
|
||||
@router.message()
|
||||
async def handle_question(message: Message):
|
||||
@ -19,58 +17,37 @@ async def handle_question(message: Message):
|
||||
if question_text.startswith('/'):
|
||||
return
|
||||
|
||||
async with AsyncSessionLocal() as session:
|
||||
try:
|
||||
user_service = UserService(session)
|
||||
user = await user_service.get_user_by_telegram_id(user_id)
|
||||
try:
|
||||
user = await user_service.get_user_by_telegram_id(user_id)
|
||||
|
||||
if not user:
|
||||
user = await user_service.get_or_create_user(
|
||||
user_id,
|
||||
message.from_user.username or "",
|
||||
message.from_user.first_name or "",
|
||||
message.from_user.last_name or ""
|
||||
)
|
||||
await ensure_user_in_backend(str(user_id), message.from_user)
|
||||
|
||||
if user.is_premium:
|
||||
await process_premium_question(message, user, question_text, user_service)
|
||||
|
||||
elif user.questions_used < settings.FREE_QUESTIONS_LIMIT:
|
||||
await process_free_question(message, user, question_text, user_service)
|
||||
|
||||
else:
|
||||
await handle_limit_exceeded(message, user)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error processing question: {e}")
|
||||
await message.answer(
|
||||
"Произошла ошибка. Попробуйте позже.",
|
||||
parse_mode="HTML"
|
||||
if not user:
|
||||
user = await user_service.get_or_create_user(
|
||||
user_id,
|
||||
message.from_user.username or "",
|
||||
message.from_user.first_name or "",
|
||||
message.from_user.last_name or ""
|
||||
)
|
||||
|
||||
if user.is_premium:
|
||||
await process_premium_question(message, user, question_text)
|
||||
|
||||
elif user.questions_used < settings.FREE_QUESTIONS_LIMIT:
|
||||
await process_free_question(message, user, question_text)
|
||||
|
||||
else:
|
||||
await handle_limit_exceeded(message, user)
|
||||
|
||||
async def ensure_user_in_backend(telegram_id: str, telegram_user):
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
f"{BACKEND_URL}/users/telegram/{telegram_id}"
|
||||
) as response:
|
||||
if response.status == 200:
|
||||
return
|
||||
|
||||
async with session.post(
|
||||
f"{BACKEND_URL}/users",
|
||||
json={"telegram_id": telegram_id, "role": "user"}
|
||||
) as create_response:
|
||||
if create_response.status in [200, 201]:
|
||||
print(f"Пользователь {telegram_id} создан в backend")
|
||||
except Exception as e:
|
||||
print(f"Error creating user in backend: {e}")
|
||||
print(f"Error processing question: {e}")
|
||||
await message.answer(
|
||||
"Произошла ошибка. Попробуйте позже.",
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
|
||||
async def process_premium_question(message: Message, user: UserModel, question_text: str, user_service: UserService):
|
||||
await user_service.update_user_questions(user.telegram_id)
|
||||
async def process_premium_question(message: Message, user: User, question_text: str):
|
||||
await user_service.update_user_questions(int(user.telegram_id))
|
||||
user = await user_service.get_user_by_telegram_id(int(user.telegram_id))
|
||||
|
||||
await message.bot.send_chat_action(message.chat.id, "typing")
|
||||
|
||||
@ -129,9 +106,9 @@ async def process_premium_question(message: Message, user: UserModel, question_t
|
||||
await message.answer(response, parse_mode="HTML")
|
||||
|
||||
|
||||
async def process_free_question(message: Message, user: UserModel, question_text: str, user_service: UserService):
|
||||
await user_service.update_user_questions(user.telegram_id)
|
||||
user = await user_service.get_user_by_telegram_id(user.telegram_id)
|
||||
async def process_free_question(message: Message, user: User, question_text: str):
|
||||
await user_service.update_user_questions(int(user.telegram_id))
|
||||
user = await user_service.get_user_by_telegram_id(int(user.telegram_id))
|
||||
remaining = settings.FREE_QUESTIONS_LIMIT - user.questions_used
|
||||
|
||||
await message.bot.send_chat_action(message.chat.id, "typing")
|
||||
@ -201,9 +178,11 @@ async def process_free_question(message: Message, user: UserModel, question_text
|
||||
|
||||
async def save_conversation_to_backend(telegram_id: str, question: str, answer: str, sources: list):
|
||||
try:
|
||||
from tg_bot.config.settings import settings
|
||||
backend_url = settings.BACKEND_URL
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
f"{BACKEND_URL}/users/telegram/{telegram_id}"
|
||||
f"{backend_url}/users/telegram/{telegram_id}"
|
||||
) as user_response:
|
||||
if user_response.status != 200:
|
||||
return
|
||||
@ -211,7 +190,7 @@ async def save_conversation_to_backend(telegram_id: str, question: str, answer:
|
||||
user_uuid = user_data.get("user_id")
|
||||
|
||||
async with session.get(
|
||||
f"{BACKEND_URL}/collections/",
|
||||
f"{backend_url}/collections/",
|
||||
headers={"X-Telegram-ID": telegram_id}
|
||||
) as collections_response:
|
||||
collections = []
|
||||
@ -223,7 +202,7 @@ async def save_conversation_to_backend(telegram_id: str, question: str, answer:
|
||||
collection_id = collections[0].get("collection_id")
|
||||
else:
|
||||
async with session.post(
|
||||
f"{BACKEND_URL}/collections",
|
||||
f"{backend_url}/collections",
|
||||
json={
|
||||
"name": "Основная коллекция",
|
||||
"description": "Коллекция по умолчанию",
|
||||
@ -239,7 +218,7 @@ async def save_conversation_to_backend(telegram_id: str, question: str, answer:
|
||||
return
|
||||
|
||||
async with session.post(
|
||||
f"{BACKEND_URL}/conversations",
|
||||
f"{backend_url}/conversations",
|
||||
json={"collection_id": str(collection_id)},
|
||||
headers={"X-Telegram-ID": telegram_id}
|
||||
) as conversation_response:
|
||||
@ -252,7 +231,7 @@ async def save_conversation_to_backend(telegram_id: str, question: str, answer:
|
||||
return
|
||||
|
||||
await session.post(
|
||||
f"{BACKEND_URL}/messages",
|
||||
f"{backend_url}/messages",
|
||||
json={
|
||||
"conversation_id": str(conversation_id),
|
||||
"content": question,
|
||||
@ -262,7 +241,7 @@ async def save_conversation_to_backend(telegram_id: str, question: str, answer:
|
||||
)
|
||||
|
||||
await session.post(
|
||||
f"{BACKEND_URL}/messages",
|
||||
f"{backend_url}/messages",
|
||||
json={
|
||||
"conversation_id": str(conversation_id),
|
||||
"content": answer,
|
||||
@ -276,7 +255,7 @@ async def save_conversation_to_backend(telegram_id: str, question: str, answer:
|
||||
print(f"Error saving conversation: {e}")
|
||||
|
||||
|
||||
async def handle_limit_exceeded(message: Message, user: UserModel):
|
||||
async def handle_limit_exceeded(message: Message, user: User):
|
||||
response = (
|
||||
f"<b>Лимит бесплатных вопросов исчерпан!</b>\n\n"
|
||||
|
||||
|
||||
@ -4,10 +4,10 @@ from aiogram.types import Message
|
||||
from datetime import datetime
|
||||
|
||||
from tg_bot.config.settings import settings
|
||||
from tg_bot.infrastructure.database.database import AsyncSessionLocal
|
||||
from tg_bot.domain.services.user_service import UserService
|
||||
|
||||
router = Router()
|
||||
user_service = UserService()
|
||||
|
||||
@router.message(Command("start"))
|
||||
async def cmd_start(message: Message):
|
||||
@ -16,22 +16,19 @@ async def cmd_start(message: Message):
|
||||
username = message.from_user.username or ""
|
||||
first_name = message.from_user.first_name or ""
|
||||
last_name = message.from_user.last_name or ""
|
||||
async with AsyncSessionLocal() as session:
|
||||
try:
|
||||
user_service = UserService(session)
|
||||
existing_user = await user_service.get_user_by_telegram_id(user_id)
|
||||
user = await user_service.get_or_create_user(
|
||||
user_id,
|
||||
username,
|
||||
first_name,
|
||||
last_name
|
||||
)
|
||||
if not existing_user:
|
||||
print(f"Новый пользователь: {user_id}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Ошибка сохранения пользователя: {e}")
|
||||
await session.rollback()
|
||||
try:
|
||||
existing_user = await user_service.get_user_by_telegram_id(user_id)
|
||||
user = await user_service.get_or_create_user(
|
||||
user_id,
|
||||
username,
|
||||
first_name,
|
||||
last_name
|
||||
)
|
||||
if not existing_user:
|
||||
print(f"Новый пользователь: {user_id}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Ошибка сохранения пользователя: {e}")
|
||||
welcome_text = (
|
||||
f"<b>Привет, {first_name}!</b>\n\n"
|
||||
f"Я <b>VibeLawyerBot</b> - ваш помощник в юридических вопросах.\n\n"
|
||||
|
||||
@ -4,58 +4,56 @@ from aiogram.filters import Command
|
||||
from aiogram.types import Message
|
||||
|
||||
from tg_bot.config.settings import settings
|
||||
from tg_bot.infrastructure.database.database import AsyncSessionLocal
|
||||
from tg_bot.domain.services.user_service import UserService
|
||||
|
||||
router = Router()
|
||||
user_service = UserService()
|
||||
|
||||
|
||||
@router.message(Command("stats"))
|
||||
async def cmd_stats(message: Message):
|
||||
user_id = message.from_user.id
|
||||
|
||||
async with AsyncSessionLocal() as session:
|
||||
try:
|
||||
user_service = UserService(session)
|
||||
user = await user_service.get_user_by_telegram_id(user_id)
|
||||
try:
|
||||
user = await user_service.get_user_by_telegram_id(user_id)
|
||||
|
||||
if user:
|
||||
stats_text = (
|
||||
f"<b>Ваша статистика</b>\n\n"
|
||||
f"<b>Основное:</b>\n"
|
||||
f"• ID: <code>{user_id}</code>\n"
|
||||
f"• Premium: {'Да' if user.is_premium else 'Нет'}\n"
|
||||
f"• Вопросов использовано: {user.questions_used}/{settings.FREE_QUESTIONS_LIMIT}\n\n"
|
||||
)
|
||||
|
||||
if user.is_premium:
|
||||
stats_text += (
|
||||
f"<b>Premium статус:</b>\n"
|
||||
f"• Активен до: {user.premium_until.strftime('%d.%m.%Y') if user.premium_until else 'Не указано'}\n"
|
||||
f"• Лимит вопросов: безлимитно\n\n"
|
||||
)
|
||||
else:
|
||||
remaining = max(0, settings.FREE_QUESTIONS_LIMIT - user.questions_used)
|
||||
stats_text += (
|
||||
f"<b>Бесплатный доступ:</b>\n"
|
||||
f"• Осталось вопросов: {remaining}\n"
|
||||
f"• Для безлимита: /buy\n\n"
|
||||
)
|
||||
else:
|
||||
stats_text = (
|
||||
f"<b>Добро пожаловать!</b>\n\n"
|
||||
f"Вы новый пользователь.\n"
|
||||
f"• Ваш ID: <code>{user_id}</code>\n"
|
||||
f"• Бесплатных вопросов: {settings.FREE_QUESTIONS_LIMIT}\n"
|
||||
f"• Для начала работы просто задайте вопрос!\n\n"
|
||||
f"<i>Используйте /buy для получения полного доступа</i>"
|
||||
)
|
||||
|
||||
await message.answer(stats_text, parse_mode="HTML")
|
||||
|
||||
except Exception as e:
|
||||
await message.answer(
|
||||
f"<b>Ошибка получения статистики</b>\n\n"
|
||||
f"Попробуйте позже.",
|
||||
parse_mode="HTML"
|
||||
if user:
|
||||
stats_text = (
|
||||
f"<b>Ваша статистика</b>\n\n"
|
||||
f"<b>Основное:</b>\n"
|
||||
f"• ID: <code>{user_id}</code>\n"
|
||||
f"• Premium: {'Да' if user.is_premium else 'Нет'}\n"
|
||||
f"• Вопросов использовано: {user.questions_used}/{settings.FREE_QUESTIONS_LIMIT}\n\n"
|
||||
)
|
||||
|
||||
if user.is_premium:
|
||||
stats_text += (
|
||||
f"<b>Premium статус:</b>\n"
|
||||
f"• Активен до: {user.premium_until.strftime('%d.%m.%Y') if user.premium_until else 'Не указано'}\n"
|
||||
f"• Лимит вопросов: безлимитно\n\n"
|
||||
)
|
||||
else:
|
||||
remaining = max(0, settings.FREE_QUESTIONS_LIMIT - user.questions_used)
|
||||
stats_text += (
|
||||
f"<b>Бесплатный доступ:</b>\n"
|
||||
f"• Осталось вопросов: {remaining}\n"
|
||||
f"• Для безлимита: /buy\n\n"
|
||||
)
|
||||
else:
|
||||
stats_text = (
|
||||
f"<b>Добро пожаловать!</b>\n\n"
|
||||
f"Вы новый пользователь.\n"
|
||||
f"• Ваш ID: <code>{user_id}</code>\n"
|
||||
f"• Бесплатных вопросов: {settings.FREE_QUESTIONS_LIMIT}\n"
|
||||
f"• Для начала работы просто задайте вопрос!\n\n"
|
||||
f"<i>Используйте /buy для получения полного доступа</i>"
|
||||
)
|
||||
|
||||
await message.answer(stats_text, parse_mode="HTML")
|
||||
|
||||
except Exception as e:
|
||||
await message.answer(
|
||||
f"<b>Ошибка получения статистики</b>\n\n"
|
||||
f"Попробуйте позже.",
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
@ -19,9 +19,6 @@ async def handle_yookassa_webhook(request: Request):
|
||||
try:
|
||||
from tg_bot.config.settings import settings
|
||||
from tg_bot.domain.services.user_service import UserService
|
||||
from tg_bot.infrastructure.database.database import AsyncSessionLocal
|
||||
from tg_bot.infrastructure.database.models import UserModel
|
||||
from sqlalchemy import select
|
||||
from aiogram import Bot
|
||||
|
||||
if event_type == "payment.succeeded":
|
||||
@ -29,38 +26,34 @@ async def handle_yookassa_webhook(request: Request):
|
||||
user_id = payment.get("metadata", {}).get("user_id")
|
||||
|
||||
if user_id:
|
||||
async with AsyncSessionLocal() as session:
|
||||
user_service = UserService(session)
|
||||
success = await user_service.activate_premium(int(user_id))
|
||||
if success:
|
||||
print(f"Premium activated for user {user_id}")
|
||||
|
||||
result = await session.execute(
|
||||
select(UserModel).filter_by(telegram_id=str(user_id))
|
||||
)
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
if user and settings.TELEGRAM_BOT_TOKEN:
|
||||
try:
|
||||
bot = Bot(token=settings.TELEGRAM_BOT_TOKEN)
|
||||
premium_until = user.premium_until or datetime.now() + timedelta(days=30)
|
||||
|
||||
notification = (
|
||||
f"<b>Оплата подтверждена!</b>\n\n"
|
||||
f"Premium активирован до {premium_until.strftime('%d.%m.%Y')}"
|
||||
)
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=int(user_id),
|
||||
text=notification,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
print(f"Notification sent to user {user_id}")
|
||||
await bot.session.close()
|
||||
except Exception as e:
|
||||
print(f"Error sending notification: {e}")
|
||||
else:
|
||||
print(f"User {user_id} not found")
|
||||
user_service = UserService()
|
||||
success = await user_service.activate_premium(int(user_id))
|
||||
if success:
|
||||
print(f"Premium activated for user {user_id}")
|
||||
|
||||
user = await user_service.get_user_by_telegram_id(int(user_id))
|
||||
|
||||
if user and settings.TELEGRAM_BOT_TOKEN:
|
||||
try:
|
||||
bot = Bot(token=settings.TELEGRAM_BOT_TOKEN)
|
||||
premium_until = user.premium_until or datetime.now() + timedelta(days=30)
|
||||
|
||||
notification = (
|
||||
f"<b>Оплата подтверждена!</b>\n\n"
|
||||
f"Premium активирован до {premium_until.strftime('%d.%m.%Y')}"
|
||||
)
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=int(user_id),
|
||||
text=notification,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
print(f"Notification sent to user {user_id}")
|
||||
await bot.session.close()
|
||||
except Exception as e:
|
||||
print(f"Error sending notification: {e}")
|
||||
else:
|
||||
print(f"User {user_id} not found or failed to activate premium")
|
||||
|
||||
except ImportError as e:
|
||||
print(f"Import error: {e}")
|
||||
|
||||
@ -2,8 +2,6 @@ pydantic>=2.5.0
|
||||
pydantic-settings>=2.1.0
|
||||
python-dotenv>=1.0.0
|
||||
aiogram>=3.10.0
|
||||
sqlalchemy>=2.0.0
|
||||
aiosqlite>=0.19.0
|
||||
httpx>=0.25.2
|
||||
yookassa>=2.4.0
|
||||
aiohttp>=3.9.1
|
||||
|
||||
20
tg_bot/run.py
Normal file
20
tg_bot/run.py
Normal file
@ -0,0 +1,20 @@
|
||||
|
||||
"""
|
||||
Скрипт для запуска Telegram бота без Docker
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
tg_bot_dir = Path(__file__).parent
|
||||
sys.path.insert(0, str(tg_bot_dir))
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
from main import main
|
||||
import asyncio
|
||||
|
||||
asyncio.run(main())
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user