|
| 1 | +"""Add viewer tokens, app settings, session fields, and no_download columns (v7.2.0). |
| 2 | +
|
| 3 | +Creates: |
| 4 | +1. viewer_tokens table for share-token authentication |
| 5 | +2. app_settings table for cross-container configuration |
| 6 | +3. no_download column on viewer_accounts |
| 7 | +4. no_download + source_token_id columns on viewer_sessions |
| 8 | +
|
| 9 | +Revision ID: 010 |
| 10 | +Revises: 009 |
| 11 | +Create Date: 2026-03-10 |
| 12 | +
|
| 13 | +""" |
| 14 | + |
| 15 | +from collections.abc import Sequence |
| 16 | + |
| 17 | +import sqlalchemy as sa |
| 18 | + |
| 19 | +from alembic import op |
| 20 | + |
| 21 | +revision: str = "010" |
| 22 | +down_revision: str | None = "009" |
| 23 | +branch_labels: str | Sequence[str] | None = None |
| 24 | +depends_on: str | Sequence[str] | None = None |
| 25 | + |
| 26 | + |
| 27 | +def upgrade() -> None: |
| 28 | + conn = op.get_bind() |
| 29 | + inspector = sa.inspect(conn) |
| 30 | + existing_tables = set(inspector.get_table_names()) |
| 31 | + |
| 32 | + # -- viewer_tokens table -- |
| 33 | + if "viewer_tokens" not in existing_tables: |
| 34 | + op.create_table( |
| 35 | + "viewer_tokens", |
| 36 | + sa.Column("id", sa.Integer(), nullable=False), |
| 37 | + sa.Column("label", sa.String(255), nullable=True), |
| 38 | + sa.Column("token_hash", sa.String(128), nullable=False, unique=True), |
| 39 | + sa.Column("token_salt", sa.String(64), nullable=False), |
| 40 | + sa.Column("created_by", sa.String(255), nullable=False), |
| 41 | + sa.Column("allowed_chat_ids", sa.Text(), nullable=False), |
| 42 | + sa.Column("is_revoked", sa.Integer(), server_default="0"), |
| 43 | + sa.Column("no_download", sa.Integer(), server_default="0"), |
| 44 | + sa.Column("expires_at", sa.DateTime(), nullable=True), |
| 45 | + sa.Column("last_used_at", sa.DateTime(), nullable=True), |
| 46 | + sa.Column("use_count", sa.Integer(), server_default="0"), |
| 47 | + sa.Column("created_at", sa.DateTime(), server_default=sa.func.now()), |
| 48 | + sa.PrimaryKeyConstraint("id"), |
| 49 | + ) |
| 50 | + op.create_index("idx_viewer_tokens_created_by", "viewer_tokens", ["created_by"]) |
| 51 | + op.create_index("idx_viewer_tokens_is_revoked", "viewer_tokens", ["is_revoked"]) |
| 52 | + else: |
| 53 | + existing_indexes = {idx["name"] for idx in inspector.get_indexes("viewer_tokens")} |
| 54 | + if "idx_viewer_tokens_created_by" not in existing_indexes: |
| 55 | + op.create_index("idx_viewer_tokens_created_by", "viewer_tokens", ["created_by"]) |
| 56 | + if "idx_viewer_tokens_is_revoked" not in existing_indexes: |
| 57 | + op.create_index("idx_viewer_tokens_is_revoked", "viewer_tokens", ["is_revoked"]) |
| 58 | + |
| 59 | + # -- app_settings table -- |
| 60 | + if "app_settings" not in existing_tables: |
| 61 | + op.create_table( |
| 62 | + "app_settings", |
| 63 | + sa.Column("key", sa.String(255), nullable=False), |
| 64 | + sa.Column("value", sa.Text(), nullable=False), |
| 65 | + sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now()), |
| 66 | + sa.PrimaryKeyConstraint("key"), |
| 67 | + ) |
| 68 | + |
| 69 | + # -- no_download column on viewer_accounts -- |
| 70 | + existing_va_cols = {c["name"] for c in inspector.get_columns("viewer_accounts")} |
| 71 | + if "no_download" not in existing_va_cols: |
| 72 | + op.add_column("viewer_accounts", sa.Column("no_download", sa.Integer(), server_default="0", nullable=True)) |
| 73 | + |
| 74 | + # -- no_download and source_token_id columns on viewer_sessions -- |
| 75 | + if "viewer_sessions" in existing_tables: |
| 76 | + existing_vs_cols = {c["name"] for c in inspector.get_columns("viewer_sessions")} |
| 77 | + if "no_download" not in existing_vs_cols: |
| 78 | + op.add_column("viewer_sessions", sa.Column("no_download", sa.Integer(), server_default="0", nullable=True)) |
| 79 | + if "source_token_id" not in existing_vs_cols: |
| 80 | + op.add_column("viewer_sessions", sa.Column("source_token_id", sa.Integer(), nullable=True)) |
| 81 | + existing_vs_indexes = {idx["name"] for idx in inspector.get_indexes("viewer_sessions")} |
| 82 | + if "idx_viewer_sessions_source_token" not in existing_vs_indexes: |
| 83 | + op.create_index("idx_viewer_sessions_source_token", "viewer_sessions", ["source_token_id"]) |
| 84 | + |
| 85 | + |
| 86 | +def downgrade() -> None: |
| 87 | + op.drop_index("idx_viewer_sessions_source_token", table_name="viewer_sessions") |
| 88 | + op.drop_column("viewer_sessions", "source_token_id") |
| 89 | + op.drop_column("viewer_sessions", "no_download") |
| 90 | + op.drop_column("viewer_accounts", "no_download") |
| 91 | + op.drop_table("app_settings") |
| 92 | + op.drop_index("idx_viewer_tokens_is_revoked", table_name="viewer_tokens") |
| 93 | + op.drop_index("idx_viewer_tokens_created_by", table_name="viewer_tokens") |
| 94 | + op.drop_table("viewer_tokens") |
0 commit comments