- JobSetting ORM model (JSONB extra_config) + Pydantic schemas
- GET/PUT /api/v1/admin/jobs/settings + /{job_type} (X-Admin-Token auth)
- celery_app.py builds beat_schedule from job_settings DB (env fallback
retained for safety on first deploy / DB unreachable)
- nspd_geo task reads rate_ms from job_settings when per-job row has
no override
- enqueue/resume geo jobs route to queue_name from job_settings
- Worker container: --queues=celery,scrape_kn,geo (one container,
three named queues — kn sweep no longer blocks nspd_geo)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
89 lines
3.1 KiB
Python
89 lines
3.1 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, Header, HTTPException
|
||
from sqlalchemy.orm import Session
|
||
|
||
from app.core.config import settings
|
||
from app.core.db import get_db
|
||
from app.schemas.job_settings import JobSettingRead, JobSettingUpdate
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
def _check_token(x_admin_token: str | None) -> None:
|
||
if not settings.scrape_admin_token:
|
||
raise HTTPException(
|
||
status_code=503,
|
||
detail="admin jobs disabled — set SCRAPE_ADMIN_TOKEN",
|
||
)
|
||
if x_admin_token != settings.scrape_admin_token:
|
||
raise HTTPException(status_code=401, detail="invalid admin token")
|
||
|
||
|
||
@router.get("/settings", response_model=list[JobSettingRead])
|
||
def list_job_settings(
|
||
db: Annotated[Session, Depends(get_db)],
|
||
x_admin_token: Annotated[str | None, Header(alias="X-Admin-Token")] = None,
|
||
) -> Any:
|
||
"""Список всех job_settings (все job_type)."""
|
||
_check_token(x_admin_token)
|
||
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)],
|
||
x_admin_token: Annotated[str | None, Header(alias="X-Admin-Token")] = None,
|
||
) -> Any:
|
||
"""Настройки одного job_type. Возвращает fallback defaults если строки нет в БД."""
|
||
_check_token(x_admin_token)
|
||
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)],
|
||
x_admin_token: Annotated[str | None, Header(alias="X-Admin-Token")] = None,
|
||
) -> Any:
|
||
"""PATCH-style update: передавать только изменяемые поля.
|
||
|
||
После изменения cron_schedule нужен restart beat-контейнера.
|
||
Остальные поля (rate_ms, extra_config, enabled) применяются динамически.
|
||
|
||
cron_schedule='' устанавливает NULL (manual-only режим).
|
||
rate_ms=0 устанавливает NULL (no throttle).
|
||
"""
|
||
_check_token(x_admin_token)
|
||
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
|