#!/usr/bin/env node /** * Script de migracao SQLite -> PostgreSQL * Executa com acesso ao SQLite e PostgreSQL para migrar todos os dados * * Uso: * SQLITE_PATH=/path/to/db.sqlite POSTGRES_URL=postgresql://... node scripts/migrate-sqlite-to-postgres.mjs */ import Database from "better-sqlite3" import pg from "pg" const SQLITE_PATH = process.env.SQLITE_PATH || "/app/data/db.sqlite" const POSTGRES_URL = process.env.POSTGRES_URL || process.env.DATABASE_URL if (!POSTGRES_URL) { console.error("ERRO: POSTGRES_URL ou DATABASE_URL e obrigatorio") process.exit(1) } console.log("=".repeat(60)) console.log("MIGRACAO SQLite -> PostgreSQL") console.log("=".repeat(60)) console.log(`SQLite: ${SQLITE_PATH}`) console.log(`PostgreSQL: ${POSTGRES_URL.replace(/:[^:@]+@/, ":***@")}`) console.log("") // Ordem das tabelas respeitando foreign keys const TABLES_ORDER = [ // Tabelas sem dependencias "Team", "SlaPolicy", "Company", // Tabelas com dependencias simples "User", "Queue", "AuthUser", // Tabelas com chave composta "TeamMember", // Tabelas principais "Ticket", // Tabelas dependentes de Ticket "TicketEvent", "TicketComment", "TicketRating", "TicketAccessToken", // Tabelas de usuario "NotificationPreferences", // Tabelas de relatorios "ReportExportSchedule", "ReportExportRun", // Tabelas de autenticacao "AuthSession", "AuthAccount", "AuthInvite", "AuthInviteEvent", "AuthVerification", ] let sqlite let pgClient async function connect() { console.log("[1/4] Conectando aos bancos de dados...") try { sqlite = new Database(SQLITE_PATH, { readonly: true }) console.log(" SQLite: conectado") } catch (error) { console.error(" SQLite: ERRO -", error.message) process.exit(1) } try { pgClient = new pg.Client({ connectionString: POSTGRES_URL }) await pgClient.connect() console.log(" PostgreSQL: conectado") } catch (error) { console.error(" PostgreSQL: ERRO -", error.message) process.exit(1) } } async function migrateTable(tableName) { let rows try { rows = sqlite.prepare(`SELECT * FROM "${tableName}"`).all() } catch (error) { console.log(` ${tableName}: tabela nao existe no SQLite, pulando`) return { table: tableName, migrated: 0, skipped: true } } if (rows.length === 0) { console.log(` ${tableName}: 0 registros`) return { table: tableName, migrated: 0 } } const columns = Object.keys(rows[0]) const quotedColumns = columns.map(c => `"${c}"`).join(", ") const placeholders = columns.map((_, i) => `$${i + 1}`).join(", ") const insertSql = `INSERT INTO "${tableName}" (${quotedColumns}) VALUES (${placeholders}) ON CONFLICT DO NOTHING` let migrated = 0 let errors = 0 for (const row of rows) { const values = columns.map(col => { let val = row[col] // Converter JSON string para objeto se necessario (PostgreSQL usa JSONB) if (typeof val === "string") { // Detectar campos JSON pelo conteudo if ((val.startsWith("{") && val.endsWith("}")) || (val.startsWith("[") && val.endsWith("]"))) { try { val = JSON.parse(val) } catch { // Manter como string se nao for JSON valido } } } // SQLite usa 0/1 para boolean, PostgreSQL usa true/false // Prisma ja trata isso, mas vamos garantir if (val === 0 || val === 1) { // Detectar campos boolean pelo nome const booleanFields = ["emailVerified", "isLead", "isAvulso", "emailEnabled", "hasBranches", "privacyPolicyAccepted"] if (booleanFields.includes(col)) { val = val === 1 } } return val }) try { await pgClient.query(insertSql, values) migrated++ } catch (error) { errors++ if (errors <= 3) { console.error(` Erro ao inserir em ${tableName}:`, error.message) } } } console.log(` ${tableName}: ${migrated}/${rows.length} registros migrados${errors > 0 ? ` (${errors} erros)` : ""}`) return { table: tableName, migrated, total: rows.length, errors } } async function updateSequences() { console.log("\n[3/4] Atualizando sequences do PostgreSQL...") // PostgreSQL precisa atualizar sequences para auto-increment funcionar corretamente // Mas como usamos CUIDs, isso geralmente nao e necessario // Vamos apenas verificar se ha sequences e atualiza-las se existirem const sequenceQuery = ` SELECT t.relname as table_name, a.attname as column_name, pg_get_serial_sequence(t.relname::text, a.attname::text) as sequence_name FROM pg_class t JOIN pg_attribute a ON a.attrelid = t.oid WHERE t.relkind = 'r' AND pg_get_serial_sequence(t.relname::text, a.attname::text) IS NOT NULL ` try { const result = await pgClient.query(sequenceQuery) for (const row of result.rows) { const updateSeq = ` SELECT setval('${row.sequence_name}', COALESCE((SELECT MAX("${row.column_name}") FROM "${row.table_name}"), 0) + 1, false) ` try { await pgClient.query(updateSeq) console.log(` Sequence ${row.sequence_name} atualizada`) } catch { // Ignorar erros de sequence } } } catch { console.log(" Nenhuma sequence encontrada (usando CUIDs)") } } async function validateMigration() { console.log("\n[4/4] Validando migracao...") const validation = [] for (const tableName of TABLES_ORDER) { let sqliteCount = 0 let pgCount = 0 try { const sqliteResult = sqlite.prepare(`SELECT COUNT(*) as count FROM "${tableName}"`).get() sqliteCount = sqliteResult?.count || 0 } catch { // Tabela nao existe no SQLite continue } try { const pgResult = await pgClient.query(`SELECT COUNT(*) as count FROM "${tableName}"`) pgCount = parseInt(pgResult.rows[0]?.count || 0) } catch { pgCount = 0 } const match = sqliteCount === pgCount validation.push({ table: tableName, sqlite: sqliteCount, postgres: pgCount, match }) const status = match ? "OK" : "DIFF" console.log(` ${tableName}: SQLite=${sqliteCount}, PostgreSQL=${pgCount} [${status}]`) } const allMatch = validation.every(v => v.match) return allMatch } async function main() { await connect() console.log("\n[2/4] Migrando tabelas...") try { await pgClient.query("BEGIN") for (const tableName of TABLES_ORDER) { await migrateTable(tableName) } await pgClient.query("COMMIT") console.log("\n Transacao commitada com sucesso!") } catch (error) { await pgClient.query("ROLLBACK") console.error("\nERRO: Rollback executado -", error.message) process.exit(1) } await updateSequences() const valid = await validateMigration() console.log("\n" + "=".repeat(60)) if (valid) { console.log("MIGRACAO CONCLUIDA COM SUCESSO!") } else { console.log("MIGRACAO CONCLUIDA COM DIFERENCAS (verifique os logs)") } console.log("=".repeat(60)) sqlite.close() await pgClient.end() process.exit(valid ? 0 : 1) } main().catch(error => { console.error("Erro fatal:", error) process.exit(1) })