Merge pull request #2032 from BEDOLAGA-DEV/revert-2030-j1ummj-bedolaga/expand-bot-api-for-welcome-text

Revert "Expose admin APIs for welcome texts and menu messages"
This commit is contained in:
Egor
2025-11-25 01:19:01 +03:00
committed by GitHub
8 changed files with 7 additions and 487 deletions

View File

@@ -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()

View File

@@ -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 бесплатно! "

View File

@@ -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"])

View File

@@ -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",

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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