feat: 23 опкод для регистрации, смс шлюз, докер

This commit is contained in:
zavolo
2026-03-15 13:25:40 -04:00
parent d4d5dd5530
commit 2d09f52c2e
29 changed files with 1088 additions and 29 deletions

View File

View File

@@ -0,0 +1,51 @@
from __future__ import annotations
import logging
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from app.config import reload_config
from app.deps import get_sms_service, init_service
from app.service import SmsService
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/admin", tags=["Admin"])
class RoutingInfo(BaseModel):
phone: str
primary_provider: str
fallback_provider: str | None
@router.post("/reload", response_model=dict)
async def reload() -> dict:
"""Перечитать config.yaml без перезапуска сервиса."""
new_config = reload_config()
init_service()
providers = list(new_config.providers.keys())
rules_count = len(new_config.routing.rules)
logger.info("Конфиг перезагружен: провайдеры=%s правил=%d", providers, rules_count)
return {"success": True, "providers": providers, "routing_rules": rules_count}
@router.get("/routing/resolve", response_model=RoutingInfo)
async def resolve_routing(
phone: str,
service: SmsService = Depends(get_sms_service),
) -> RoutingInfo:
"""Проверить, какой провайдер будет выбран для номера."""
primary, fallback = service.config.resolve_provider(phone)
return RoutingInfo(phone=phone, primary_provider=primary, fallback_provider=fallback)
@router.get("/routing/rules", response_model=list[dict])
async def list_rules(
service: SmsService = Depends(get_sms_service),
) -> list[dict]:
"""Список всех правил маршрутизации."""
return [rule.model_dump() for rule in service.config.routing.rules]
@router.get("/providers", response_model=list[dict])
async def list_providers(
service: SmsService = Depends(get_sms_service),
) -> list[dict]:
"""Список активных провайдеров."""
return [
{"name": name, "type": name, "enabled": True}
for name in service.providers.keys()
]

View File

@@ -0,0 +1,43 @@
from __future__ import annotations
import logging
from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel
from app.deps import get_sms_service
from app.service import SmsService
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/lk", tags=["Личный кабинет"])
class PendingCode(BaseModel):
phone: str
code: str
expires_in: int
@router.get("/codes", response_model=list[PendingCode])
async def list_codes(
service: SmsService = Depends(get_sms_service),
) -> list[PendingCode]:
items = await service.list_pending_codes()
return [PendingCode(**item) for item in items]
@router.get("/code", response_model=PendingCode)
async def get_code(
phone: str = Query(..., description="Номер телефона"),
service: SmsService = Depends(get_sms_service),
) -> PendingCode:
items = await service.list_pending_codes()
normalized = phone if phone.startswith("+") else f"+{phone}"
for item in items:
if item["phone"] == normalized:
return PendingCode(**item)
raise HTTPException(status_code=404, detail="Код не найден или истёк")
@router.delete("/code", response_model=dict)
async def consume_code(
phone: str = Query(..., description="Номер телефона"),
service: SmsService = Depends(get_sms_service),
) -> dict:
code = await service.consume_code(phone)
if code is None:
raise HTTPException(status_code=404, detail="Код не найден или истёк")
return {"success": True, "phone": phone, "consumed_code": code}

View File

@@ -0,0 +1,41 @@
from __future__ import annotations
import logging
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from app.deps import get_sms_service
from app.service import RateLimitExceeded, SmsService
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/sms", tags=["SMS"])
class SendCodeRequest(BaseModel):
phone_number: str
class SendCodeResponse(BaseModel):
success: bool
provider: str
phone_number: str
code: str | None = None
error: str | None = None
@router.post("/send", response_model=SendCodeResponse)
async def send_code(
request: SendCodeRequest,
service: SmsService = Depends(get_sms_service),
) -> SendCodeResponse:
try:
result = await service.send_code(request.phone_number)
except RateLimitExceeded as e:
raise HTTPException(
status_code=429,
detail={"error": "Слишком много запросов для этого номера", "retry_after": e.retry_after},
headers={"Retry-After": str(e.retry_after)},
)
if not result.success:
raise HTTPException(status_code=502, detail=result.error or "Ошибка отправки SMS")
return SendCodeResponse(
success=True,
provider=result.provider,
phone_number=request.phone_number,
code=result.code,
)