mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-05 19:50:23 +00:00
513 lines
16 KiB
Python
513 lines
16 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any, List, Optional
|
|
|
|
from fastapi import (
|
|
APIRouter,
|
|
Body,
|
|
Depends,
|
|
HTTPException,
|
|
Query,
|
|
Response,
|
|
Security,
|
|
status,
|
|
)
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.config import settings
|
|
from app.database.crud.faq import get_faq_page_by_id
|
|
from app.database.crud.rules import (
|
|
clear_all_rules,
|
|
create_or_update_rules,
|
|
get_all_rules_versions,
|
|
get_rules_by_language,
|
|
restore_rules_version,
|
|
)
|
|
from app.database.models import ServiceRule
|
|
from app.services.faq_service import FaqService
|
|
from app.services.privacy_policy_service import PrivacyPolicyService
|
|
from app.services.public_offer_service import PublicOfferService
|
|
|
|
from ..dependencies import get_db_session, require_api_token
|
|
from ..schemas.pages import (
|
|
FaqPageCreateRequest,
|
|
FaqPageListResponse,
|
|
FaqPageResponse,
|
|
FaqPageUpdateRequest,
|
|
FaqReorderRequest,
|
|
FaqStatusResponse,
|
|
FaqStatusUpdateRequest,
|
|
RichTextPageResponse,
|
|
RichTextPageUpdateRequest,
|
|
ServiceRulesHistoryResponse,
|
|
ServiceRulesResponse,
|
|
ServiceRulesUpdateRequest,
|
|
)
|
|
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def _serialize_rich_page(
|
|
*,
|
|
requested_language: str,
|
|
content: str,
|
|
language: str,
|
|
is_enabled: Optional[bool],
|
|
created_at,
|
|
updated_at,
|
|
splitter,
|
|
) -> RichTextPageResponse:
|
|
pages = splitter(content or "")
|
|
return RichTextPageResponse(
|
|
requested_language=requested_language,
|
|
language=language,
|
|
is_enabled=is_enabled,
|
|
content=content or "",
|
|
content_pages=pages,
|
|
created_at=created_at,
|
|
updated_at=updated_at,
|
|
)
|
|
|
|
|
|
def _serialize_faq_page(page) -> FaqPageResponse:
|
|
return FaqPageResponse(
|
|
id=page.id,
|
|
language=page.language,
|
|
title=page.title,
|
|
content=page.content,
|
|
content_pages=FaqService.split_content_into_pages(page.content),
|
|
display_order=page.display_order,
|
|
is_active=page.is_active,
|
|
created_at=page.created_at,
|
|
updated_at=page.updated_at,
|
|
)
|
|
|
|
|
|
def _serialize_rules(rule: ServiceRule) -> ServiceRulesResponse:
|
|
return ServiceRulesResponse(
|
|
id=rule.id,
|
|
title=rule.title,
|
|
content=rule.content,
|
|
language=rule.language,
|
|
is_active=rule.is_active,
|
|
created_at=rule.created_at,
|
|
updated_at=rule.updated_at,
|
|
)
|
|
|
|
|
|
@router.get("/public-offer", response_model=RichTextPageResponse)
|
|
async def get_public_offer(
|
|
_: object = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
language: str = Query("ru", min_length=2, max_length=10),
|
|
fallback: bool = Query(True, description="Использовать запасной язык, если контента нет"),
|
|
include_disabled: bool = Query(
|
|
True,
|
|
description="Возвращать контент даже если страница выключена",
|
|
),
|
|
) -> RichTextPageResponse:
|
|
requested_lang = PublicOfferService.normalize_language(language)
|
|
offer = await PublicOfferService.get_offer(db, requested_lang, fallback=fallback)
|
|
|
|
if not offer:
|
|
raise HTTPException(status.HTTP_404_NOT_FOUND, "Public offer not found")
|
|
|
|
if not include_disabled and not offer.is_enabled:
|
|
raise HTTPException(status.HTTP_404_NOT_FOUND, "Public offer disabled")
|
|
|
|
return _serialize_rich_page(
|
|
requested_language=requested_lang,
|
|
language=offer.language,
|
|
is_enabled=offer.is_enabled,
|
|
content=offer.content or "",
|
|
created_at=offer.created_at,
|
|
updated_at=offer.updated_at,
|
|
splitter=PublicOfferService.split_content_into_pages,
|
|
)
|
|
|
|
|
|
@router.put("/public-offer", response_model=RichTextPageResponse)
|
|
async def update_public_offer(
|
|
payload: RichTextPageUpdateRequest,
|
|
_: object = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
) -> RichTextPageResponse:
|
|
lang = PublicOfferService.normalize_language(payload.language)
|
|
offer = await PublicOfferService.save_offer(db, lang, payload.content)
|
|
|
|
if payload.is_enabled is not None:
|
|
offer = await PublicOfferService.set_enabled(db, lang, payload.is_enabled)
|
|
|
|
refreshed = await PublicOfferService.get_offer(db, lang, fallback=False)
|
|
offer = refreshed or offer
|
|
|
|
return _serialize_rich_page(
|
|
requested_language=lang,
|
|
language=offer.language,
|
|
is_enabled=offer.is_enabled,
|
|
content=offer.content or "",
|
|
created_at=offer.created_at,
|
|
updated_at=offer.updated_at,
|
|
splitter=PublicOfferService.split_content_into_pages,
|
|
)
|
|
|
|
|
|
@router.get("/privacy-policy", response_model=RichTextPageResponse)
|
|
async def get_privacy_policy(
|
|
_: object = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
language: str = Query("ru", min_length=2, max_length=10),
|
|
fallback: bool = Query(True),
|
|
include_disabled: bool = Query(True),
|
|
) -> RichTextPageResponse:
|
|
requested_lang = PrivacyPolicyService.normalize_language(language)
|
|
policy = await PrivacyPolicyService.get_policy(db, requested_lang, fallback=fallback)
|
|
|
|
if not policy:
|
|
raise HTTPException(status.HTTP_404_NOT_FOUND, "Privacy policy not found")
|
|
|
|
if not include_disabled and not policy.is_enabled:
|
|
raise HTTPException(status.HTTP_404_NOT_FOUND, "Privacy policy disabled")
|
|
|
|
return _serialize_rich_page(
|
|
requested_language=requested_lang,
|
|
language=policy.language,
|
|
is_enabled=policy.is_enabled,
|
|
content=policy.content or "",
|
|
created_at=policy.created_at,
|
|
updated_at=policy.updated_at,
|
|
splitter=PrivacyPolicyService.split_content_into_pages,
|
|
)
|
|
|
|
|
|
@router.put("/privacy-policy", response_model=RichTextPageResponse)
|
|
async def update_privacy_policy(
|
|
payload: RichTextPageUpdateRequest,
|
|
_: object = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
) -> RichTextPageResponse:
|
|
lang = PrivacyPolicyService.normalize_language(payload.language)
|
|
policy = await PrivacyPolicyService.save_policy(db, lang, payload.content)
|
|
|
|
if payload.is_enabled is not None:
|
|
policy = await PrivacyPolicyService.set_enabled(db, lang, payload.is_enabled)
|
|
|
|
refreshed = await PrivacyPolicyService.get_policy(db, lang, fallback=False)
|
|
policy = refreshed or policy
|
|
|
|
return _serialize_rich_page(
|
|
requested_language=lang,
|
|
language=policy.language,
|
|
is_enabled=policy.is_enabled,
|
|
content=policy.content or "",
|
|
created_at=policy.created_at,
|
|
updated_at=policy.updated_at,
|
|
splitter=PrivacyPolicyService.split_content_into_pages,
|
|
)
|
|
|
|
|
|
@router.get("/faq", response_model=FaqPageListResponse)
|
|
async def list_faq_pages(
|
|
_: object = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
language: str = Query("ru", min_length=2, max_length=10),
|
|
include_inactive: bool = Query(True),
|
|
fallback: bool = Query(True),
|
|
) -> FaqPageListResponse:
|
|
requested_lang = FaqService.normalize_language(language)
|
|
pages = await FaqService.get_pages(
|
|
db,
|
|
requested_lang,
|
|
include_inactive=include_inactive,
|
|
fallback=fallback,
|
|
)
|
|
|
|
resolved_language = requested_lang
|
|
if pages:
|
|
resolved_language = pages[0].language
|
|
|
|
setting = await FaqService.get_setting(db, requested_lang, fallback=fallback)
|
|
is_enabled = bool(setting.is_enabled) if setting else False
|
|
if setting:
|
|
resolved_language = setting.language
|
|
|
|
serialized = [_serialize_faq_page(page) for page in pages]
|
|
return FaqPageListResponse(
|
|
requested_language=requested_lang,
|
|
language=resolved_language,
|
|
is_enabled=is_enabled,
|
|
total=len(serialized),
|
|
items=serialized,
|
|
)
|
|
|
|
|
|
@router.get("/faq/status", response_model=FaqStatusResponse)
|
|
async def get_faq_status(
|
|
_: object = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
language: str = Query("ru", min_length=2, max_length=10),
|
|
fallback: bool = Query(True),
|
|
) -> FaqStatusResponse:
|
|
requested_lang = FaqService.normalize_language(language)
|
|
setting = await FaqService.get_setting(db, requested_lang, fallback=fallback)
|
|
|
|
if not setting:
|
|
return FaqStatusResponse(
|
|
requested_language=requested_lang,
|
|
language=requested_lang,
|
|
is_enabled=False,
|
|
)
|
|
|
|
return FaqStatusResponse(
|
|
requested_language=requested_lang,
|
|
language=setting.language,
|
|
is_enabled=bool(setting.is_enabled),
|
|
)
|
|
|
|
|
|
@router.put("/faq/status", response_model=FaqStatusResponse)
|
|
async def update_faq_status(
|
|
payload: Optional[FaqStatusUpdateRequest] = Body(None),
|
|
language: str = Query("ru", min_length=2, max_length=10),
|
|
is_enabled: Optional[bool] = Query(None),
|
|
_: object = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
) -> FaqStatusResponse:
|
|
resolved_language = FaqService.normalize_language(
|
|
payload.language if payload and payload.language else language
|
|
)
|
|
|
|
enabled_status = payload.is_enabled if payload else is_enabled
|
|
if enabled_status is None:
|
|
raise HTTPException(
|
|
status.HTTP_400_BAD_REQUEST, "Parameter 'is_enabled' is required"
|
|
)
|
|
|
|
setting = await FaqService.set_enabled(db, resolved_language, enabled_status)
|
|
|
|
return FaqStatusResponse(
|
|
requested_language=resolved_language,
|
|
language=setting.language,
|
|
is_enabled=bool(setting.is_enabled),
|
|
)
|
|
|
|
|
|
@router.post("/faq", response_model=FaqPageResponse, status_code=status.HTTP_201_CREATED)
|
|
async def create_faq_page(
|
|
payload: FaqPageCreateRequest,
|
|
_: object = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
) -> FaqPageResponse:
|
|
lang = FaqService.normalize_language(payload.language)
|
|
is_active = True if payload.is_active is None else payload.is_active
|
|
|
|
page = await FaqService.create_page(
|
|
db,
|
|
language=lang,
|
|
title=payload.title,
|
|
content=payload.content,
|
|
display_order=payload.display_order,
|
|
is_active=is_active,
|
|
)
|
|
|
|
return _serialize_faq_page(page)
|
|
|
|
|
|
@router.get("/faq/{page_id}", response_model=FaqPageResponse)
|
|
async def get_faq_page(
|
|
page_id: int,
|
|
_: object = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
language: str = Query("ru", min_length=2, max_length=10),
|
|
include_inactive: bool = Query(True),
|
|
) -> FaqPageResponse:
|
|
requested_lang = FaqService.normalize_language(language)
|
|
page = await FaqService.get_page(
|
|
db,
|
|
page_id,
|
|
requested_lang,
|
|
include_inactive=include_inactive,
|
|
fallback=True,
|
|
)
|
|
|
|
if not page:
|
|
raise HTTPException(status.HTTP_404_NOT_FOUND, "FAQ page not found")
|
|
|
|
return _serialize_faq_page(page)
|
|
|
|
|
|
@router.put("/faq/{page_id}", response_model=FaqPageResponse)
|
|
async def update_faq_page(
|
|
page_id: int,
|
|
payload: FaqPageUpdateRequest,
|
|
_: object = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
) -> FaqPageResponse:
|
|
page = await get_faq_page_by_id(db, page_id)
|
|
|
|
if not page:
|
|
raise HTTPException(status.HTTP_404_NOT_FOUND, "FAQ page not found")
|
|
|
|
updated = await FaqService.update_page(
|
|
db,
|
|
page,
|
|
title=payload.title,
|
|
content=payload.content,
|
|
display_order=payload.display_order,
|
|
is_active=payload.is_active,
|
|
)
|
|
|
|
return _serialize_faq_page(updated)
|
|
|
|
|
|
@router.delete("/faq/{page_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
async def delete_faq_page(
|
|
page_id: int,
|
|
_: object = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
) -> Response:
|
|
page = await get_faq_page_by_id(db, page_id)
|
|
if not page:
|
|
raise HTTPException(status.HTTP_404_NOT_FOUND, "FAQ page not found")
|
|
|
|
await FaqService.delete_page(db, page_id)
|
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
|
|
|
|
@router.post("/faq/reorder", response_model=FaqPageListResponse)
|
|
async def reorder_faq_pages(
|
|
payload: FaqReorderRequest,
|
|
_: object = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
) -> FaqPageListResponse:
|
|
lang = FaqService.normalize_language(payload.language)
|
|
|
|
ordered_payload = sorted(payload.items, key=lambda item: item.display_order)
|
|
|
|
existing_pages = await FaqService.get_pages(
|
|
db,
|
|
lang,
|
|
include_inactive=True,
|
|
fallback=False,
|
|
)
|
|
pages_by_id = {page.id: page for page in existing_pages}
|
|
|
|
pages: List[Any] = []
|
|
for item in ordered_payload:
|
|
page = pages_by_id.get(item.id)
|
|
if not page:
|
|
raise HTTPException(
|
|
status.HTTP_404_NOT_FOUND,
|
|
f"FAQ page {item.id} not found for language {lang}",
|
|
)
|
|
pages.append(page)
|
|
|
|
ordered_ids = {item.id for item in ordered_payload}
|
|
remaining = [page for page in existing_pages if page.id not in ordered_ids]
|
|
pages.extend(sorted(remaining, key=lambda page: (page.display_order, page.id)))
|
|
|
|
await FaqService.reorder_pages(db, lang, pages)
|
|
|
|
updated_pages = await FaqService.get_pages(
|
|
db,
|
|
lang,
|
|
include_inactive=True,
|
|
fallback=False,
|
|
)
|
|
setting = await FaqService.get_setting(db, lang, fallback=False)
|
|
serialized = [_serialize_faq_page(page) for page in updated_pages]
|
|
return FaqPageListResponse(
|
|
requested_language=lang,
|
|
language=lang,
|
|
is_enabled=bool(setting.is_enabled) if setting else False,
|
|
total=len(serialized),
|
|
items=serialized,
|
|
)
|
|
|
|
|
|
@router.get("/service-rules", response_model=ServiceRulesResponse)
|
|
async def get_service_rules(
|
|
_: object = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
language: str = Query("ru", min_length=2, max_length=10),
|
|
fallback: bool = Query(True),
|
|
) -> ServiceRulesResponse:
|
|
requested_lang = language.split("-")[0].lower()
|
|
rules = await get_rules_by_language(db, requested_lang)
|
|
|
|
if not rules and fallback:
|
|
default_lang = (settings.DEFAULT_LANGUAGE or "ru").split("-")[0].lower()
|
|
if default_lang != requested_lang:
|
|
rules = await get_rules_by_language(db, default_lang)
|
|
|
|
if not rules:
|
|
raise HTTPException(status.HTTP_404_NOT_FOUND, "Service rules not found")
|
|
|
|
return _serialize_rules(rules)
|
|
|
|
|
|
@router.put("/service-rules", response_model=ServiceRulesResponse)
|
|
async def update_service_rules(
|
|
payload: ServiceRulesUpdateRequest,
|
|
_: object = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
) -> ServiceRulesResponse:
|
|
lang = payload.language.split("-")[0].lower()
|
|
title = payload.title or "Правила сервиса"
|
|
rules = await create_or_update_rules(
|
|
db,
|
|
content=payload.content,
|
|
language=lang,
|
|
title=title,
|
|
)
|
|
|
|
return _serialize_rules(rules)
|
|
|
|
|
|
@router.delete("/service-rules", status_code=status.HTTP_204_NO_CONTENT)
|
|
async def clear_service_rules(
|
|
_: object = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
language: str = Query("ru", min_length=2, max_length=10),
|
|
) -> Response:
|
|
lang = language.split("-")[0].lower()
|
|
await clear_all_rules(db, lang)
|
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
|
|
|
|
@router.get("/service-rules/history", response_model=ServiceRulesHistoryResponse)
|
|
async def get_service_rules_history(
|
|
_: object = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
language: str = Query("ru", min_length=2, max_length=10),
|
|
limit: int = Query(10, ge=1, le=100),
|
|
) -> ServiceRulesHistoryResponse:
|
|
lang = language.split("-")[0].lower()
|
|
history = await get_all_rules_versions(db, lang, limit=limit)
|
|
items = [_serialize_rules(item) for item in history]
|
|
return ServiceRulesHistoryResponse(
|
|
language=lang,
|
|
total=len(items),
|
|
items=items,
|
|
)
|
|
|
|
|
|
@router.post(
|
|
"/service-rules/history/{rule_id}/restore",
|
|
response_model=ServiceRulesResponse,
|
|
status_code=status.HTTP_201_CREATED,
|
|
)
|
|
async def restore_service_rules_version(
|
|
rule_id: int,
|
|
_: object = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
language: str = Query("ru", min_length=2, max_length=10),
|
|
) -> ServiceRulesResponse:
|
|
lang = language.split("-")[0].lower()
|
|
restored = await restore_rules_version(db, rule_id, language=lang)
|
|
if not restored:
|
|
raise HTTPException(status.HTTP_404_NOT_FOUND, "Rules version not found")
|
|
return _serialize_rules(restored)
|
|
|