File size: 3,670 Bytes
57a1132
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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); 
});