Per audit batch #127 P2 hygiene (issue #128). ## Problem Identical `_check_token` function в 3 admin files: - backend/app/api/v1/admin_scrape.py:37-44 - backend/app/api/v1/admin_leads.py:26-33 - backend/app/api/v1/admin_jobs.py:24-31 ## Fix New `backend/app/core/deps.py` с `verify_admin_token` + Annotated alias: ```python AdminTokenAuth = Annotated[None, Depends(verify_admin_token)] ``` Заменено в 27 endpoints (18 admin_scrape + 6 admin_leads + 3 admin_jobs): - Removed local `_check_token` definitions - `_: None = Depends(_check_token)` → `_: AdminTokenAuth` - Removed unused imports (`Header`, `HTTPException`, `settings` где не используется) ## Semantics preserved Все 3 original implementations были identical (503 if env missing + 401 if mismatch, same header `X-Admin-Token`, same env `SCRAPE_ADMIN_TOKEN`). Минор: 503 detail text unified to `"admin disabled — set SCRAPE_ADMIN_TOKEN"` (в оригинале три разные строки — `"admin scrape disabled..."` etc). Cosmetic — detail в HTTP 503 не парсится клиентским кодом. ## Tests `uv run pytest tests/ -x -k admin` → 64/64 passed. ## Vault `code/patterns/Pattern_Admin_Token_Dependency_May14.md` — created. Closes #128 Refs: #127 Co-authored-by: lekss361 <claudestars@proton.me>
76 lines
2.5 KiB
Python
76 lines
2.5 KiB
Python
"""Admin API для централизованных настроек Celery-job'ов.
|
||
|
||
GET /api/v1/admin/jobs/settings — список всех job_settings
|
||
GET /api/v1/admin/jobs/settings/{type} — одна строка по job_type
|
||
PUT /api/v1/admin/jobs/settings/{type} — PATCH-style update
|
||
|
||
Auth: X-Admin-Token header (тот же токен что у admin_scrape endpoints).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Annotated, Any
|
||
|
||
from fastapi import APIRouter, Depends
|
||
from sqlalchemy.orm import Session
|
||
|
||
from app.core.db import get_db
|
||
from app.core.deps import AdminTokenAuth
|
||
from app.schemas.job_settings import JobSettingRead, JobSettingUpdate
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
@router.get("/settings", response_model=list[JobSettingRead])
|
||
def list_job_settings(
|
||
db: Annotated[Session, Depends(get_db)],
|
||
_: AdminTokenAuth,
|
||
) -> Any:
|
||
"""Список всех job_settings (все job_type)."""
|
||
from app.services.job_settings import get_all
|
||
|
||
return get_all(db)
|
||
|
||
|
||
@router.get("/settings/{job_type}", response_model=JobSettingRead)
|
||
def get_job_setting(
|
||
job_type: str,
|
||
db: Annotated[Session, Depends(get_db)],
|
||
_: AdminTokenAuth,
|
||
) -> Any:
|
||
"""Настройки одного job_type. Возвращает fallback defaults если строки нет в БД."""
|
||
from app.services.job_settings import get_one
|
||
|
||
return get_one(job_type, db)
|
||
|
||
|
||
@router.put("/settings/{job_type}", response_model=JobSettingRead)
|
||
def update_job_setting(
|
||
job_type: str,
|
||
payload: JobSettingUpdate,
|
||
db: Annotated[Session, Depends(get_db)],
|
||
_: AdminTokenAuth,
|
||
) -> Any:
|
||
"""PATCH-style update: передавать только изменяемые поля.
|
||
|
||
После изменения cron_schedule нужен restart beat-контейнера.
|
||
Остальные поля (rate_ms, extra_config, enabled) применяются динамически.
|
||
|
||
cron_schedule='' устанавливает NULL (manual-only режим).
|
||
rate_ms=0 устанавливает NULL (no throttle).
|
||
"""
|
||
from app.services.job_settings import update
|
||
|
||
result = update(
|
||
job_type,
|
||
db,
|
||
enabled=payload.enabled,
|
||
queue_name=payload.queue_name,
|
||
cron_schedule=payload.cron_schedule,
|
||
rate_ms=payload.rate_ms,
|
||
max_retries=payload.max_retries,
|
||
max_concurrency=payload.max_concurrency,
|
||
extra_config=payload.extra_config,
|
||
updated_by="admin-ui",
|
||
)
|
||
return result
|