Jul 16 - Fill the gaps between Single-tenant mode and Multi-tenant mode - MT3
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
"""phase42 — areas.qr_token for per-area public QR landing pages
|
||||
|
||||
Adds a unique, unguessable token per area. Each area's QR encodes
|
||||
/f/area/<qr_token>, a login-free summary scoped to that area plus a
|
||||
"report a problem" form.
|
||||
|
||||
Ported from the single-tenant chain (phase39_area_public_token) and adapted:
|
||||
the MT column is named `qr_token` (matching `facilities.qr_token` from
|
||||
phase38_facility_qr), not `public_token`, and is VARCHAR(64) to match the
|
||||
facility column and MT's `secrets.token_urlsafe(32)` generator.
|
||||
|
||||
Unlike the ST original, existing areas are NOT backfilled: MT mints tokens
|
||||
lazily via `Area.ensure_qr_token()` on first use, exactly as
|
||||
`Facility.ensure_qr_token()` already does. A backfill would mint tokens for
|
||||
areas nobody ever prints a code for.
|
||||
|
||||
Uses INFORMATION_SCHEMA checks — safe to re-run on every tenant DB.
|
||||
"""
|
||||
|
||||
revision = 'phase42_area_qr_token'
|
||||
down_revision = 'phase41_auditor_role'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def _column_exists(conn, table, column):
|
||||
return conn.execute(sa.text(
|
||||
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS "
|
||||
"WHERE TABLE_SCHEMA = DATABASE() "
|
||||
"AND TABLE_NAME = :t AND COLUMN_NAME = :c"
|
||||
), {"t": table, "c": column}).scalar() > 0
|
||||
|
||||
|
||||
def _index_exists(conn, table, index):
|
||||
return conn.execute(sa.text(
|
||||
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS "
|
||||
"WHERE TABLE_SCHEMA = DATABASE() "
|
||||
"AND TABLE_NAME = :t AND INDEX_NAME = :i"
|
||||
), {"t": table, "i": index}).scalar() > 0
|
||||
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
|
||||
if not _column_exists(bind, 'areas', 'qr_token'):
|
||||
op.execute(sa.text(
|
||||
"ALTER TABLE areas ADD COLUMN qr_token VARCHAR(64) NULL"
|
||||
))
|
||||
|
||||
# Unique index tolerates multiple NULLs in MySQL, so it can be created
|
||||
# immediately — no backfill needed before enforcing uniqueness.
|
||||
if not _index_exists(bind, 'areas', 'uq_area_qr_token'):
|
||||
op.execute(sa.text(
|
||||
"CREATE UNIQUE INDEX uq_area_qr_token ON areas (qr_token)"
|
||||
))
|
||||
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
|
||||
if _index_exists(bind, 'areas', 'uq_area_qr_token'):
|
||||
op.execute(sa.text("DROP INDEX uq_area_qr_token ON areas"))
|
||||
if _column_exists(bind, 'areas', 'qr_token'):
|
||||
op.execute(sa.text("ALTER TABLE areas DROP COLUMN qr_token"))
|
||||
Reference in New Issue
Block a user