Files
siftlode/backend/alembic/versions/0098_drop_demo_account.py
T
peter eecc195b7e fix(migrations): dropping the demo also takes its reset job's interval-change audit rows
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.
2026-10-05 00:19:50 +02:00

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)