"""phase13 connectors: connector_account/sync_state/external_link/oauth_state/log + finance_tx/health_sample/chat_message Revision ID: p13a1b2c3d4e5 Revises: p12f6a7b8c9d0 Create Date: 2026-06-15 06:00:00.000000 """ from typing import Sequence, Union import sqlalchemy as sa import sqlmodel from alembic import op revision: str = "p13a1b2c3d4e5" down_revision: Union[str, Sequence[str], None] = "p12f6a7b8c9d0" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None S = sqlmodel.sql.sqltypes.AutoString def upgrade() -> None: op.create_table( "connector_account", sa.Column("id", S(), nullable=False), sa.Column("user_id", S(), nullable=False), sa.Column("domain", S(), nullable=False), sa.Column("mode", S(), nullable=False), sa.Column("provider", S(), nullable=False), sa.Column("external_account_id", S(), nullable=False), sa.Column("name", S(), nullable=False), sa.Column("kind", S(), nullable=False), sa.Column("tone", S(), nullable=False), sa.Column("state", S(), nullable=False), sa.Column("last_synced_at", sa.DateTime(), nullable=True), sa.Column("last_label", S(), nullable=False), sa.Column("token_enc", S(), nullable=False), sa.Column("scopes", S(), nullable=False), sa.Column("error_detail", S(), nullable=False), sa.Column("created_at", sa.DateTime(), nullable=False), sa.Column("updated_at", sa.DateTime(), nullable=False), sa.ForeignKeyConstraint(["user_id"], ["person.id"]), sa.PrimaryKeyConstraint("id"), ) op.create_table( "connector_sync_state", sa.Column("id", S(), nullable=False), sa.Column("account_id", S(), nullable=False), sa.Column("cursor", S(), nullable=False), sa.Column("page_token", S(), nullable=False), sa.Column("last_full_sync_at", sa.DateTime(), nullable=True), sa.Column("last_delta_sync_at", sa.DateTime(), nullable=True), sa.Column("items_seen", sa.Integer(), nullable=False), sa.Column("items_upserted", sa.Integer(), nullable=False), sa.ForeignKeyConstraint(["account_id"], ["connector_account.id"]), sa.PrimaryKeyConstraint("id"), ) op.create_table( "external_link", sa.Column("id", S(), nullable=False), sa.Column("account_id", S(), nullable=False), sa.Column("external_id", S(), nullable=False), sa.Column("entity_type", S(), nullable=False), sa.Column("entity_id", S(), nullable=False), sa.Column("etag", S(), nullable=False), sa.Column("external_updated_at", sa.DateTime(), nullable=True), sa.Column("created_at", sa.DateTime(), nullable=False), sa.ForeignKeyConstraint(["account_id"], ["connector_account.id"]), sa.PrimaryKeyConstraint("id"), ) op.create_index("ix_external_link_account_id", "external_link", ["account_id"]) op.create_index("ix_external_link_external_id", "external_link", ["external_id"]) # 멱등성 핵심: (account_id, external_id) 복합 UNIQUE op.create_index( "ux_extlink_acct_ext", "external_link", ["account_id", "external_id"], unique=True ) op.create_table( "oauth_state", sa.Column("id", S(), nullable=False), sa.Column("domain", S(), nullable=False), sa.Column("provider", S(), nullable=False), sa.Column("code_verifier", S(), nullable=False), sa.Column("redirect_after", S(), nullable=False), sa.Column("created_at", sa.DateTime(), nullable=False), sa.PrimaryKeyConstraint("id"), ) op.create_table( "connector_account_log", sa.Column("id", S(), nullable=False), sa.Column("account_id", S(), nullable=False), sa.Column("action", S(), nullable=False), sa.Column("detail", S(), nullable=False), sa.Column("items", sa.Integer(), nullable=False), sa.Column("created_at", sa.DateTime(), nullable=False), sa.ForeignKeyConstraint(["account_id"], ["connector_account.id"]), sa.PrimaryKeyConstraint("id"), ) op.create_table( "finance_tx", sa.Column("id", S(), nullable=False), sa.Column("account_id", S(), nullable=False), sa.Column("date", S(), nullable=False), sa.Column("merchant", S(), nullable=False), sa.Column("amount", sa.Integer(), nullable=False), sa.Column("category", S(), nullable=False), sa.Column("category_id", S(), nullable=False), sa.Column("account_label", S(), nullable=False), sa.Column("created_at", sa.DateTime(), nullable=False), sa.ForeignKeyConstraint(["account_id"], ["connector_account.id"]), sa.PrimaryKeyConstraint("id"), ) op.create_table( "health_sample", sa.Column("id", S(), nullable=False), sa.Column("account_id", S(), nullable=False), sa.Column("kind", S(), nullable=False), sa.Column("value", sa.Float(), nullable=False), sa.Column("unit", S(), nullable=False), sa.Column("day", S(), nullable=False), sa.Column("created_at", sa.DateTime(), nullable=False), sa.ForeignKeyConstraint(["account_id"], ["connector_account.id"]), sa.PrimaryKeyConstraint("id"), ) op.create_table( "chat_message", sa.Column("id", S(), nullable=False), sa.Column("account_id", S(), nullable=False), sa.Column("sender", S(), nullable=False), sa.Column("text", S(), nullable=False), sa.Column("channel", S(), nullable=False), sa.Column("at", S(), nullable=False), sa.Column("created_at", sa.DateTime(), nullable=False), sa.ForeignKeyConstraint(["account_id"], ["connector_account.id"]), sa.PrimaryKeyConstraint("id"), ) def downgrade() -> None: op.drop_table("chat_message") op.drop_table("health_sample") op.drop_table("finance_tx") op.drop_table("connector_account_log") op.drop_table("oauth_state") op.drop_index("ux_extlink_acct_ext", table_name="external_link") op.drop_index("ix_external_link_external_id", table_name="external_link") op.drop_index("ix_external_link_account_id", table_name="external_link") op.drop_table("external_link") op.drop_table("connector_sync_state") op.drop_table("connector_account")