diff --git a/app/database/crud/user_message.py b/app/database/crud/user_message.py index 3f26f026..488a27d0 100644 --- a/app/database/crud/user_message.py +++ b/app/database/crud/user_message.py @@ -61,27 +61,19 @@ async def get_random_active_message(db: AsyncSession) -> Optional[str]: async def get_all_user_messages( db: AsyncSession, offset: int = 0, - limit: int = 50, - include_inactive: bool = True, + limit: int = 50 ) -> List[UserMessage]: - query = select(UserMessage).order_by(UserMessage.created_at.desc()) - if not include_inactive: - query = query.where(UserMessage.is_active == True) - result = await db.execute( - query + select(UserMessage) + .order_by(UserMessage.created_at.desc()) .offset(offset) .limit(limit) ) return result.scalars().all() -async def get_user_messages_count(db: AsyncSession, include_inactive: bool = True) -> int: - query = select(func.count(UserMessage.id)) - if not include_inactive: - query = query.where(UserMessage.is_active == True) - - result = await db.execute(query) +async def get_user_messages_count(db: AsyncSession) -> int: + result = await db.execute(select(func.count(UserMessage.id))) return result.scalar() diff --git a/app/database/crud/welcome_text.py b/app/database/crud/welcome_text.py index 145f6ac8..2e1646eb 100644 --- a/app/database/crud/welcome_text.py +++ b/app/database/crud/welcome_text.py @@ -1,7 +1,7 @@ import logging from datetime import datetime from typing import Optional -from sqlalchemy import select, update, func +from sqlalchemy import select, update from sqlalchemy.ext.asyncio import AsyncSession from app.database.models import WelcomeText @@ -45,37 +45,6 @@ async def get_current_welcome_text_settings(db: AsyncSession) -> dict: 'id': None } - -async def get_welcome_text_by_id(db: AsyncSession, welcome_text_id: int) -> Optional[WelcomeText]: - result = await db.execute( - select(WelcomeText).where(WelcomeText.id == welcome_text_id) - ) - return result.scalar_one_or_none() - - -async def list_welcome_texts( - db: AsyncSession, - *, - include_inactive: bool = True, - limit: int = 50, - offset: int = 0, -): - query = select(WelcomeText).order_by(WelcomeText.updated_at.desc()) - if not include_inactive: - query = query.where(WelcomeText.is_active == True) - - result = await db.execute(query.limit(limit).offset(offset)) - return result.scalars().all() - - -async def count_welcome_texts(db: AsyncSession, *, include_inactive: bool = True) -> int: - query = select(func.count(WelcomeText.id)) - if not include_inactive: - query = query.where(WelcomeText.is_active == True) - - result = await db.execute(query) - return result.scalar() - async def toggle_welcome_text_status(db: AsyncSession, admin_id: int) -> bool: try: result = await db.execute( @@ -144,81 +113,6 @@ async def set_welcome_text(db: AsyncSession, text_content: str, admin_id: int) - await db.rollback() return False - -async def create_welcome_text( - db: AsyncSession, - *, - text_content: str, - created_by: Optional[int] = None, - is_enabled: bool = True, - is_active: bool = True, -) -> WelcomeText: - if is_active: - await db.execute(update(WelcomeText).values(is_active=False)) - - welcome_text = WelcomeText( - text_content=text_content, - is_active=is_active, - is_enabled=is_enabled, - created_by=created_by, - ) - - db.add(welcome_text) - await db.commit() - await db.refresh(welcome_text) - - logger.info( - "✅ Создан приветственный текст ID %s (активный=%s, включен=%s)", - welcome_text.id, - welcome_text.is_active, - welcome_text.is_enabled, - ) - return welcome_text - - -async def update_welcome_text( - db: AsyncSession, - welcome_text: WelcomeText, - *, - text_content: Optional[str] = None, - is_enabled: Optional[bool] = None, - is_active: Optional[bool] = None, -) -> WelcomeText: - if is_active: - await db.execute( - update(WelcomeText) - .where(WelcomeText.id != welcome_text.id) - .values(is_active=False) - ) - - if text_content is not None: - welcome_text.text_content = text_content - - if is_enabled is not None: - welcome_text.is_enabled = is_enabled - - if is_active is not None: - welcome_text.is_active = is_active - - welcome_text.updated_at = datetime.utcnow() - - await db.commit() - await db.refresh(welcome_text) - - logger.info( - "📝 Обновлен приветственный текст ID %s (активный=%s, включен=%s)", - welcome_text.id, - welcome_text.is_active, - welcome_text.is_enabled, - ) - return welcome_text - - -async def delete_welcome_text(db: AsyncSession, welcome_text: WelcomeText) -> None: - await db.delete(welcome_text) - await db.commit() - logger.info("🗑️ Удален приветственный текст ID %s", welcome_text.id) - async def get_current_welcome_text_or_default() -> str: return ( f"Привет, {{user_name}}! 🎁 3 дней VPN бесплатно! " diff --git a/app/webapi/app.py b/app/webapi/app.py index ba58d493..6e87aa69 100644 --- a/app/webapi/app.py +++ b/app/webapi/app.py @@ -20,8 +20,6 @@ from .routes import ( promocodes, promo_groups, promo_offers, - user_messages, - welcome_texts, pages, remnawave, servers, @@ -51,11 +49,7 @@ OPENAPI_TAGS = [ }, { "name": "main-menu", - "description": "Управление кнопками и сообщениями главного меню Telegram-бота.", - }, - { - "name": "welcome-texts", - "description": "Создание, редактирование и управление приветственными текстами.", + "description": "Управление кнопками главного меню Telegram-бота.", }, { "name": "users", @@ -175,16 +169,6 @@ def create_web_api_app() -> FastAPI: prefix="/main-menu/buttons", tags=["main-menu"], ) - app.include_router( - user_messages.router, - prefix="/main-menu/messages", - tags=["main-menu"], - ) - app.include_router( - welcome_texts.router, - prefix="/welcome-texts", - tags=["welcome-texts"], - ) app.include_router(pages.router, prefix="/pages", tags=["pages"]) app.include_router(promocodes.router, prefix="/promo-codes", tags=["promo-codes"]) app.include_router(broadcasts.router, prefix="/broadcasts", tags=["broadcasts"]) diff --git a/app/webapi/routes/__init__.py b/app/webapi/routes/__init__.py index cbe4030a..4484462a 100644 --- a/app/webapi/routes/__init__.py +++ b/app/webapi/routes/__init__.py @@ -7,8 +7,6 @@ from . import ( partners, polls, promo_offers, - user_messages, - welcome_texts, pages, promo_groups, servers, @@ -32,8 +30,6 @@ __all__ = [ "partners", "polls", "promo_offers", - "user_messages", - "welcome_texts", "pages", "promo_groups", "servers", diff --git a/app/webapi/routes/user_messages.py b/app/webapi/routes/user_messages.py deleted file mode 100644 index d6fba008..00000000 --- a/app/webapi/routes/user_messages.py +++ /dev/null @@ -1,124 +0,0 @@ -from __future__ import annotations - -from typing import Any - -from fastapi import APIRouter, Depends, HTTPException, Query, Response, Security, status -from sqlalchemy.ext.asyncio import AsyncSession - -from app.database.crud.user_message import ( - create_user_message, - delete_user_message, - get_all_user_messages, - get_user_message_by_id, - get_user_messages_count, - toggle_user_message_status, - update_user_message, -) - -from ..dependencies import get_db_session, require_api_token -from ..schemas.user_messages import ( - UserMessageCreateRequest, - UserMessageListResponse, - UserMessageResponse, - UserMessageUpdateRequest, -) - -router = APIRouter() - - -def _serialize(message) -> UserMessageResponse: - return UserMessageResponse( - id=message.id, - message_text=message.message_text, - is_active=message.is_active, - sort_order=message.sort_order, - created_by=message.created_by, - created_at=message.created_at, - updated_at=message.updated_at, - ) - - -@router.get("", response_model=UserMessageListResponse) -async def list_user_messages( - _: Any = Security(require_api_token), - db: AsyncSession = Depends(get_db_session), - limit: int = Query(50, ge=1, le=200), - offset: int = Query(0, ge=0), - include_inactive: bool = Query(True, description="Включать неактивные сообщения"), -) -> UserMessageListResponse: - total = await get_user_messages_count(db, include_inactive=include_inactive) - messages = await get_all_user_messages( - db, - offset=offset, - limit=limit, - include_inactive=include_inactive, - ) - - return UserMessageListResponse( - items=[_serialize(message) for message in messages], - total=total, - limit=limit, - offset=offset, - ) - - -@router.post("", response_model=UserMessageResponse, status_code=status.HTTP_201_CREATED) -async def create_user_message_endpoint( - payload: UserMessageCreateRequest, - token: Any = Security(require_api_token), - db: AsyncSession = Depends(get_db_session), -) -> UserMessageResponse: - created_by = getattr(token, "id", None) - message = await create_user_message( - db, - message_text=payload.message_text, - created_by=created_by, - is_active=payload.is_active, - sort_order=payload.sort_order, - ) - - return _serialize(message) - - -@router.patch("/{message_id}", response_model=UserMessageResponse) -async def update_user_message_endpoint( - message_id: int, - payload: UserMessageUpdateRequest, - _: Any = Security(require_api_token), - db: AsyncSession = Depends(get_db_session), -) -> UserMessageResponse: - update_payload = payload.dict(exclude_unset=True) - message = await update_user_message(db, message_id, **update_payload) - - if not message: - raise HTTPException(status.HTTP_404_NOT_FOUND, "User message not found") - - return _serialize(message) - - -@router.post("/{message_id}/toggle", response_model=UserMessageResponse) -async def toggle_user_message_endpoint( - message_id: int, - _: Any = Security(require_api_token), - db: AsyncSession = Depends(get_db_session), -) -> UserMessageResponse: - message = await toggle_user_message_status(db, message_id) - - if not message: - raise HTTPException(status.HTTP_404_NOT_FOUND, "User message not found") - - return _serialize(message) - - -@router.delete("/{message_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_user_message_endpoint( - message_id: int, - _: Any = Security(require_api_token), - db: AsyncSession = Depends(get_db_session), -) -> Response: - message = await get_user_message_by_id(db, message_id) - if not message: - raise HTTPException(status.HTTP_404_NOT_FOUND, "User message not found") - - await delete_user_message(db, message_id) - return Response(status_code=status.HTTP_204_NO_CONTENT) diff --git a/app/webapi/routes/welcome_texts.py b/app/webapi/routes/welcome_texts.py deleted file mode 100644 index 906cc0d8..00000000 --- a/app/webapi/routes/welcome_texts.py +++ /dev/null @@ -1,122 +0,0 @@ -from __future__ import annotations - -from typing import Any - -from fastapi import APIRouter, Depends, HTTPException, Query, Response, Security, status -from sqlalchemy.ext.asyncio import AsyncSession - -from app.database.crud.welcome_text import ( - count_welcome_texts, - create_welcome_text, - delete_welcome_text, - get_welcome_text_by_id, - list_welcome_texts, - update_welcome_text, -) - -from ..dependencies import get_db_session, require_api_token -from ..schemas.welcome_texts import ( - WelcomeTextCreateRequest, - WelcomeTextListResponse, - WelcomeTextResponse, - WelcomeTextUpdateRequest, -) - -router = APIRouter() - - -def _serialize(text) -> WelcomeTextResponse: - return WelcomeTextResponse( - id=text.id, - text=text.text_content, - is_active=text.is_active, - is_enabled=text.is_enabled, - created_by=text.created_by, - created_at=text.created_at, - updated_at=text.updated_at, - ) - - -@router.get("", response_model=WelcomeTextListResponse) -async def list_welcome_texts_endpoint( - _: Any = Security(require_api_token), - db: AsyncSession = Depends(get_db_session), - limit: int = Query(50, ge=1, le=200), - offset: int = Query(0, ge=0), - include_inactive: bool = Query(True, description="Включать неактивные тексты"), -) -> WelcomeTextListResponse: - total = await count_welcome_texts(db, include_inactive=include_inactive) - records = await list_welcome_texts( - db, - limit=limit, - offset=offset, - include_inactive=include_inactive, - ) - - return WelcomeTextListResponse( - items=[_serialize(item) for item in records], - total=total, - limit=limit, - offset=offset, - ) - - -@router.post("", response_model=WelcomeTextResponse, status_code=status.HTTP_201_CREATED) -async def create_welcome_text_endpoint( - payload: WelcomeTextCreateRequest, - token: Any = Security(require_api_token), - db: AsyncSession = Depends(get_db_session), -) -> WelcomeTextResponse: - created_by = getattr(token, "id", None) - record = await create_welcome_text( - db, - text_content=payload.text, - created_by=created_by, - is_enabled=payload.is_enabled, - is_active=payload.is_active, - ) - - return _serialize(record) - - -@router.get("/{welcome_text_id}", response_model=WelcomeTextResponse) -async def get_welcome_text_endpoint( - welcome_text_id: int, - _: Any = Security(require_api_token), - db: AsyncSession = Depends(get_db_session), -) -> WelcomeTextResponse: - record = await get_welcome_text_by_id(db, welcome_text_id) - if not record: - raise HTTPException(status.HTTP_404_NOT_FOUND, "Welcome text not found") - - return _serialize(record) - - -@router.patch("/{welcome_text_id}", response_model=WelcomeTextResponse) -async def update_welcome_text_endpoint( - welcome_text_id: int, - payload: WelcomeTextUpdateRequest, - _: Any = Security(require_api_token), - db: AsyncSession = Depends(get_db_session), -) -> WelcomeTextResponse: - record = await get_welcome_text_by_id(db, welcome_text_id) - if not record: - raise HTTPException(status.HTTP_404_NOT_FOUND, "Welcome text not found") - - update_payload = payload.dict(exclude_unset=True) - updated = await update_welcome_text(db, record, **update_payload) - return _serialize(updated) - - -@router.delete("/{welcome_text_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_welcome_text_endpoint( - welcome_text_id: int, - _: Any = Security(require_api_token), - db: AsyncSession = Depends(get_db_session), -) -> Response: - record = await get_welcome_text_by_id(db, welcome_text_id) - if not record: - raise HTTPException(status.HTTP_404_NOT_FOUND, "Welcome text not found") - - await delete_welcome_text(db, record) - return Response(status_code=status.HTTP_204_NO_CONTENT) diff --git a/app/webapi/schemas/user_messages.py b/app/webapi/schemas/user_messages.py deleted file mode 100644 index 97f41c69..00000000 --- a/app/webapi/schemas/user_messages.py +++ /dev/null @@ -1,50 +0,0 @@ -from __future__ import annotations - -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, Field, validator - - -def _normalize_text(value: str) -> str: - cleaned = (value or "").strip() - if not cleaned: - raise ValueError("Message text cannot be empty") - return cleaned - - -class UserMessageResponse(BaseModel): - id: int - message_text: str - is_active: bool - sort_order: int - created_by: Optional[int] - created_at: datetime - updated_at: datetime - - -class UserMessageCreateRequest(BaseModel): - message_text: str = Field(..., min_length=1, max_length=4000) - is_active: bool = True - sort_order: int = Field(0, ge=0) - - _normalize_message_text = validator("message_text", allow_reuse=True)(_normalize_text) - - -class UserMessageUpdateRequest(BaseModel): - message_text: Optional[str] = Field(None, min_length=1, max_length=4000) - is_active: Optional[bool] = None - sort_order: Optional[int] = Field(None, ge=0) - - @validator("message_text") - def validate_message_text(cls, value): # noqa: D401,B902 - if value is None: - return value - return _normalize_text(value) - - -class UserMessageListResponse(BaseModel): - items: list[UserMessageResponse] - total: int - limit: int - offset: int diff --git a/app/webapi/schemas/welcome_texts.py b/app/webapi/schemas/welcome_texts.py deleted file mode 100644 index e68c9410..00000000 --- a/app/webapi/schemas/welcome_texts.py +++ /dev/null @@ -1,50 +0,0 @@ -from __future__ import annotations - -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, Field, validator - - -def _normalize_text(value: str) -> str: - cleaned = (value or "").strip() - if not cleaned: - raise ValueError("Text cannot be empty") - return cleaned - - -class WelcomeTextResponse(BaseModel): - id: int - text: str - is_active: bool - is_enabled: bool - created_by: Optional[int] - created_at: datetime - updated_at: datetime - - -class WelcomeTextCreateRequest(BaseModel): - text: str = Field(..., min_length=1, max_length=4000) - is_enabled: bool = True - is_active: bool = True - - _normalize_text = validator("text", allow_reuse=True)(_normalize_text) - - -class WelcomeTextUpdateRequest(BaseModel): - text: Optional[str] = Field(None, min_length=1, max_length=4000) - is_enabled: Optional[bool] = None - is_active: Optional[bool] = None - - @validator("text") - def validate_text(cls, value): # noqa: D401,B902 - if value is None: - return value - return _normalize_text(value) - - -class WelcomeTextListResponse(BaseModel): - items: list[WelcomeTextResponse] - total: int - limit: int - offset: int