PMFlow/backend/alembic/versions/20260428_add_phone_auth_ide...

125 lines
5.5 KiB
Python

"""add phone auth identity
Revision ID: 20260428_add_phone_auth_identity
Revises: 20260428_repair_admin_yangyu_workspace_state
Create Date: 2026-04-28
"""
from __future__ import annotations
from alembic import op
import sqlalchemy as sa
revision = "20260428_add_phone_auth_identity"
down_revision = "20260428_repair_admin_yangyu_workspace_state"
branch_labels = None
depends_on = None
def _table_exists(bind, table_name: str) -> bool:
return sa.inspect(bind).has_table(table_name)
def _column_exists(bind, table_name: str, column_name: str) -> bool:
if not _table_exists(bind, table_name):
return False
return any(column["name"] == column_name for column in sa.inspect(bind).get_columns(table_name))
def _index_exists(bind, table_name: str, index_name: str) -> bool:
if not _table_exists(bind, table_name):
return False
return any(index["name"] == index_name for index in sa.inspect(bind).get_indexes(table_name))
def upgrade() -> None:
bind = op.get_bind()
if _table_exists(bind, "users"):
for column in (
sa.Column("phone_country_code", sa.String(length=8), nullable=True),
sa.Column("phone_number", sa.String(length=32), nullable=True),
sa.Column("phone_e164", sa.String(length=32), nullable=True),
sa.Column("phone_verified_at", sa.TIMESTAMP(), nullable=True),
sa.Column("last_login_at", sa.TIMESTAMP(), nullable=True),
sa.Column("login_method", sa.String(length=32), nullable=True),
):
if not _column_exists(bind, "users", column.name):
op.add_column("users", column)
if not _index_exists(bind, "users", "ix_users_phone_e164"):
op.create_index("ix_users_phone_e164", "users", ["phone_e164"], unique=True)
if _table_exists(bind, "tenant_invitations"):
for column in (
sa.Column("phone_country_code", sa.String(length=8), nullable=True),
sa.Column("phone_number", sa.String(length=32), nullable=True),
sa.Column("phone_e164", sa.String(length=32), nullable=True),
):
if not _column_exists(bind, "tenant_invitations", column.name):
op.add_column("tenant_invitations", column)
if not _index_exists(bind, "tenant_invitations", "ix_tenant_invitations_phone_e164"):
op.create_index("ix_tenant_invitations_phone_e164", "tenant_invitations", ["phone_e164"], unique=False)
if bind.dialect.name == "postgresql" and not _index_exists(
bind,
"tenant_invitations",
"uq_tenant_invitations_pending_phone",
):
op.execute(
"""
CREATE UNIQUE INDEX uq_tenant_invitations_pending_phone
ON tenant_invitations (tenant_id, phone_e164)
WHERE status = 'pending' AND phone_e164 IS NOT NULL
"""
)
if not _table_exists(bind, "phone_verification_codes"):
op.create_table(
"phone_verification_codes",
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
sa.Column("created_at", sa.TIMESTAMP(), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.TIMESTAMP(), server_default=sa.func.now(), nullable=False),
sa.Column("phone_e164", sa.String(length=32), nullable=False, index=True),
sa.Column("purpose", sa.String(length=32), nullable=False, index=True),
sa.Column("code_hash", sa.String(length=128), nullable=False),
sa.Column("expires_at", sa.TIMESTAMP(), nullable=False),
sa.Column("consumed_at", sa.TIMESTAMP(), nullable=True),
sa.Column("attempt_count", sa.Integer(), nullable=False, server_default="0"),
sa.Column("request_ip", sa.String(length=64), nullable=True),
)
op.create_index(
"ix_phone_verification_codes_phone_purpose_created",
"phone_verification_codes",
["phone_e164", "purpose", "created_at"],
unique=False,
)
def downgrade() -> None:
bind = op.get_bind()
if _table_exists(bind, "phone_verification_codes"):
if _index_exists(bind, "phone_verification_codes", "ix_phone_verification_codes_phone_purpose_created"):
op.drop_index("ix_phone_verification_codes_phone_purpose_created", table_name="phone_verification_codes")
op.drop_table("phone_verification_codes")
if _table_exists(bind, "tenant_invitations"):
if bind.dialect.name == "postgresql" and _index_exists(bind, "tenant_invitations", "uq_tenant_invitations_pending_phone"):
op.drop_index("uq_tenant_invitations_pending_phone", table_name="tenant_invitations")
if _index_exists(bind, "tenant_invitations", "ix_tenant_invitations_phone_e164"):
op.drop_index("ix_tenant_invitations_phone_e164", table_name="tenant_invitations")
for column_name in ("phone_e164", "phone_number", "phone_country_code"):
if _column_exists(bind, "tenant_invitations", column_name):
op.drop_column("tenant_invitations", column_name)
if _table_exists(bind, "users"):
if _index_exists(bind, "users", "ix_users_phone_e164"):
op.drop_index("ix_users_phone_e164", table_name="users")
for column_name in (
"login_method",
"last_login_at",
"phone_verified_at",
"phone_e164",
"phone_number",
"phone_country_code",
):
if _column_exists(bind, "users", column_name):
op.drop_column("users", column_name)