Files
awoooi/apps/api/scripts/run_migration.py
Your Name 8c4dc7a5a8
Some checks failed
Code Review / ai-code-review (push) Successful in 10s
CD Pipeline / tests (push) Successful in 1m5s
CD Pipeline / build-and-deploy (push) Failing after 10m6s
CD Pipeline / post-deploy-checks (push) Has been skipped
chore(rls): 新增 manual script gate 與 canary wave1
2026-05-12 20:23:27 +08:00

139 lines
4.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Phase 18 AuditLog Migration Script
===================================
執行 Phase 18 新增字段的數據庫遷移
使用方式:
cd apps/api && python scripts/run_migration.py
"""
import asyncio
import os
from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine
# 2026-04-22 ogt: 移除硬碼 changeme改為讀取環境變數強制要求設定
# 執行前: 從 secret manager / operator vault 設定 DATABASE_URL禁止在指令或檔案中寫入 URL。
DATABASE_URL = os.environ["DATABASE_URL"]
MIGRATION_SQLS = [
# 1. authorization_channel
"""
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'audit_logs' AND column_name = 'authorization_channel'
) THEN
ALTER TABLE audit_logs ADD COLUMN authorization_channel VARCHAR(20);
END IF;
END $$;
""",
# 2. retry_count
"""
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'audit_logs' AND column_name = 'retry_count'
) THEN
ALTER TABLE audit_logs ADD COLUMN retry_count INTEGER DEFAULT 0 NOT NULL;
END IF;
END $$;
""",
# 3. failure_classification
"""
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'audit_logs' AND column_name = 'failure_classification'
) THEN
ALTER TABLE audit_logs ADD COLUMN failure_classification VARCHAR(50);
END IF;
END $$;
""",
# 4. source_approval_id
"""
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'audit_logs' AND column_name = 'source_approval_id'
) THEN
ALTER TABLE audit_logs ADD COLUMN source_approval_id VARCHAR(36);
END IF;
END $$;
""",
# 5. auto_repair_attempted
"""
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'audit_logs' AND column_name = 'auto_repair_attempted'
) THEN
ALTER TABLE audit_logs ADD COLUMN auto_repair_attempted BOOLEAN DEFAULT FALSE NOT NULL;
END IF;
END $$;
""",
# 6. auto_repair_result
"""
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'audit_logs' AND column_name = 'auto_repair_result'
) THEN
ALTER TABLE audit_logs ADD COLUMN auto_repair_result TEXT;
END IF;
END $$;
""",
# 創建索引
"CREATE INDEX IF NOT EXISTS ix_audit_authorization_channel ON audit_logs(authorization_channel);",
"CREATE INDEX IF NOT EXISTS ix_audit_failure_classification ON audit_logs(failure_classification);",
"CREATE INDEX IF NOT EXISTS ix_audit_source_approval_id ON audit_logs(source_approval_id);",
]
async def run_migration():
"""執行遷移"""
print("=" * 60)
print("Phase 18 AuditLog Migration")
print("=" * 60)
engine = create_async_engine(DATABASE_URL, echo=False)
async with engine.begin() as conn:
# 執行遷移
for i, sql in enumerate(MIGRATION_SQLS, 1):
try:
await conn.execute(text(sql))
print(f"✅ Step {i}/{len(MIGRATION_SQLS)} completed")
except Exception as e:
print(f"❌ Step {i} failed: {e}")
# 驗證結果
print("\n" + "=" * 60)
print("驗證欄位:")
print("=" * 60)
result = await conn.execute(text("""
SELECT column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_name = 'audit_logs'
ORDER BY ordinal_position
"""))
for row in result:
print(f" {row[0]}: {row[1]} (nullable={row[2]}, default={row[3]})")
await engine.dispose()
print("\n✅ Migration completed!")
if __name__ == "__main__":
asyncio.run(run_migration())