gendesign/backend/app/api/v1/admin_jobs.py
lekss361 2fee7739e8 feat(jobs): centralized job_settings API + DB-driven beat schedule
- 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>
2026-05-11 15:38:13 +03:00

89 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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