import { Router } from 'express'; import { authenticateToken } from '../middleware/auth.js'; import { getDb } from '../config/database.js'; import { getRecordings, getRecordingByRecordId, deleteRecording, publishRecording, } from '../config/bbb.js'; const router = Router(); // GET /api/recordings - Get recordings for a room (by meetingID/uid) router.get('/', authenticateToken, async (req, res) => { try { const { meetingID } = req.query; // M11: verify user has access to the room if a meetingID is specified if (meetingID) { const db = getDb(); const room = await db.get('SELECT id, user_id FROM rooms WHERE uid = ?', [meetingID]); if (!room) { return res.status(404).json({ error: 'Room not found' }); } 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 recordings for this room' }); } } } else if (req.user.role !== 'admin') { // Non-admins must specify a meetingID return res.status(400).json({ error: 'meetingID query parameter is required' }); } const recordings = await getRecordings(meetingID || undefined); // Format recordings const formatted = recordings.map(rec => { const playback = rec.playback?.format; let formats = []; if (playback) { formats = Array.isArray(playback) ? playback : [playback]; } return { recordID: rec.recordID, meetingID: rec.meetingID, name: rec.name || 'Recording', state: rec.state, published: rec.published === 'true', startTime: rec.startTime, endTime: rec.endTime, participants: rec.participants, size: rec.size, formats: formats.map(f => ({ type: f.type, url: f.url, length: f.length, size: f.size, })), metadata: rec.metadata || {}, }; }); res.json({ recordings: formatted }); } catch (err) { console.error('Get recordings error:', err); res.status(500).json({ error: 'Recordings could not be loaded', recordings: [] }); } }); // GET /api/recordings/room/:uid - Get recordings for a specific room router.get('/room/:uid', authenticateToken, async (req, res) => { try { const db = getDb(); const room = await db.get('SELECT * FROM rooms WHERE uid = ?', [req.params.uid]); if (!room) { return res.status(404).json({ error: 'Room not found' }); } // H9: verify requesting user has access to this room 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 recordings for this room' }); } } const recordings = await getRecordings(room.uid); const formatted = recordings.map(rec => { const playback = rec.playback?.format; let formats = []; if (playback) { formats = Array.isArray(playback) ? playback : [playback]; } return { recordID: rec.recordID, meetingID: rec.meetingID, name: rec.name || room.name, state: rec.state, published: rec.published === 'true', startTime: rec.startTime, endTime: rec.endTime, participants: rec.participants, size: rec.size, formats: formats.map(f => ({ type: f.type, url: f.url, length: f.length, size: f.size, })), }; }); res.json({ recordings: formatted }); } catch (err) { console.error('Get room recordings error:', err); res.status(500).json({ error: 'Recordings could not be loaded', recordings: [] }); } }); // DELETE /api/recordings/:recordID router.delete('/:recordID', authenticateToken, async (req, res) => { try { // M14 fix: look up the recording from BBB to find its meetingID (room UID), // then verify the user owns or shares that room. if (req.user.role !== 'admin') { const rec = await getRecordingByRecordId(req.params.recordID); if (!rec) { return res.status(404).json({ error: 'Recording not found' }); } const db = getDb(); const room = await db.get('SELECT id, user_id FROM rooms WHERE uid = ?', [rec.meetingID]); if (!room) { return res.status(404).json({ error: 'Room not found' }); } if (room.user_id !== req.user.id) { 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 delete this recording' }); } } } await deleteRecording(req.params.recordID); res.json({ message: 'Recording deleted' }); } catch (err) { console.error('Delete recording error:', err); res.status(500).json({ error: 'Recording could not be deleted' }); } }); // PUT /api/recordings/:recordID/publish router.put('/:recordID/publish', authenticateToken, async (req, res) => { try { // M14 fix: look up the recording from BBB to find its meetingID (room UID), // then verify the user owns or shares that room. if (req.user.role !== 'admin') { const rec = await getRecordingByRecordId(req.params.recordID); if (!rec) { return res.status(404).json({ error: 'Recording not found' }); } const db = getDb(); const room = await db.get('SELECT id, user_id FROM rooms WHERE uid = ?', [rec.meetingID]); if (!room) { return res.status(404).json({ error: 'Room not found' }); } if (room.user_id !== req.user.id) { 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 update this recording' }); } } } const { publish } = req.body; await publishRecording(req.params.recordID, publish); res.json({ message: publish ? 'Recording published' : 'Recording unpublished' }); } catch (err) { console.error('Publish recording error:', err); res.status(500).json({ error: 'Recording could not be updated' }); } }); export default router;