const express = require('express'); const http = require('http'); const { Server } = require('socket.io'); const cors = require('cors'); const cron = require('node-cron'); require('dotenv').config(); const { syncSheetsToDB } = require('./services/googleSheets'); const { requireAuth, getTenantDb } = require('./services/auth'); const app = express(); const server = http.createServer(app); const io = new Server(server, { cors: { origin: '*', methods: ['GET', 'POST'] } }); app.use(cors()); app.use(express.json()); // --- Cron Job --- // Schedule Google Sheets sync every 30 seconds cron.schedule('*/30 * * * * *', () => { console.log('Running scheduled sync with Google Sheets...'); syncSheetsToDB(io); }); // --- Real-time Socket.IO with Room Isolation --- io.on('connection', (socket) => { console.log('Frontend client connected via WebSocket:', socket.id); // Isolate counselor sessions by organizational room socket.on('join_organization', (orgId) => { socket.join(`org-${orgId}`); console.log(`Socket ${socket.id} joined secure channel: org-${orgId}`); }); socket.on('disconnect', () => { console.log('Frontend client disconnected:', socket.id); }); }); // --- Modular Routing Blocks --- const leadRouter = require('./services/leadService'); const admissionRouter = require('./services/admissionService'); const aiRouter = require('./services/aiService'); const { router: notificationRouter, initNotificationService } = require('./services/notificationService'); // Inject live Socket.IO hooks into the notification event controller const notificationHelper = initNotificationService(io); // Mount Scoped API Sub-routers app.use('/api/leads', leadRouter); app.use('/api/admissions', admissionRouter); app.use('/api/ai-insights', aiRouter); app.use('/api/notifications', notificationRouter); // GET /api/stats - Dashboard KPI metrics scoped strictly by active organization app.get('/api/stats', requireAuth, async (req, res) => { try { const db = getTenantDb(req); // Parallel counts enforced by database Row-Level Security automatically const [totalLeadsRes, activeLeadsRes, admissionsRes, revenueRes] = await Promise.all([ db.from('leads').select('*', { count: 'exact', head: true }), db.from('leads').select('*', { count: 'exact', head: true }).not('status', 'in', '("Not Interested","Converted")'), db.from('admissions').select('*', { count: 'exact', head: true }), db.from('admissions').select('fees') ]); if (totalLeadsRes.error) throw totalLeadsRes.error; if (activeLeadsRes.error) throw activeLeadsRes.error; if (admissionsRes.error) throw admissionsRes.error; if (revenueRes.error) throw revenueRes.error; const revenue = (revenueRes.data || []).reduce((sum, adm) => sum + parseFloat(adm.fees || 0), 0); res.json({ totalLeads: totalLeadsRes.count || 0, activeLeads: activeLeadsRes.count || 0, admissions: admissionsRes.count || 0, revenue }); } catch (error) { console.error('Error fetching KPI metrics:', error); res.status(500).json({ error: 'Internal Server Error' }); } }); // POST /api/sync-sheet - Trigger external sheets sync app.post('/api/sync-sheet', requireAuth, async (req, res) => { try { await syncSheetsToDB(io); res.json({ message: 'Sheets sync completed' }); } catch (error) { res.status(500).json({ error: 'Sheets sync failed' }); } }); const PORT = process.env.PORT || 5000; server.listen(PORT, () => { console.log(`Production API Gateway running on port ${PORT}`); // Initialize baseline sync setTimeout(() => syncSheetsToDB(io), 2000); });