acadflow / deploy-backend /server.js
Vijayadhith7's picture
Upload 29 files
57a1132 verified
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);
});