const express = require('express'); const cors = require('cors'); require('dotenv').config(); const { requireAuth, getTenantDb } = require('./services/auth'); const dbAdmin = require('./db'); // --- 🛡️ Global Crash Protection (10-Year Uptime Shield) --- process.on('uncaughtException', (err) => { console.error('CRITICAL [Uncaught Exception]:', err); }); process.on('unhandledRejection', (reason, promise) => { console.error('CRITICAL [Unhandled Rejection]:', reason); }); // ------------------------------------------------------------- const app = express(); app.use(cors()); app.use(express.json()); // Health check endpoint with environmental diagnostics app.get('/', (req, res) => { const dbModule = require('./db'); res.status(200).json({ status: 'Healthy', timestamp: new Date().toISOString(), nodeVersion: process.version, port: process.env.PORT || 7860, diagnostics: { SUPABASE_URL_EXISTS: !!process.env.SUPABASE_URL, SUPABASE_KEY_EXISTS: !!process.env.SUPABASE_KEY, SUPABASE_DB_URL_EXISTS: !!process.env.SUPABASE_DB_URL, SPREADSHEET_ID_EXISTS: !!process.env.SPREADSHEET_ID, GOOGLE_CREDS_JSON_EXISTS: !!process.env.GOOGLE_CREDS_JSON, NVIDIA_API_KEY_EXISTS: !!process.env.NVIDIA_API_KEY }, databaseInitializationError: dbModule.dbError || null }); }); // --- Modular Routing Blocks --- const leadRouter = require('./services/leadService'); const admissionRouter = require('./services/admissionService'); const aiRouter = require('./services/aiService'); const counselorRouter = require('./services/counselorService'); const publicRouter = require('./services/publicService'); const whatsappRouter = require('./services/whatsappService'); const courseRouter = require('./services/courseService'); const { router: notificationRouter, initNotificationService } = require('./services/notificationService'); // Initialize decoupled event subscribers for database auditing and real-time triggers initNotificationService(); // Legacy compatibility route rewrite for /api/update-lead and /server-api/update-lead app.put(['/api/update-lead', '/server-api/update-lead'], (req, res, next) => { req.url = '/update-lead'; leadRouter(req, res, next); }); // Mount Scoped API Sub-routers on both /api and /server-api ['/api', '/server-api'].forEach(prefix => { app.use(`${prefix}/leads`, leadRouter); app.use(`${prefix}/admissions`, admissionRouter); app.use(`${prefix}/ai-insights`, aiRouter); app.use(`${prefix}/insights`, aiRouter); app.use(`${prefix}/notifications`, notificationRouter); app.use(`${prefix}/counselors`, counselorRouter); app.use(`${prefix}/public`, publicRouter); app.use(`${prefix}/whatsapp`, whatsappRouter); app.use(`${prefix}/courses`, courseRouter); }); // GET /api/stats and /server-api/stats - Dashboard KPI metrics scoped strictly by active organization app.get(['/api/stats', '/server-api/stats'], requireAuth, async (req, res) => { try { const db = getTenantDb(req); // Fetch active leads, admissions, counselors, and activities in parallel const [leadsRes, admissionsRes, counselorsRes, activitiesRes] = await Promise.all([ db.from('active_leads').select('id, status, followup_time, course_interested, counselor_id'), db.from('active_admissions').select('id, fees, course, lead_id'), db.from('active_counselors').select('id, name'), db.from('lead_activities').select('activity_type, description') ]); if (leadsRes.error) throw leadsRes.error; if (admissionsRes.error) throw admissionsRes.error; if (counselorsRes.error) throw counselorsRes.error; const leads = leadsRes.data || []; const admissions = admissionsRes.data || []; const counselors = counselorsRes.data || []; const activities = (activitiesRes && !activitiesRes.error) ? activitiesRes.data : []; const totalLeads = leads.length; const activeLeads = leads.filter(l => !['Not Interested', 'Converted', 'Lost'].includes(l.status)).length; const lostLeads = leads.filter(l => ['Not Interested', 'Lost'].includes(l.status)).length; const admissionsCount = admissions.length; const revenue = admissions.reduce((sum, adm) => sum + (parseFloat(adm.fees) || 0), 0); // Follow-ups due today/immediate or pending const followupsDue = leads.filter(l => l.status === 'Pending' || l.followup_time === 'Today' || l.followup_time === 'Immediate').length; // Trending courses list const courseCounts = {}; leads.forEach(l => { if (l.course_interested) { courseCounts[l.course_interested] = (courseCounts[l.course_interested] || 0) + 1; } }); const trendingCourses = Object.entries(courseCounts) .map(([name, count]) => ({ name, count })) .sort((a, b) => b.count - a.count) .slice(0, 5); // Counselor stats const counselorStats = counselors.map(c => { const cLeads = leads.filter(l => l.counselor_id === c.id); const cAdmissions = admissions.filter(a => cLeads.some(l => l.id === a.lead_id)); const conversionRate = cLeads.length > 0 ? Math.round((cAdmissions.length / cLeads.length) * 100) : 0; return { name: c.name, leadsAssigned: cLeads.length, conversions: cAdmissions.length, conversionRate }; }); // WhatsApp Stats const whatsappTotal = activities.filter(a => a.activity_type === 'WhatsApp Follow-up Sent').length; const whatsappFollowups = activities.filter(a => a.description && a.description.includes('Follow-up Reminder')).length; const whatsappAdmissions = activities.filter(a => a.description && a.description.includes('Admission Reminder')).length; res.json({ totalLeads, activeLeads, lostLeads, admissions: admissionsCount, revenue, followupsDue, trendingCourses, counselorStats, whatsappStats: { totalSent: whatsappTotal, followupReminders: whatsappFollowups, admissionReminders: whatsappAdmissions } }); } catch (error) { console.error('Error fetching KPI metrics:', error); res.status(500).json({ error: 'Internal Server Error' }); } }); // POST /api/sync-sheet and /server-api/sync-sheet - Keep sync endpoint strictly for manual importing if requested app.post(['/api/sync-sheet', '/server-api/sync-sheet'], requireAuth, async (req, res) => { try { const { syncSheetsToDB } = require('./services/googleSheets'); await syncSheetsToDB(null); res.json({ message: 'Sheets sync completed successfully' }); } catch (error) { console.error('Sheets sync failed:', error); res.status(500).json({ error: 'Sheets sync failed' }); } }); // --- 🛡️ Global Express Error Handler --- app.use((err, req, res, next) => { console.error('Express Pipeline Error:', err); res.status(500).json({ error: 'Internal Server Error (Caught by Global Handler)' }); }); // ----------------------------------------- const PORT = process.env.PORT || 7860; app.listen(PORT, '0.0.0.0', () => { console.log(`Production API Gateway running on port ${PORT}`); });