작업 내용 저장

This commit is contained in:
2026-04-23 09:56:08 +00:00
parent 514b49cb98
commit d383c7086f
29 changed files with 1654 additions and 1372 deletions
@@ -0,0 +1,158 @@
"""remove bot_principals and auth_audit_logs, add service token support
Revision ID: 20260423_0013
Revises: 20260414_0012
Create Date: 2026-04-23 00:00:00
"""
from __future__ import annotations
import sqlalchemy as sa
from alembic import op
from sqlalchemy import inspect
revision = "20260423_0013"
down_revision = "20260414_0012"
branch_labels = None
depends_on = None
def _table_exists(inspector, table_name: str) -> bool:
return table_name in inspector.get_table_names()
def _column_exists(inspector, table_name: str, column_name: str) -> bool:
if not _table_exists(inspector, table_name):
return False
return any(c["name"] == column_name for c in inspector.get_columns(table_name))
def _index_exists(inspector, table_name: str, index_name: str) -> bool:
if not _table_exists(inspector, table_name):
return False
return any(idx["name"] == index_name for idx in inspector.get_indexes(table_name))
def upgrade() -> None:
bind = op.get_bind()
inspector = inspect(bind)
# 사전 검증: bot_principals.owner_user_id NULL 행이 있으면 중단
if _table_exists(inspector, "bot_principals"):
null_count = bind.execute(
sa.text("SELECT COUNT(*) FROM bot_principals WHERE owner_user_id IS NULL")
).scalar()
if null_count and int(null_count) > 0:
raise RuntimeError(
f"마이그레이션 중단: bot_principals.owner_user_id NULL 행 {null_count}개 발견. "
"owner_user_id를 먼저 보정한 후 다시 실행하세요."
)
# auth_tokens.user_id 보정: bot_principal.id → bot_principal.owner_user_id
if _column_exists(inspector, "auth_tokens", "bot_principal_id"):
bind.execute(
sa.text(
"""
UPDATE auth_tokens t
SET user_id = b.owner_user_id
FROM bot_principals b
WHERE t.bot_principal_id = b.id
AND t.bot_principal_id IS NOT NULL
"""
)
)
# token_type 'bot' → 'service' rename
bind.execute(
sa.text("UPDATE auth_tokens SET token_type = 'service' WHERE token_type = 'bot'")
)
# service token name unique index 생성
inspector = inspect(bind)
if not _index_exists(inspector, "auth_tokens", "idx_auth_tokens_service_name_unique"):
op.execute(
"""
CREATE UNIQUE INDEX IF NOT EXISTS idx_auth_tokens_service_name_unique
ON auth_tokens (name)
WHERE token_type = 'service'
"""
)
# auth_tokens.bot_principal_id 컬럼 DROP
inspector = inspect(bind)
if _column_exists(inspector, "auth_tokens", "bot_principal_id"):
# 기존 인덱스 제거 후 컬럼 DROP
if _index_exists(inspector, "auth_tokens", "idx_auth_tokens_bot_created"):
op.drop_index("idx_auth_tokens_bot_created", table_name="auth_tokens")
op.drop_column("auth_tokens", "bot_principal_id")
# auth_audit_logs 테이블 DROP
inspector = inspect(bind)
if _table_exists(inspector, "auth_audit_logs"):
op.drop_table("auth_audit_logs")
# bot_principals 테이블 DROP (관련 인덱스·FK 포함)
inspector = inspect(bind)
if _table_exists(inspector, "bot_principals"):
for idx_name in [
"idx_bot_principals_active_created",
"idx_bot_principals_owner_active_created",
]:
if _index_exists(inspector, "bot_principals", idx_name):
op.drop_index(idx_name, table_name="bot_principals")
op.drop_table("bot_principals")
def downgrade() -> None:
bind = op.get_bind()
inspector = inspect(bind)
# bot_principals 테이블 복원
if not _table_exists(inspector, "bot_principals"):
op.create_table(
"bot_principals",
sa.Column("id", sa.Text(), nullable=False),
sa.Column("name", sa.Text(), nullable=False),
sa.Column("owner_user_id", sa.Text(), nullable=True),
sa.Column("permission", sa.Text(), nullable=False),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("is_active", sa.Boolean(), nullable=True, server_default=sa.text("true")),
sa.Column("created_at", sa.TIMESTAMP(timezone=True), nullable=True),
sa.Column("updated_at", sa.TIMESTAMP(timezone=True), nullable=True),
sa.Column("deactivated_at", sa.TIMESTAMP(timezone=True), nullable=True),
sa.PrimaryKeyConstraint("id"),
)
# auth_audit_logs 테이블 복원
if not _table_exists(inspector, "auth_audit_logs"):
op.create_table(
"auth_audit_logs",
sa.Column("id", sa.Text(), nullable=False),
sa.Column("event_type", sa.Text(), nullable=False),
sa.Column("action", sa.Text(), nullable=False),
sa.Column("actor_id", sa.Text(), nullable=True),
sa.Column("bot_id", sa.Text(), nullable=False),
sa.Column("token_id_masked", sa.Text(), nullable=True),
sa.Column("token_ref_masked", sa.Text(), nullable=True),
sa.Column("result", sa.Text(), nullable=True),
sa.Column("detail", sa.Text(), nullable=True),
sa.Column("payload", sa.JSON(), nullable=True),
sa.Column("occurred_at", sa.TIMESTAMP(timezone=True), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
# auth_tokens.bot_principal_id 컬럼 복원
inspector = inspect(bind)
if not _column_exists(inspector, "auth_tokens", "bot_principal_id"):
op.add_column("auth_tokens", sa.Column("bot_principal_id", sa.Text(), nullable=True))
# token_type 'service' → 'bot' 역방향 (구조 복원 목적)
bind.execute(
sa.text("UPDATE auth_tokens SET token_type = 'bot' WHERE token_type = 'service'")
)
# service token unique index 제거
inspector = inspect(bind)
if _index_exists(inspector, "auth_tokens", "idx_auth_tokens_service_name_unique"):
op.drop_index("idx_auth_tokens_service_name_unique", table_name="auth_tokens")