import { Router } from 'express'; import { getDb } from '../config/database.js'; import { authenticateToken } from '../middleware/auth.js'; import { log } from '../config/logger.js'; const router = Router(); // POST /api/analytics/callback/:uid - BBB Learning Analytics callback (no auth, called by BBB) router.post('/callback/:uid', async (req, res) => { try { const db = getDb(); const room = await db.get('SELECT id, uid, learning_analytics FROM rooms WHERE uid = ?', [req.params.uid]); if (!room) { return res.status(404).json({ error: 'Room not found' }); } if (!room.learning_analytics) { return res.status(403).json({ error: 'Learning analytics not enabled for this room' }); } const data = req.body; if (!data || typeof data !== 'object') { return res.status(400).json({ error: 'Invalid analytics data' }); } // Extract meeting info from BBB analytics payload const meetingId = data.intId || data.extId || room.uid; const meetingName = data.name || room.uid; // Upsert: update if same meeting already exists (BBB sends updates during the meeting) const existing = await db.get( 'SELECT id FROM learning_analytics_data WHERE room_id = ? AND meeting_id = ?', [room.id, meetingId] ); const jsonData = JSON.stringify(data); if (existing) { await db.run( 'UPDATE learning_analytics_data SET data = ?, meeting_name = ?, created_at = CURRENT_TIMESTAMP WHERE id = ?', [jsonData, meetingName, existing.id] ); } else { await db.run( 'INSERT INTO learning_analytics_data (room_id, meeting_id, meeting_name, data) VALUES (?, ?, ?, ?)', [room.id, meetingId, meetingName, jsonData] ); } log.server.info(`Analytics callback received for room ${room.uid} (meeting: ${meetingId})`); res.json({ success: true }); } catch (err) { log.server.error(`Analytics callback error: ${err.message}`); res.status(500).json({ error: 'Error processing analytics data' }); } }); // GET /api/analytics/room/:uid - Get analytics for a room (authenticated) router.get('/room/:uid', authenticateToken, async (req, res) => { try { const db = getDb(); const room = await db.get('SELECT id, user_id FROM rooms WHERE uid = ?', [req.params.uid]); if (!room) { return res.status(404).json({ error: 'Room not found' }); } // Check access: owner, shared, or admin if (room.user_id !== req.user.id && req.user.role !== 'admin') { const share = await db.get('SELECT id FROM room_shares WHERE room_id = ? AND user_id = ?', [room.id, req.user.id]); if (!share) { return res.status(403).json({ error: 'No permission to view analytics for this room' }); } } const rows = await db.all( 'SELECT id, meeting_id, meeting_name, data, created_at FROM learning_analytics_data WHERE room_id = ? ORDER BY created_at DESC', [room.id] ); const analytics = rows.map(row => ({ id: row.id, meetingId: row.meeting_id, meetingName: row.meeting_name, data: typeof row.data === 'string' ? JSON.parse(row.data) : row.data, createdAt: row.created_at, })); res.json({ analytics }); } catch (err) { log.server.error(`Get analytics error: ${err.message}`); res.status(500).json({ error: 'Error fetching analytics' }); } }); // DELETE /api/analytics/:id - Delete analytics entry (authenticated, owner only) router.delete('/:id', authenticateToken, async (req, res) => { try { const db = getDb(); const entry = await db.get( `SELECT la.id, la.room_id, r.user_id FROM learning_analytics_data la JOIN rooms r ON la.room_id = r.id WHERE la.id = ?`, [req.params.id] ); if (!entry) { return res.status(404).json({ error: 'Analytics entry not found' }); } if (entry.user_id !== req.user.id && req.user.role !== 'admin') { return res.status(403).json({ error: 'No permission to delete this entry' }); } await db.run('DELETE FROM learning_analytics_data WHERE id = ?', [req.params.id]); res.json({ success: true }); } catch (err) { log.server.error(`Delete analytics error: ${err.message}`); res.status(500).json({ error: 'Error deleting analytics entry' }); } }); export default router;