Files
security-alert-center/backend/alembic/versions/014_mobile_seaca.py
T
PTah 563b836acc feat: Seaca mobile API, enrollment, FCM push and admin UI (0.9.0)
Adds mobile device registration by admin codes, refresh tokens, push channel
in notification policy, and Settings section for managing Seaca clients.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 13:19:14 +10:00

106 lines
5.2 KiB
Python

"""mobile settings, enrollment codes, devices, refresh tokens; policy use_mobile
Revision ID: 014
Revises: 013
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
revision: str = "014"
down_revision: Union[str, None] = "013"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
MOBILE_SETTINGS_ROW_ID = 1
def upgrade() -> None:
op.add_column(
"notification_policy",
sa.Column("use_mobile", sa.Boolean(), nullable=False, server_default=sa.text("false")),
)
op.create_table(
"mobile_settings",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("devices_allowed", sa.Boolean(), nullable=False, server_default=sa.text("false")),
sa.Column("max_devices_per_user", sa.Integer(), nullable=False, server_default="3"),
sa.Column("min_app_version", sa.String(length=32), nullable=True),
sa.PrimaryKeyConstraint("id"),
)
op.execute(
sa.text(
"INSERT INTO mobile_settings (id, devices_allowed, max_devices_per_user) "
f"VALUES ({MOBILE_SETTINGS_ROW_ID}, false, 3)"
)
)
op.create_table(
"mobile_enrollment_codes",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("label", sa.String(length=128), nullable=False, server_default=""),
sa.Column("code_hash", sa.String(length=64), nullable=False),
sa.Column("code_prefix", sa.String(length=16), nullable=False),
sa.Column("created_by_user_id", sa.Integer(), nullable=True),
sa.Column("target_user_id", sa.Integer(), nullable=True),
sa.Column("login_mode", sa.String(length=16), nullable=False, server_default="password"),
sa.Column("max_uses", sa.Integer(), nullable=False, server_default="1"),
sa.Column("use_count", sa.Integer(), nullable=False, server_default="0"),
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("revoked_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
sa.ForeignKeyConstraint(["created_by_user_id"], ["sac_users.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(["target_user_id"], ["sac_users.id"], ondelete="SET NULL"),
sa.PrimaryKeyConstraint("id"),
)
op.create_index("ix_mobile_enrollment_codes_code_hash", "mobile_enrollment_codes", ["code_hash"], unique=True)
op.create_table(
"mobile_devices",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("user_id", sa.Integer(), nullable=False),
sa.Column("device_uuid", sa.String(length=64), nullable=False),
sa.Column("display_name", sa.String(length=128), nullable=False, server_default=""),
sa.Column("platform", sa.String(length=32), nullable=False, server_default="android"),
sa.Column("app_version", sa.String(length=32), nullable=True),
sa.Column("fcm_token", sa.String(length=512), nullable=True),
sa.Column("fcm_token_updated_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("enrollment_code_id", sa.Integer(), nullable=True),
sa.Column("enrolled_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
sa.Column("last_seen_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("revoked_at", sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(["user_id"], ["sac_users.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["enrollment_code_id"], ["mobile_enrollment_codes.id"], ondelete="SET NULL"),
sa.PrimaryKeyConstraint("id"),
)
op.create_index("ix_mobile_devices_device_uuid", "mobile_devices", ["device_uuid"], unique=True)
op.create_index("ix_mobile_devices_user_id", "mobile_devices", ["user_id"])
op.create_table(
"mobile_refresh_tokens",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("device_id", sa.Integer(), nullable=False),
sa.Column("token_hash", sa.String(length=64), nullable=False),
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
sa.Column("revoked_at", sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(["device_id"], ["mobile_devices.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
)
op.create_index("ix_mobile_refresh_tokens_token_hash", "mobile_refresh_tokens", ["token_hash"], unique=True)
def downgrade() -> None:
op.drop_index("ix_mobile_refresh_tokens_token_hash", table_name="mobile_refresh_tokens")
op.drop_table("mobile_refresh_tokens")
op.drop_index("ix_mobile_devices_user_id", table_name="mobile_devices")
op.drop_index("ix_mobile_devices_device_uuid", table_name="mobile_devices")
op.drop_table("mobile_devices")
op.drop_index("ix_mobile_enrollment_codes_code_hash", table_name="mobile_enrollment_codes")
op.drop_table("mobile_enrollment_codes")
op.drop_table("mobile_settings")
op.drop_column("notification_policy", "use_mobile")