0098 deleted the reset job's run rows but not the scheduler_interval rows that changing its interval wrote; with the job's label gone they rendered as a raw id. The delete now takes every job-targeted row for it, and a test seeds both kinds below 0098 and upgrades (red on the old filter). The test has its own file so the removed-features guard can name it.
56 lines
2.4 KiB
Python
56 lines
2.4 KiB
Python
"""demo: drop the shared demo account (the owner's decision, 2026-10-04)
|
|
|
|
The demo account — one shared user that whitelisted emails entered from the login page, reset on a
|
|
schedule — goes with its code. Prod held the demo user and 10 whitelist rows.
|
|
|
|
The upgrade deletes the demo user first (its playlists, states and subscriptions cascade; audit and
|
|
quota rows keep their history with the actor nulled, as for any deleted account), then the traces
|
|
that only the demo code could render: its audit rows (whitelist edits, manual resets, and every row
|
|
about the reset job — its runs and its interval changes), the reset job's interval override and its
|
|
completion notices. Nothing is migrated anywhere; the nightly backup holds the rows.
|
|
|
|
The downgrade restores the schema as it stood (0014), empty: the demo user is created lazily on its
|
|
first sign-in, so an empty `demo_whitelist` is the whole of its state.
|
|
"""
|
|
|
|
import sqlalchemy as sa
|
|
from alembic import op
|
|
|
|
revision: str = "0098_drop_demo_account"
|
|
down_revision: str | None = "0097_drop_messages"
|
|
branch_labels: str | None = None
|
|
depends_on: str | None = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.execute("DELETE FROM users WHERE is_demo")
|
|
op.execute(
|
|
"DELETE FROM audit_log WHERE action IN ('demo_add', 'demo_remove', 'demo_reset')"
|
|
" OR (target_type = 'job' AND target_id = 'demo_reset')"
|
|
)
|
|
op.execute("DELETE FROM scheduler_settings WHERE job_id = 'demo_reset'")
|
|
op.execute("DELETE FROM notifications WHERE data->>'job_id' = 'demo_reset'")
|
|
op.drop_index("ix_demo_whitelist_email", table_name="demo_whitelist")
|
|
op.drop_table("demo_whitelist")
|
|
op.drop_index("ix_users_is_demo", table_name="users")
|
|
op.drop_column("users", "is_demo")
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.add_column(
|
|
"users",
|
|
sa.Column("is_demo", sa.Boolean(), nullable=False, server_default=sa.false()),
|
|
)
|
|
op.create_index("ix_users_is_demo", "users", ["is_demo"])
|
|
op.create_table(
|
|
"demo_whitelist",
|
|
sa.Column("id", sa.Integer(), primary_key=True),
|
|
sa.Column("email", sa.String(length=320), nullable=False),
|
|
sa.Column("note", sa.Text(), nullable=True),
|
|
sa.Column("added_by", sa.String(length=320), nullable=True),
|
|
sa.Column(
|
|
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
|
),
|
|
)
|
|
op.create_index("ix_demo_whitelist_email", "demo_whitelist", ["email"], unique=True)
|