import pg from 'pg'; const { Pool } = pg; // Configuration from environment variables const config = { host: process.env.DB_HOST || 'localhost', port: parseInt(process.env.DB_PORT || '5432'), database: process.env.DB_NAME || 'agiliton', user: process.env.DB_USER || 'agiliton', password: process.env.DB_PASSWORD || 'QtqiwCOAUpQNF6pjzOMAREzUny2bY8V1', max: 5, idleTimeoutMillis: 30000, connectionTimeoutMillis: 5000, }; // Create connection pool const pool = new Pool(config); // Log connection errors pool.on('error', (err) => { console.error('Unexpected database error:', err); }); /** * Execute a query and return all rows */ export async function query>( text: string, params?: unknown[] ): Promise { const result = await pool.query(text, params); return result.rows as T[]; } /** * Execute a query and return the first row */ export async function queryOne>( text: string, params?: unknown[] ): Promise { const result = await pool.query(text, params); return (result.rows[0] as T) || null; } /** * Execute a query without returning results (INSERT, UPDATE, DELETE) */ export async function execute( text: string, params?: unknown[] ): Promise { const result = await pool.query(text, params); return result.rowCount || 0; } /** * Get the next task ID for a project */ export async function getNextTaskId(projectKey: string): Promise { const result = await queryOne<{ next_id: number }>( `INSERT INTO task_sequences (project, next_id) VALUES ($1, 1) ON CONFLICT (project) DO UPDATE SET next_id = task_sequences.next_id + 1 RETURNING next_id`, [projectKey] ); return `${projectKey}-${result?.next_id || 1}`; } /** * Detect project from current working directory */ export function detectProjectFromCwd(): string | null { const cwd = process.cwd(); // Known project path patterns const patterns: Record = { 'SmartTranslate': 'ST', 'VPN': 'VPN', 'BestGPT': 'BGPT', 'AssistOWUI': 'OWUI', 'AssistForOpenWebUI': 'OWUI', 'AssistForJira': 'AFJ', 'eToroGridbot': 'GB', 'WildFiles': 'WF', 'Circles': 'CIR', 'ClaudeFramework': 'CF', 'AgilitonScripts': 'AS', 'WHMCS': 'WHMCS', 'Cardscanner': 'CS', 'ZorkiOS': 'ZORK', 'MeetingMind': 'MM', 'PropertyMap': 'PM', 'Rubic': 'RUB', 'Socialguard': 'SG', }; // Check each pattern for (const [name, key] of Object.entries(patterns)) { if (cwd.includes(`/${name}/`) || cwd.endsWith(`/${name}`)) { return key; } } // Try to extract from Apps/X or Libraries/X path const appsMatch = cwd.match(/\/Apps\/([^/]+)/); if (appsMatch) { return appsMatch[1].replace(/[a-z]/g, '').slice(0, 4) || appsMatch[1].slice(0, 3).toUpperCase(); } const infraMatch = cwd.match(/\/Infrastructure\/([^/]+)/); if (infraMatch) { return infraMatch[1].replace(/[a-z]/g, '').slice(0, 4) || infraMatch[1].slice(0, 3).toUpperCase(); } return null; } /** * Get project key from name, or generate one * If projectName is empty/undefined, tries to detect from CWD */ export async function getProjectKey(projectName?: string): Promise { // Auto-detect if not provided if (!projectName) { const detected = detectProjectFromCwd(); if (detected) { // Ensure project exists in DB await execute( `INSERT INTO projects (key, name) VALUES ($1, $1) ON CONFLICT (key) DO NOTHING`, [detected] ); return detected; } return 'MISC'; // Fallback for unknown projects } // First check if already registered const existing = await queryOne<{ key: string }>( `SELECT key FROM projects WHERE name = $1 LIMIT 1`, [projectName] ); if (existing) { return existing.key; } // Generate a key from the name (uppercase first letters) let generated = projectName.replace(/[a-z]/g, '').slice(0, 4); if (!generated) { generated = projectName.slice(0, 3).toUpperCase(); } // Register the new project await execute( `INSERT INTO projects (key, name) VALUES ($1, $2) ON CONFLICT (key) DO NOTHING`, [generated, projectName] ); return generated; } /** * Test database connection */ export async function testConnection(): Promise { try { await query('SELECT 1'); return true; } catch (error) { console.error('Database connection failed:', error); return false; } } /** * Close the connection pool */ export async function close(): Promise { await pool.end(); } export default pool;