#!/usr/bin/env node /** * Migration script - Migrate data from JSON files to database */ const fs = require('fs'); const path = require('path'); const { initDatabase, getDatabase, closeDatabase } = require('../src/database/connection'); const { initEncryption } = require('../src/utils/encryption'); const userRepo = require('../src/repositories/userRepository'); const DATA_ROOT = process.env.CHAT_DATA_ROOT || path.join(__dirname, '..', '.data'); const DATABASE_PATH = process.env.DATABASE_PATH || path.join(DATA_ROOT, 'shopify_ai.db'); const DATABASE_ENCRYPTION_KEY = process.env.DATABASE_ENCRYPTION_KEY; const USERS_FILE = path.join(DATA_ROOT, 'users.json'); const SESSIONS_FILE = path.join(DATA_ROOT, 'user-sessions.json'); const AFFILIATES_FILE = path.join(DATA_ROOT, 'affiliates.json'); async function loadJsonFile(filePath, defaultValue = []) { try { const data = fs.readFileSync(filePath, 'utf8'); return JSON.parse(data); } catch (error) { if (error.code === 'ENOENT') { console.log(` File not found: ${filePath}, using default`); return defaultValue; } throw error; } } async function migrateUsers() { console.log('\nšŸ“¦ Migrating users...'); const users = await loadJsonFile(USERS_FILE, []); console.log(` Found ${users.length} users in JSON`); if (users.length === 0) { console.log(' No users to migrate'); return { success: 0, failed: 0 }; } let success = 0; let failed = 0; for (const user of users) { try { // Check if user already exists const existing = userRepo.getUserById(user.id); if (existing) { console.log(` Skipping existing user: ${user.email}`); success++; continue; } // Create user in database userRepo.createUser({ id: user.id, email: user.email, name: user.name || null, passwordHash: user.passwordHash || user.password_hash, providers: user.providers || [], emailVerified: user.emailVerified, verificationToken: user.verificationToken || null, verificationExpiresAt: user.verificationExpiresAt || null, plan: user.plan || 'hobby', billingStatus: user.billingStatus || 'active', billingEmail: user.billingEmail || user.email }); console.log(` āœ“ Migrated user: ${user.email}`); success++; } catch (error) { console.error(` āœ— Failed to migrate user ${user.email}:`, error.message); failed++; } } console.log(` Completed: ${success} success, ${failed} failed`); return { success, failed }; } async function migrateSessions() { console.log('\nšŸ“¦ Migrating sessions...'); const sessions = await loadJsonFile(SESSIONS_FILE, {}); const sessionCount = Object.keys(sessions).length; console.log(` Found ${sessionCount} sessions in JSON`); if (sessionCount === 0) { console.log(' No sessions to migrate'); return { success: 0, failed: 0, expired: 0 }; } const now = Date.now(); let success = 0; let failed = 0; let expired = 0; const db = getDatabase(); const sessionRepo = require('../src/repositories/sessionRepository'); for (const [token, session] of Object.entries(sessions)) { try { // Skip expired sessions if (session.expiresAt && session.expiresAt <= now) { expired++; continue; } // Check if user exists const user = userRepo.getUserById(session.userId); if (!user) { console.log(` Skipping session for non-existent user: ${session.userId}`); failed++; continue; } // Create session in database sessionRepo.createSession({ id: session.id || require('crypto').randomUUID(), userId: session.userId, token: token, deviceFingerprint: session.deviceFingerprint || null, ipAddress: session.ipAddress || null, userAgent: session.userAgent || null, expiresAt: session.expiresAt, createdAt: session.createdAt || now, lastAccessedAt: session.lastAccessedAt || now }); success++; } catch (error) { console.error(` āœ— Failed to migrate session:`, error.message); failed++; } } console.log(` Completed: ${success} success, ${failed} failed, ${expired} expired`); return { success, failed, expired }; } async function migrateAffiliates() { console.log('\nšŸ“¦ Migrating affiliates...'); const affiliates = await loadJsonFile(AFFILIATES_FILE, []); console.log(` Found ${affiliates.length} affiliates in JSON`); if (affiliates.length === 0) { console.log(' No affiliates to migrate'); return { success: 0, failed: 0 }; } let success = 0; let failed = 0; const db = getDatabase(); for (const affiliate of affiliates) { try { // Check if user exists const user = userRepo.getUserById(affiliate.userId); if (!user) { console.log(` Skipping affiliate for non-existent user: ${affiliate.userId}`); failed++; continue; } // Insert affiliate const stmt = db.prepare(` INSERT INTO affiliates ( id, user_id, codes, earnings, commission_rate, total_referrals, total_earnings_cents, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `); stmt.run( affiliate.id || require('crypto').randomUUID(), affiliate.userId, JSON.stringify(affiliate.codes || []), JSON.stringify(affiliate.earnings || []), affiliate.commissionRate || 0.15, affiliate.totalReferrals || 0, affiliate.totalEarningsCents || 0, affiliate.createdAt || Date.now(), affiliate.updatedAt || Date.now() ); console.log(` āœ“ Migrated affiliate for user: ${user.email}`); success++; } catch (error) { console.error(` āœ— Failed to migrate affiliate:`, error.message); failed++; } } console.log(` Completed: ${success} success, ${failed} failed`); return { success, failed }; } async function createBackup() { console.log('\nšŸ’¾ Creating backup of JSON files...'); const backupDir = path.join(DATA_ROOT, `migration_backup_${Date.now()}`); if (!fs.existsSync(backupDir)) { fs.mkdirSync(backupDir, { recursive: true }); } const files = [USERS_FILE, SESSIONS_FILE, AFFILIATES_FILE]; let backedUp = 0; for (const file of files) { if (fs.existsSync(file)) { const fileName = path.basename(file); const backupPath = path.join(backupDir, fileName); fs.copyFileSync(file, backupPath); console.log(` āœ“ Backed up: ${fileName}`); backedUp++; } } console.log(` Created backup in: ${backupDir}`); console.log(` Backed up ${backedUp} files`); return backupDir; } async function runMigration() { console.log('šŸ”„ Starting database migration...'); console.log(' Source: JSON files in', DATA_ROOT); console.log(' Target: Database at', DATABASE_PATH); // Check if database exists if (!fs.existsSync(DATABASE_PATH)) { console.error('āŒ Database not found. Please run setup-database.js first.'); process.exit(1); } // Initialize encryption if (!DATABASE_ENCRYPTION_KEY) { console.error('āŒ DATABASE_ENCRYPTION_KEY not set'); process.exit(1); } try { initEncryption(DATABASE_ENCRYPTION_KEY); console.log('āœ… Encryption initialized'); } catch (error) { console.error('āŒ Failed to initialize encryption:', error.message); process.exit(1); } // Initialize database try { initDatabase(DATABASE_PATH, { verbose: false }); console.log('āœ… Database connected'); } catch (error) { console.error('āŒ Failed to connect to database:', error.message); process.exit(1); } // Create backup const backupDir = await createBackup(); // Run migrations const results = { users: await migrateUsers(), sessions: await migrateSessions(), affiliates: await migrateAffiliates() }; // Close database closeDatabase(); // Print summary console.log('\nšŸ“Š Migration Summary:'); console.log(' Users:'); console.log(` āœ“ Success: ${results.users.success}`); console.log(` āœ— Failed: ${results.users.failed}`); console.log(' Sessions:'); console.log(` āœ“ Success: ${results.sessions.success}`); console.log(` āœ— Failed: ${results.sessions.failed}`); console.log(` ā° Expired: ${results.sessions.expired}`); console.log(' Affiliates:'); console.log(` āœ“ Success: ${results.affiliates.success}`); console.log(` āœ— Failed: ${results.affiliates.failed}`); const totalSuccess = results.users.success + results.sessions.success + results.affiliates.success; const totalFailed = results.users.failed + results.sessions.failed + results.affiliates.failed; console.log('\n Total:'); console.log(` āœ“ Success: ${totalSuccess}`); console.log(` āœ— Failed: ${totalFailed}`); console.log('\nāœ… Migration complete!'); console.log(` Backup created in: ${backupDir}`); console.log('\nNext steps:'); console.log(' 1. Verify migration: node scripts/verify-migration.js'); console.log(' 2. Test the application with: USE_JSON_DATABASE=1 npm start'); console.log(' 3. Switch to database mode: unset USE_JSON_DATABASE && npm start'); } // Run migration runMigration().catch(error => { console.error('āŒ Migration failed:', error); closeDatabase(); process.exit(1); });