diff --git a/scripts/migrate-conversations.ts b/scripts/migrate-conversations.ts new file mode 100644 index 0000000..151a7f1 --- /dev/null +++ b/scripts/migrate-conversations.ts @@ -0,0 +1,112 @@ +/** + * 기존 message_buffer 데이터를 새 conversation 테이블로 마이그레이션 + * + * 사용법: + * 1. Worker 내부에서 1회성 호출 + * 2. 또는 wrangler dev에서 수동 트리거 + */ + +import type { Env } from '../src/types'; + +export async function migrateExistingConversations(env: Env): Promise<{ + migrated_users: number; + migrated_messages: number; + errors: string[]; +}> { + const db = env.DB; + const result = { + migrated_users: 0, + migrated_messages: 0, + errors: [] as string[], + }; + + // 1. 기존 message_buffer에서 사용자 목록 조회 + const { results: users } = await db + .prepare(` + SELECT DISTINCT u.telegram_id, mb.user_id + FROM message_buffer mb + JOIN users u ON mb.user_id = u.id + `) + .all<{ telegram_id: string; user_id: number }>(); + + if (!users || users.length === 0) { + console.log('마이그레이션 대상 없음'); + return result; + } + + console.log(`마이그레이션 대상 사용자: ${users.length}명`); + + // 2. 각 사용자별 마이그레이션 + for (const user of users) { + try { + const tableName = `conv_${user.telegram_id}`; + + // 테이블 생성 + await db.exec(` + CREATE TABLE IF NOT EXISTS ${tableName} ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + role TEXT NOT NULL CHECK(role IN ('user', 'assistant')), + content TEXT NOT NULL, + tool_calls TEXT, + tool_results TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + ) + `); + + await db.exec(` + CREATE INDEX IF NOT EXISTS idx_${tableName}_created ON ${tableName}(created_at DESC) + `); + + // 메시지 복사 + const { results: messages } = await db + .prepare(` + SELECT role, message as content, created_at + FROM message_buffer + WHERE user_id = ? + ORDER BY created_at ASC + `) + .bind(user.user_id) + .all<{ role: string; content: string; created_at: string }>(); + + if (messages && messages.length > 0) { + for (const msg of messages) { + // role 변환: bot -> assistant + const role = msg.role === 'bot' ? 'assistant' : 'user'; + await db + .prepare(`INSERT INTO ${tableName} (role, content, created_at) VALUES (?, ?, ?)`) + .bind(role, msg.content, msg.created_at) + .run(); + } + + result.migrated_messages += messages.length; + } + + // 메타 테이블에 등록 (이미 존재하면 업데이트) + await db + .prepare(` + INSERT INTO conversation_tables (telegram_id, table_name, message_count, last_message_at) + VALUES (?, ?, ?, (SELECT MAX(created_at) FROM ${tableName})) + ON CONFLICT(telegram_id) DO UPDATE SET + message_count = excluded.message_count, + last_message_at = excluded.last_message_at + `) + .bind(user.telegram_id, tableName, messages?.length || 0) + .run(); + + result.migrated_users++; + console.log(`✓ ${user.telegram_id}: ${messages?.length || 0}개 메시지 마이그레이션 완료`); + + } catch (error) { + const errMsg = error instanceof Error ? error.message : String(error); + result.errors.push(`${user.telegram_id}: ${errMsg}`); + console.error(`✗ ${user.telegram_id}: 마이그레이션 실패 - ${errMsg}`); + } + } + + console.log('\n=== 마이그레이션 결과 ==='); + console.log(`사용자: ${result.migrated_users}명`); + console.log(`메시지: ${result.migrated_messages}개`); + console.log(`에러: ${result.errors.length}건`); + + return result; +}