Files
vibn-frontend/scripts/migrate-from-postgres.ts

374 lines
12 KiB
TypeScript

// MUST load environment variables BEFORE any other imports
require('dotenv').config({ path: require('path').resolve(__dirname, '../.env.local') });
import { Client } from 'pg';
import admin from 'firebase-admin';
import { FieldValue } from 'firebase-admin/firestore';
const PG_CONNECTION_STRING = 'postgresql://postgres:jhsRNOIyjjVfrdvDXnUVcXXXsuzjvcFc@metro.proxy.rlwy.net:30866/railway';
// Initialize Firebase Admin directly
if (!admin.apps.length) {
const privateKey = process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n');
if (!process.env.FIREBASE_PROJECT_ID || !process.env.FIREBASE_CLIENT_EMAIL || !privateKey) {
throw new Error('Missing Firebase Admin credentials. Check your .env.local file.');
}
admin.initializeApp({
credential: admin.credential.cert({
projectId: process.env.FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
privateKey: privateKey,
}),
});
console.log('✅ Firebase Admin initialized successfully');
}
const adminDb = admin.firestore();
const adminAuth = admin.auth();
interface PgUser {
id: number;
email: string;
name: string;
created_at: Date;
settings: any;
}
interface PgProject {
id: number;
client_id: number;
name: string;
workspace_path: string;
status: string;
created_at: Date;
updated_at: Date;
metadata: any;
}
interface PgSession {
id: number;
session_id: string;
project_id: number;
user_id: number;
started_at: Date;
last_updated: Date;
ended_at: Date | null;
status: string;
conversation: any[];
file_changes: any[];
message_count: number;
user_message_count: number;
assistant_message_count: number;
file_change_count: number;
duration_minutes: number;
summary: string | null;
tasks_identified: any[];
decisions_made: any[];
technologies_used: any[];
metadata: any;
total_tokens: number;
prompt_tokens: number;
completion_tokens: number;
estimated_cost_usd: number;
model: string;
}
interface PgWorkCompleted {
id: number;
project_id: number;
session_id: number;
title: string;
description: string;
completed_at: Date;
files_modified: any[];
lines_added: number;
lines_removed: number;
metadata: any;
}
interface PgClient {
id: number;
owner_user_id: number;
name: string;
email: string | null;
created_at: Date;
metadata: any;
}
async function migrateUsers(pgClient: Client, userMapping: Map<number, string>) {
console.log('\n📋 Migrating Users...');
const result = await pgClient.query<PgUser>('SELECT * FROM users');
for (const pgUser of result.rows) {
try {
// Create Firebase Auth user
let firebaseUser;
try {
firebaseUser = await adminAuth.getUserByEmail(pgUser.email);
console.log(` ✅ User already exists: ${pgUser.email}`);
} catch {
// User doesn't exist, create them
firebaseUser = await adminAuth.createUser({
email: pgUser.email,
displayName: pgUser.name,
emailVerified: true,
});
console.log(` ✨ Created Firebase Auth user: ${pgUser.email}`);
}
// Store mapping
userMapping.set(pgUser.id, firebaseUser.uid);
// Create user document in Firestore
const workspace = pgUser.email.split('@')[0].replace(/[^a-z0-9]/gi, '-').toLowerCase();
await adminDb.collection('users').doc(firebaseUser.uid).set({
uid: firebaseUser.uid,
email: pgUser.email,
displayName: pgUser.name,
workspace: workspace,
settings: pgUser.settings || {},
createdAt: FieldValue.serverTimestamp(),
updatedAt: FieldValue.serverTimestamp(),
migratedFrom: 'postgresql',
originalPgId: pgUser.id,
});
console.log(` ✅ Migrated user: ${pgUser.email}${firebaseUser.uid}`);
} catch (error) {
console.error(` ❌ Error migrating user ${pgUser.email}:`, error);
}
}
}
async function migrateClients(pgClient: Client, userMapping: Map<number, string>) {
console.log('\n📋 Migrating Clients...');
const result = await pgClient.query<PgClient>('SELECT * FROM clients');
for (const pgClient of result.rows) {
const firebaseUserId = userMapping.get(pgClient.owner_user_id);
if (!firebaseUserId) {
console.log(` ⚠️ Skipping client ${pgClient.name} - user not found`);
continue;
}
try {
const clientRef = adminDb.collection('clients').doc();
await clientRef.set({
id: clientRef.id,
ownerId: firebaseUserId,
name: pgClient.name,
email: pgClient.email || null,
createdAt: FieldValue.serverTimestamp(),
metadata: pgClient.metadata || {},
migratedFrom: 'postgresql',
originalPgId: pgClient.id,
});
console.log(` ✅ Migrated client: ${pgClient.name}`);
} catch (error) {
console.error(` ❌ Error migrating client ${pgClient.name}:`, error);
}
}
}
async function migrateProjects(pgClient: Client, userMapping: Map<number, string>, projectMapping: Map<number, string>) {
console.log('\n📋 Migrating Projects...');
const result = await pgClient.query<PgProject>('SELECT * FROM projects');
for (const pgProject of result.rows) {
try {
// Get the client to find the owner
const clientResult = await pgClient.query('SELECT owner_user_id FROM clients WHERE id = $1', [pgProject.client_id]);
const firebaseUserId = userMapping.get(clientResult.rows[0]?.owner_user_id);
if (!firebaseUserId) {
console.log(` ⚠️ Skipping project ${pgProject.name} - user not found`);
continue;
}
// Get user's workspace
const userDoc = await adminDb.collection('users').doc(firebaseUserId).get();
const workspace = userDoc.data()?.workspace || 'default-workspace';
const projectRef = adminDb.collection('projects').doc();
await projectRef.set({
id: projectRef.id,
name: pgProject.name,
slug: pgProject.name.toLowerCase().replace(/[^a-z0-9]/g, '-'),
userId: firebaseUserId,
workspace: workspace,
productName: pgProject.name,
productVision: pgProject.metadata?.vision || null,
workspacePath: pgProject.workspace_path,
status: pgProject.status,
isForClient: true,
hasLogo: false,
hasDomain: false,
hasWebsite: false,
hasGithub: false,
hasChatGPT: false,
createdAt: FieldValue.serverTimestamp(),
updatedAt: FieldValue.serverTimestamp(),
metadata: pgProject.metadata || {},
migratedFrom: 'postgresql',
originalPgId: pgProject.id,
});
projectMapping.set(pgProject.id, projectRef.id);
console.log(` ✅ Migrated project: ${pgProject.name}${projectRef.id}`);
} catch (error) {
console.error(` ❌ Error migrating project ${pgProject.name}:`, error);
}
}
}
async function migrateSessions(pgClient: Client, userMapping: Map<number, string>, projectMapping: Map<number, string>) {
console.log('\n📋 Migrating Sessions...');
const result = await pgClient.query<PgSession>('SELECT * FROM sessions ORDER BY started_at');
for (const pgSession of result.rows) {
try {
const firebaseUserId = userMapping.get(pgSession.user_id);
const firebaseProjectId = projectMapping.get(pgSession.project_id);
if (!firebaseUserId) {
console.log(` ⚠️ Skipping session ${pgSession.session_id} - user not found`);
continue;
}
const sessionRef = adminDb.collection('sessions').doc();
await sessionRef.set({
id: sessionRef.id,
userId: firebaseUserId,
projectId: firebaseProjectId || null,
// Session data
startTime: pgSession.started_at,
endTime: pgSession.ended_at || null,
duration: pgSession.duration_minutes * 60, // Convert to seconds
// Project context
workspacePath: null, // Not in old schema
workspaceName: null,
// AI usage
model: pgSession.model,
tokensUsed: pgSession.total_tokens,
promptTokens: pgSession.prompt_tokens,
completionTokens: pgSession.completion_tokens,
cost: parseFloat(String(pgSession.estimated_cost_usd)),
// Context
filesModified: pgSession.file_changes.map((fc: any) => fc.path || fc.file),
conversationSummary: pgSession.summary || null,
conversation: pgSession.conversation || [],
// Additional data from old schema
messageCount: pgSession.message_count,
userMessageCount: pgSession.user_message_count,
assistantMessageCount: pgSession.assistant_message_count,
fileChangeCount: pgSession.file_change_count,
tasksIdentified: pgSession.tasks_identified || [],
decisionsMade: pgSession.decisions_made || [],
technologiesUsed: pgSession.technologies_used || [],
status: pgSession.status,
metadata: pgSession.metadata || {},
createdAt: pgSession.started_at,
updatedAt: pgSession.last_updated,
migratedFrom: 'postgresql',
originalPgId: pgSession.id,
originalSessionId: pgSession.session_id,
});
console.log(` ✅ Migrated session: ${pgSession.session_id}`);
} catch (error) {
console.error(` ❌ Error migrating session ${pgSession.session_id}:`, error);
}
}
}
async function migrateWorkCompleted(pgClient: Client, projectMapping: Map<number, string>) {
console.log('\n📋 Migrating Work Completed...');
const result = await pgClient.query<PgWorkCompleted>('SELECT * FROM work_completed ORDER BY completed_at');
for (const work of result.rows) {
try {
const firebaseProjectId = projectMapping.get(work.project_id);
if (!firebaseProjectId) {
console.log(` ⚠️ Skipping work ${work.title} - project not found`);
continue;
}
const workRef = adminDb.collection('workCompleted').doc();
await workRef.set({
id: workRef.id,
projectId: firebaseProjectId,
sessionId: work.session_id ? `pg-session-${work.session_id}` : null,
title: work.title,
description: work.description,
completedAt: work.completed_at,
filesModified: work.files_modified || [],
linesAdded: work.lines_added || 0,
linesRemoved: work.lines_removed || 0,
metadata: work.metadata || {},
createdAt: work.completed_at,
migratedFrom: 'postgresql',
originalPgId: work.id,
});
console.log(` ✅ Migrated work: ${work.title}`);
} catch (error) {
console.error(` ❌ Error migrating work ${work.title}:`, error);
}
}
}
async function main() {
console.log('🚀 Starting PostgreSQL to Firebase migration...\n');
const pgClient = new Client({
connectionString: PG_CONNECTION_STRING,
});
try {
// Connect to PostgreSQL
console.log('📡 Connecting to PostgreSQL...');
await pgClient.connect();
console.log('✅ Connected to PostgreSQL\n');
// Mappings to track old ID -> new ID
const userMapping = new Map<number, string>();
const projectMapping = new Map<number, string>();
// Migrate in order (respecting foreign keys)
await migrateUsers(pgClient, userMapping);
await migrateClients(pgClient, userMapping);
await migrateProjects(pgClient, userMapping, projectMapping);
await migrateSessions(pgClient, userMapping, projectMapping);
await migrateWorkCompleted(pgClient, projectMapping);
console.log('\n✅ Migration completed successfully!');
console.log('\n📊 Summary:');
console.log(` - Users migrated: ${userMapping.size}`);
console.log(` - Projects migrated: ${projectMapping.size}`);
} catch (error) {
console.error('\n❌ Migration failed:', error);
throw error;
} finally {
await pgClient.end();
console.log('\n📡 Disconnected from PostgreSQL');
}
}
// Run migration
main().catch(console.error);