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.
80 lines
2.6 KiB
Python
80 lines
2.6 KiB
Python
"""Job intervals move into sysconfig; the unread SponsorBlock default goes
|
|
|
|
Revision ID: 0110_intervals_in_sysconfig
|
|
Revises: 0109_job_runs
|
|
Create Date: 2026-10-18
|
|
|
|
An admin's interval override lived in its own table, `scheduler_settings`, beside the one store every
|
|
other admin knob uses. Each override becomes the `job_interval_<job>` sysconfig key and the table
|
|
goes. Only the jobs that still exist are copied: a row for any other id never did anything.
|
|
|
|
The `sponsorblock_default` setting was offered on the Configuration page but nothing ever read it, so
|
|
its key is gone and its stored value with it — the downgrade cannot bring that value back.
|
|
"""
|
|
|
|
import sqlalchemy as sa
|
|
from alembic import op
|
|
|
|
revision: str = "0110_intervals_in_sysconfig"
|
|
down_revision: str | None = "0109_job_runs"
|
|
branch_labels: str | None = None
|
|
depends_on: str | None = None
|
|
|
|
# The interval jobs as of this revision (a migration does not read the live code's list).
|
|
_JOBS = (
|
|
"rss_poll",
|
|
"enrich",
|
|
"backfill",
|
|
"autotag",
|
|
"shorts",
|
|
"subscriptions",
|
|
"playlist_sync",
|
|
"maintenance",
|
|
"explore_cleanup",
|
|
"download_gc",
|
|
"plex_sync",
|
|
"plex_watch_sync",
|
|
"plex_watch_reconcile",
|
|
"tmdb_enrich",
|
|
"people_enrich",
|
|
"audit_gc",
|
|
"log_gc",
|
|
)
|
|
_PREFIX = "job_interval_"
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.execute(
|
|
sa.text(
|
|
"INSERT INTO system_config (key, value, encrypted)"
|
|
" SELECT :prefix || job_id, interval_minutes::text, false FROM scheduler_settings"
|
|
" WHERE interval_minutes IS NOT NULL AND job_id IN :jobs"
|
|
" ON CONFLICT (key) DO NOTHING"
|
|
).bindparams(sa.bindparam("jobs", expanding=True), prefix=_PREFIX, jobs=list(_JOBS))
|
|
)
|
|
op.drop_table("scheduler_settings")
|
|
op.execute("DELETE FROM system_config WHERE key = 'sponsorblock_default'")
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.create_table(
|
|
"scheduler_settings",
|
|
sa.Column("job_id", sa.String(40), primary_key=True),
|
|
sa.Column("interval_minutes", sa.Integer(), nullable=False),
|
|
)
|
|
op.execute(
|
|
sa.text(
|
|
"INSERT INTO scheduler_settings (job_id, interval_minutes)"
|
|
" SELECT substr(key, :start), value::int FROM system_config WHERE key IN :keys"
|
|
).bindparams(
|
|
sa.bindparam("keys", expanding=True),
|
|
start=len(_PREFIX) + 1,
|
|
keys=[_PREFIX + j for j in _JOBS],
|
|
)
|
|
)
|
|
op.execute(
|
|
sa.text("DELETE FROM system_config WHERE key IN :keys").bindparams(
|
|
sa.bindparam("keys", expanding=True), keys=[_PREFIX + j for j in _JOBS]
|
|
)
|
|
)
|