VIBN Frontend for Coolify deployment
This commit is contained in:
373
scripts/migrate-from-postgres.ts
Normal file
373
scripts/migrate-from-postgres.ts
Normal file
@@ -0,0 +1,373 @@
|
||||
// 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);
|
||||
|
||||
Reference in New Issue
Block a user