import { Pool, QueryResult } from 'pg'; // ================================================== // Coolify PostgreSQL Connection // ================================================== const DATABASE_URL = process.env.DATABASE_URL || process.env.POSTGRES_URL || 'postgresql://vibn_user:password@vibn-postgres:5432/vibn'; let pool: Pool | null = null; // Internal Docker network connections (Coolify) don't use SSL. // Only enable SSL for external/RDS/cloud DB connections. function getSslConfig() { const url = DATABASE_URL; if (!url) return undefined; // Internal Docker hostnames never use SSL if (url.includes('localhost') || url.includes('127.0.0.1') || /postgresql:\/\/[^@]+@[a-z0-9_-]+:\d+\//.test(url)) { return undefined; } // External cloud DBs (RDS, AlloyDB, etc.) need SSL if (process.env.DB_SSL === 'true') { return { rejectUnauthorized: false }; } return undefined; } export function getPool() { if (!pool) { pool = new Pool({ connectionString: DATABASE_URL, ssl: getSslConfig(), max: 20, idleTimeoutMillis: 30000, connectionTimeoutMillis: 2000, }); pool.on('error', (err) => { console.error('Unexpected error on idle client', err); }); } return pool; } export async function query(text: string, params?: any[]): Promise { const pool = getPool(); const result = await pool.query(text, params); return result.rows; } export async function queryOne(text: string, params?: any[]): Promise { const rows = await query(text, params); return rows[0] || null; } // ================================================== // User operations (replaces Firebase auth.ts) // ================================================== export interface User { id: number; uid: string; email: string; display_name?: string; photo_url?: string; workspace: string; created_at: Date; updated_at: Date; } export async function createUser(data: { email: string; password_hash?: string; display_name?: string; photo_url?: string; workspace: string; google_id?: string; github_id?: string; }): Promise { const uid = `user-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const result = await query(` INSERT INTO users (uid, email, password_hash, display_name, photo_url, workspace, google_id, github_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING * `, [uid, data.email, data.password_hash, data.display_name, data.photo_url, data.workspace, data.google_id, data.github_id]); return result[0]; } export async function getUserByEmail(email: string): Promise { return await queryOne('SELECT * FROM users WHERE email = $1', [email]); } export async function getUserByUid(uid: string): Promise { return await queryOne('SELECT * FROM users WHERE uid = $1', [uid]); } export async function getUserById(id: number): Promise { return await queryOne('SELECT * FROM users WHERE id = $1', [id]); } export async function updateUser(id: number, data: Partial): Promise { const fields = Object.keys(data).filter(k => k !== 'id' && k !== 'uid' && k !== 'created_at'); const values = fields.map((_, i) => `$${i + 2}`); const setClause = fields.map((f, i) => `${f} = ${values[i]}`).join(', '); const result = await query(` UPDATE users SET ${setClause}, updated_at = NOW() WHERE id = $1 RETURNING * `, [id, ...fields.map(f => (data as any)[f])]); return result[0] || null; } // ================================================== // Project operations (replaces Firebase collections.ts) // ================================================== export interface Project { id: number; firebase_id?: string; client_id?: number; user_id: number; name: string; slug: string; workspace: string; product_name: string; product_vision?: string; status: string; current_phase: string; phase_status: string; github_repo?: string; chatgpt_project_id?: string; gitea_repo_url?: string; coolify_app_id?: string; deployment_url?: string; phase_data: any; created_at: Date; updated_at: Date; } export async function createProject(data: Omit): Promise { const result = await query(` INSERT INTO projects ( user_id, name, slug, workspace, product_name, product_vision, status, current_phase, phase_status, phase_data ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING * `, [ data.user_id, data.name, data.slug, data.workspace, data.product_name, data.product_vision, data.status || 'active', data.current_phase || 'collection', data.phase_status || 'not_started', JSON.stringify(data.phase_data || {}) ]); return result[0]; } export async function getProject(id: number): Promise { return await queryOne('SELECT * FROM projects WHERE id = $1', [id]); } export async function getUserProjects(userId: number): Promise { return await query('SELECT * FROM projects WHERE user_id = $1 ORDER BY updated_at DESC', [userId]); } export async function updateProject(id: number, data: Partial): Promise { const fields = Object.keys(data).filter(k => !['id', 'created_at', 'updated_at'].includes(k)); const values = fields.map((_, i) => `$${i + 2}`); const setClause = fields.map((f, i) => `${f} = ${values[i]}`).join(', '); const fieldValues = fields.map(f => { const val = (data as any)[f]; return (f === 'phase_data' && typeof val === 'object') ? JSON.stringify(val) : val; }); const result = await query(` UPDATE projects SET ${setClause}, updated_at = NOW() WHERE id = $1 RETURNING * `, [id, ...fieldValues]); return result[0] || null; } // ================================================== // Session operations (replaces Firebase + Railway logging) // ================================================== export interface Session { id: number; session_id: string; project_id?: number; user_id: number; started_at: Date; ended_at?: Date; duration_minutes: number; workspace_path?: string; workspace_name?: string; conversation: any[]; total_tokens: number; estimated_cost_usd: number; model: string; summary?: string; } export async function createSession(data: { session_id: string; project_id?: number; user_id: number; workspace_path?: string; workspace_name?: string; model?: string; }): Promise { const result = await query(` INSERT INTO sessions (session_id, project_id, user_id, workspace_path, workspace_name, model) VALUES ($1, $2, $3, $4, $5, $6) RETURNING * `, [data.session_id, data.project_id, data.user_id, data.workspace_path, data.workspace_name, data.model || 'unknown']); return result[0]; } export async function getSession(sessionId: string): Promise { return await queryOne('SELECT * FROM sessions WHERE session_id = $1', [sessionId]); } export async function getProjectSessions(projectId: number): Promise { return await query( 'SELECT * FROM sessions WHERE project_id = $1 ORDER BY started_at DESC', [projectId] ); } export async function updateSession(sessionId: string, data: { conversation?: any[]; total_tokens?: number; estimated_cost_usd?: number; ended_at?: Date; duration_minutes?: number; summary?: string; }): Promise { const fields = Object.keys(data); const values = fields.map((_, i) => `$${i + 2}`); const setClause = fields.map((f, i) => `${f} = ${values[i]}`).join(', '); const fieldValues = fields.map(f => { const val = (data as any)[f]; return (f === 'conversation' && typeof val === 'object') ? JSON.stringify(val) : val; }); const result = await query(` UPDATE sessions SET ${setClause}, last_updated = NOW() WHERE session_id = $1 RETURNING * `, [sessionId, ...fieldValues]); return result[0] || null; } // ================================================== // Analysis operations // ================================================== export interface Analysis { id: number; project_id: number; type: string; summary: string; tech_stack: string[]; features: string[]; raw_data: any; created_at: Date; } export async function createAnalysis(data: Omit): Promise { const result = await query(` INSERT INTO analyses (project_id, type, summary, tech_stack, features, raw_data) VALUES ($1, $2, $3, $4, $5, $6) RETURNING * `, [ data.project_id, data.type, data.summary, JSON.stringify(data.tech_stack), JSON.stringify(data.features), JSON.stringify(data.raw_data) ]); return result[0]; } export async function getProjectAnalyses(projectId: number): Promise { return await query( 'SELECT * FROM analyses WHERE project_id = $1 ORDER BY created_at DESC', [projectId] ); } // ================================================== // Health check // ================================================== export async function checkConnection(): Promise { try { await query('SELECT 1'); return true; } catch (error) { console.error('Database connection failed:', error); return false; } }