feat(config): job intervals live in sysconfig, every setting says when it applies, the unread SponsorBlock default goes

An interval override was a row of its own table beside the one store every other admin knob uses; it
is now the `job_interval_<job>` sysconfig key (migration 0110 copies them over and drops the table).
The Scheduler page stays its one editor: the Configuration page and the setup wizard neither list nor
write those keys, since a write there would skip the reschedule.

Each ConfigSpec now states `applies` (required, no default). Every key is `live`, held by a scan that
finds no read at import time; a `restart` key is refused until the page can say so. A second scan
requires a reader for every key on the Configuration page — `sponsorblock_default` had none since it
was added, so it leaves the registry, the translations and the store.
This commit is contained in:
2026-10-18 02:23:15 +02:00
parent 3bab2e7dc7
commit d13483a593
15 changed files with 529 additions and 136 deletions
+3 -3
View File
@@ -91,12 +91,12 @@ def setup_admin(body: SetupAdminIn, db: Session = Depends(require_setup)) -> dic
@router.post("/config")
def setup_config(settings_map: dict[str, Any], db: Session = Depends(require_setup)) -> dict:
"""Persist provided DB-backed settings (Google creds, SMTP, quota…). Keys must be in the
sysconfig registry; empty values are skipped (left at the default). Genuinely dynamic
"""Persist provided DB-backed settings (Google creds, SMTP, quota…). Keys must be the
Configuration page's; empty values are skipped (left at the default). Genuinely dynamic
key→value map, so it stays a typed dict rather than a fixed model."""
saved: list[str] = []
for key, value in settings_map.items():
if sysconfig.spec(key) is None:
if sysconfig.config_page_spec(key) is None:
raise refused(400, "unknown_setting", key=key)
if value is None or value == "":
continue