125 lines
5.5 KiB
Python
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)
|