Files
shopify-ai-backup/chat/scripts/migrate-to-database.js
southseact-3d cb95a916ae Add database migration scripts and configuration files
- Add verify-migration.js script for testing database migrations
- Add database config module for centralized configuration
- Add chutes.txt prompt for system responses
- Update database implementation and testing documentation
- Add database migration and setup scripts
- Update session system and LLM tool configuration
- Update deployment checklist and environment example
- Update Dockerfile and docker-compose configuration
2026-02-20 12:38:43 +00:00

408 lines
14 KiB
JavaScript
Executable File

#!/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, encrypt } = require('../src/utils/encryption');
const { STATE_DIR, DB_PATH, KEY_FILE } = require('../src/database/config');
const DATABASE_PATH = DB_PATH;
const DATABASE_ENCRYPTION_KEY = process.env.DATABASE_ENCRYPTION_KEY;
const DATABASE_USE_SQLCIPHER = process.env.DATABASE_USE_SQLCIPHER !== '0' && process.env.DATABASE_USE_SQLCIPHER !== 'false';
const USERS_FILE = path.join(STATE_DIR, 'users.json');
const SESSIONS_FILE = path.join(STATE_DIR, 'user-sessions.json');
const AFFILIATES_FILE = path.join(STATE_DIR, '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;
const db = getDatabase();
const stmt = db.prepare(`
INSERT INTO users (
id, email, email_encrypted, name, name_encrypted, password_hash,
providers, email_verified, verification_token, verification_expires_at,
reset_token, reset_expires_at, plan, billing_status, billing_email,
payment_method_last4, subscription_renews_at, referred_by_affiliate_code,
affiliate_attribution_at, affiliate_payouts, two_factor_secret,
two_factor_enabled, created_at, updated_at, last_login_at, data
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET
email = excluded.email,
email_encrypted = excluded.email_encrypted,
name = excluded.name,
name_encrypted = excluded.name_encrypted,
password_hash = excluded.password_hash,
providers = excluded.providers,
email_verified = excluded.email_verified,
verification_token = excluded.verification_token,
verification_expires_at = excluded.verification_expires_at,
reset_token = excluded.reset_token,
reset_expires_at = excluded.reset_expires_at,
plan = excluded.plan,
billing_status = excluded.billing_status,
billing_email = excluded.billing_email,
payment_method_last4 = excluded.payment_method_last4,
subscription_renews_at = excluded.subscription_renews_at,
referred_by_affiliate_code = excluded.referred_by_affiliate_code,
affiliate_attribution_at = excluded.affiliate_attribution_at,
affiliate_payouts = excluded.affiliate_payouts,
two_factor_secret = excluded.two_factor_secret,
two_factor_enabled = excluded.two_factor_enabled,
created_at = excluded.created_at,
updated_at = excluded.updated_at,
last_login_at = excluded.last_login_at,
data = excluded.data
`);
for (const user of users) {
try {
const normalizedEmail = (user.email || '').trim().toLowerCase();
if (!normalizedEmail) {
console.log(' Skipping user without email');
failed++;
continue;
}
const passwordHash = user.passwordHash || user.password_hash || user.password || '';
const providers = Array.isArray(user.providers) ? user.providers : [];
const affiliatePayouts = Array.isArray(user.affiliatePayouts) ? user.affiliatePayouts : [];
const dataPayload = JSON.stringify({ ...user, email: normalizedEmail || user.email || '' });
const emailEncrypted = normalizedEmail ? encrypt(normalizedEmail) : '';
const nameEncrypted = user.name ? encrypt(user.name) : null;
const twoFactorEncrypted = user.twoFactorSecret ? encrypt(user.twoFactorSecret) : null;
stmt.run(
user.id,
normalizedEmail,
emailEncrypted,
user.name || null,
nameEncrypted,
passwordHash,
JSON.stringify(providers),
user.emailVerified ? 1 : 0,
user.verificationToken || null,
user.verificationExpiresAt || null,
user.resetToken || null,
user.resetExpiresAt || null,
user.plan || 'hobby',
user.billingStatus || 'active',
user.billingEmail || user.email || '',
user.paymentMethodLast4 || '',
user.subscriptionRenewsAt || null,
user.referredByAffiliateCode || null,
user.affiliateAttributionAt || null,
JSON.stringify(affiliatePayouts),
twoFactorEncrypted,
user.twoFactorEnabled ? 1 : 0,
user.createdAt || Date.now(),
user.updatedAt || user.createdAt || Date.now(),
user.lastLoginAt || null,
dataPayload
);
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 userExistsStmt = db.prepare('SELECT id FROM users WHERE id = ?');
const sessionStmt = db.prepare(`
INSERT INTO sessions (
id, user_id, token, refresh_token_hash, device_fingerprint,
ip_address, user_agent, expires_at, created_at, last_accessed_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET
user_id = excluded.user_id,
token = excluded.token,
expires_at = excluded.expires_at,
last_accessed_at = excluded.last_accessed_at
`);
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 = userExistsStmt.get(session.userId);
if (!user) {
console.log(` Skipping session for non-existent user: ${session.userId}`);
failed++;
continue;
}
sessionStmt.run(
token,
session.userId,
token,
null,
session.deviceFingerprint || null,
session.ipAddress || null,
session.userAgent || null,
session.expiresAt,
session.createdAt || now,
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();
const stmt = db.prepare(`
INSERT INTO affiliate_accounts (
id, email, name, password_hash, codes, earnings, commission_rate,
created_at, updated_at, last_login_at, last_payout_at,
email_verified, verification_token, verification_expires_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET
email = excluded.email,
name = excluded.name,
password_hash = excluded.password_hash,
codes = excluded.codes,
earnings = excluded.earnings,
commission_rate = excluded.commission_rate,
updated_at = excluded.updated_at,
last_login_at = excluded.last_login_at,
last_payout_at = excluded.last_payout_at,
email_verified = excluded.email_verified,
verification_token = excluded.verification_token,
verification_expires_at = excluded.verification_expires_at
`);
for (const affiliate of affiliates) {
try {
if (!affiliate.email) {
console.log(' Skipping affiliate without email');
failed++;
continue;
}
const affiliateEmail = (affiliate.email || '').trim().toLowerCase();
stmt.run(
affiliate.id || require('crypto').randomUUID(),
affiliateEmail,
affiliate.name || '',
affiliate.password || affiliate.passwordHash || '',
JSON.stringify(affiliate.codes || []),
JSON.stringify(affiliate.earnings || []),
affiliate.commissionRate || 0.15,
affiliate.createdAt || new Date().toISOString(),
affiliate.updatedAt || affiliate.createdAt || new Date().toISOString(),
affiliate.lastLoginAt || null,
affiliate.lastPayoutAt || null,
affiliate.emailVerified ? 1 : 0,
affiliate.verificationToken || null,
affiliate.verificationExpiresAt || null
);
console.log(` ✓ Migrated affiliate: ${affiliate.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', STATE_DIR);
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
let encryptionKey = DATABASE_ENCRYPTION_KEY;
if (!encryptionKey && KEY_FILE && fs.existsSync(KEY_FILE)) {
encryptionKey = fs.readFileSync(KEY_FILE, 'utf8').trim();
if (encryptionKey) {
process.env.DATABASE_ENCRYPTION_KEY = encryptionKey;
console.log('✅ Loaded encryption key from file');
}
}
if (!encryptionKey) {
console.error('❌ DATABASE_ENCRYPTION_KEY not set');
process.exit(1);
}
try {
initEncryption(encryptionKey);
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,
sqlcipherKey: DATABASE_USE_SQLCIPHER ? encryptionKey : null,
cipherCompatibility: process.env.DATABASE_CIPHER_COMPAT || 4,
kdfIter: process.env.DATABASE_KDF_ITER || 64000
});
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);
});