"""add user_kis_credentials table (multi-tenant phase 1) Revision ID: 0009 Revises: 0008 Create Date: 2026-09-21 """ from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision = "0009" down_revision = "0008" branch_labels = None depends_on = None def upgrade() -> None: conn = op.get_bind() exists = conn.execute(sa.text( "SELECT 1 FROM information_schema.tables WHERE table_name='user_kis_credentials'" )).fetchone() if exists: return # create_all 로 이미 생성됨 op.create_table( "user_kis_credentials", sa.Column("id", sa.Integer(), primary_key=True), sa.Column("user_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False), sa.Column("mode", sa.String(10), nullable=False), sa.Column("app_key_enc", sa.Text(), nullable=False), sa.Column("app_secret_enc", sa.Text(), nullable=False), sa.Column("account_no_enc", sa.Text(), nullable=False), sa.Column("account_product_code", sa.String(2), nullable=False, server_default="01"), sa.Column("app_key_hash", sa.String(64), nullable=False), sa.Column("app_key_hint", sa.String(8), nullable=True), sa.Column("is_enabled", sa.Boolean(), nullable=False, server_default=sa.false()), sa.Column("auto_trade_enabled", sa.Boolean(), nullable=False, server_default=sa.false()), sa.Column("last_verified_at", sa.DateTime(), nullable=True), sa.Column("key_version", sa.Integer(), nullable=False, server_default="1"), sa.Column("created_at", sa.DateTime(), nullable=False, server_default=sa.func.now()), sa.Column("updated_at", sa.DateTime(), nullable=True), ) op.create_index( "ix_user_kis_credentials_user_id", "user_kis_credentials", ["user_id"], unique=True ) op.create_index( "ix_user_kis_credentials_app_key_hash", "user_kis_credentials", ["app_key_hash"], unique=True ) def downgrade() -> None: # L3: 테이블이 `create_all`(SQLAlchemy 모델의 `unique=True` 컬럼)로 먼저 생성된 # 경우 실제로는 named index가 아니라 unique constraint(`..._key`)로 존재해 # `drop_index`가 실패한다. 존재할 때만 드롭한다. conn = op.get_bind() for index_name in ( "ix_user_kis_credentials_app_key_hash", "ix_user_kis_credentials_user_id", ): exists = conn.execute(sa.text( "SELECT 1 FROM pg_indexes WHERE tablename='user_kis_credentials' AND indexname=:name" ), {"name": index_name}).fetchone() if exists: op.drop_index(index_name, table_name="user_kis_credentials") op.drop_table("user_kis_credentials")