feat(logging): implement centralized logging system and replace console errors with structured logs
feat(federation): add room sync and deletion notification endpoints for federated instances fix(federation): handle room deletion and update settings during sync process feat(federation): enhance FederatedRoomCard and FederatedRoomDetail components to display deleted rooms i18n: add translations for room deletion messages in English and German
This commit is contained in:
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'url';
|
||||
import { rateLimit } from 'express-rate-limit';
|
||||
import { getDb } from '../config/database.js';
|
||||
import { authenticateToken } from '../middleware/auth.js';
|
||||
import { log } from '../config/logger.js';
|
||||
import {
|
||||
createMeeting,
|
||||
joinMeeting,
|
||||
@@ -13,6 +14,12 @@ import {
|
||||
getMeetingInfo,
|
||||
isMeetingRunning,
|
||||
} from '../config/bbb.js';
|
||||
import {
|
||||
isFederationEnabled,
|
||||
getFederationDomain,
|
||||
signPayload,
|
||||
discoverInstance,
|
||||
} from '../config/federation.js';
|
||||
|
||||
// L6: constant-time string comparison for access/moderator codes
|
||||
function timingSafeEqual(a, b) {
|
||||
@@ -72,7 +79,7 @@ router.get('/', authenticateToken, async (req, res) => {
|
||||
|
||||
res.json({ rooms: [...ownRooms, ...sharedRooms] });
|
||||
} catch (err) {
|
||||
console.error('List rooms error:', err);
|
||||
log.rooms.error(`List rooms error: ${err.message}`);
|
||||
res.status(500).json({ error: 'Rooms could not be loaded' });
|
||||
}
|
||||
});
|
||||
@@ -94,7 +101,7 @@ router.get('/users/search', authenticateToken, async (req, res) => {
|
||||
`, [searchTerm, searchTerm, searchTerm, req.user.id]);
|
||||
res.json({ users });
|
||||
} catch (err) {
|
||||
console.error('Search users error:', err);
|
||||
log.rooms.error(`Search users error: ${err.message}`);
|
||||
res.status(500).json({ error: 'User search failed' });
|
||||
}
|
||||
});
|
||||
@@ -133,7 +140,7 @@ router.get('/:uid', authenticateToken, async (req, res) => {
|
||||
|
||||
res.json({ room, sharedUsers });
|
||||
} catch (err) {
|
||||
console.error('Get room error:', err);
|
||||
log.rooms.error(`Get room error: ${err.message}`);
|
||||
res.status(500).json({ error: 'Room could not be loaded' });
|
||||
}
|
||||
});
|
||||
@@ -205,7 +212,7 @@ router.post('/', authenticateToken, async (req, res) => {
|
||||
const room = await db.get('SELECT * FROM rooms WHERE id = ?', [result.lastInsertRowid]);
|
||||
res.status(201).json({ room });
|
||||
} catch (err) {
|
||||
console.error('Create room error:', err);
|
||||
log.rooms.error(`Create room error: ${err.message}`);
|
||||
res.status(500).json({ error: 'Room could not be created' });
|
||||
}
|
||||
});
|
||||
@@ -288,7 +295,7 @@ router.put('/:uid', authenticateToken, async (req, res) => {
|
||||
const updated = await db.get('SELECT * FROM rooms WHERE uid = ?', [req.params.uid]);
|
||||
res.json({ room: updated });
|
||||
} catch (err) {
|
||||
console.error('Update room error:', err);
|
||||
log.rooms.error(`Update room error: ${err.message}`);
|
||||
res.status(500).json({ error: 'Room could not be updated' });
|
||||
}
|
||||
});
|
||||
@@ -307,10 +314,43 @@ router.delete('/:uid', authenticateToken, async (req, res) => {
|
||||
return res.status(403).json({ error: 'No permission' });
|
||||
}
|
||||
|
||||
// Notify federated instances about deletion (fire-and-forget)
|
||||
if (isFederationEnabled()) {
|
||||
try {
|
||||
const outbound = await db.all(
|
||||
'SELECT remote_domain FROM federation_outbound_invites WHERE room_uid = ?',
|
||||
[room.uid]
|
||||
);
|
||||
for (const { remote_domain } of outbound) {
|
||||
const payload = {
|
||||
room_uid: room.uid,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
const signature = signPayload(payload);
|
||||
discoverInstance(remote_domain).then(({ baseUrl: remoteApi }) => {
|
||||
fetch(`${remoteApi}/room-deleted`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Federation-Signature': signature,
|
||||
'X-Federation-Origin': getFederationDomain(),
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
signal: AbortSignal.timeout(10_000),
|
||||
}).catch(err => log.federation.warn(`Delete notify to ${remote_domain} failed: ${err.message}`));
|
||||
}).catch(err => log.federation.warn(`Discovery for ${remote_domain} failed: ${err.message}`));
|
||||
}
|
||||
// Clean up outbound records
|
||||
await db.run('DELETE FROM federation_outbound_invites WHERE room_uid = ?', [room.uid]);
|
||||
} catch (fedErr) {
|
||||
log.federation.warn(`Delete notification error (non-fatal): ${fedErr.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
await db.run('DELETE FROM rooms WHERE uid = ?', [req.params.uid]);
|
||||
res.json({ message: 'Room deleted successfully' });
|
||||
} catch (err) {
|
||||
console.error('Delete room error:', err);
|
||||
log.rooms.error(`Delete room error: ${err.message}`);
|
||||
res.status(500).json({ error: 'Room could not be deleted' });
|
||||
}
|
||||
});
|
||||
@@ -330,7 +370,7 @@ router.get('/:uid/shares', authenticateToken, async (req, res) => {
|
||||
`, [room.id]);
|
||||
res.json({ shares });
|
||||
} catch (err) {
|
||||
console.error('Get shares error:', err);
|
||||
log.rooms.error(`Get shares error: ${err.message}`);
|
||||
res.status(500).json({ error: 'Error loading shares' });
|
||||
}
|
||||
});
|
||||
@@ -364,7 +404,7 @@ router.post('/:uid/shares', authenticateToken, async (req, res) => {
|
||||
`, [room.id]);
|
||||
res.json({ shares });
|
||||
} catch (err) {
|
||||
console.error('Share room error:', err);
|
||||
log.rooms.error(`Share room error: ${err.message}`);
|
||||
res.status(500).json({ error: 'Error sharing room' });
|
||||
}
|
||||
});
|
||||
@@ -386,7 +426,7 @@ router.delete('/:uid/shares/:userId', authenticateToken, async (req, res) => {
|
||||
`, [room.id]);
|
||||
res.json({ shares });
|
||||
} catch (err) {
|
||||
console.error('Remove share error:', err);
|
||||
log.rooms.error(`Remove share error: ${err.message}`);
|
||||
res.status(500).json({ error: 'Error removing share' });
|
||||
}
|
||||
});
|
||||
@@ -421,7 +461,7 @@ router.post('/:uid/start', authenticateToken, async (req, res) => {
|
||||
const joinUrl = await joinMeeting(room.uid, displayName, true, avatarURL);
|
||||
res.json({ joinUrl });
|
||||
} catch (err) {
|
||||
console.error('Start meeting error:', err);
|
||||
log.rooms.error(`Start meeting error: ${err.message}`);
|
||||
res.status(500).json({ error: 'Meeting could not be started' });
|
||||
}
|
||||
});
|
||||
@@ -455,7 +495,7 @@ router.post('/:uid/join', authenticateToken, async (req, res) => {
|
||||
const joinUrl = await joinMeeting(room.uid, req.user.display_name || req.user.name, isModerator, avatarURL);
|
||||
res.json({ joinUrl });
|
||||
} catch (err) {
|
||||
console.error('Join meeting error:', err);
|
||||
log.rooms.error(`Join meeting error: ${err.message}`);
|
||||
res.status(500).json({ error: 'Could not join meeting' });
|
||||
}
|
||||
});
|
||||
@@ -482,7 +522,7 @@ router.post('/:uid/end', authenticateToken, async (req, res) => {
|
||||
await endMeeting(room.uid);
|
||||
res.json({ message: 'Meeting ended' });
|
||||
} catch (err) {
|
||||
console.error('End meeting error:', err);
|
||||
log.rooms.error(`End meeting error: ${err.message}`);
|
||||
res.status(500).json({ error: 'Meeting could not be ended' });
|
||||
}
|
||||
});
|
||||
@@ -519,7 +559,7 @@ router.get('/:uid/public', async (req, res) => {
|
||||
running,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Public room info error:', err);
|
||||
log.rooms.error(`Public room info error: ${err.message}`);
|
||||
res.status(500).json({ error: 'Room info could not be loaded' });
|
||||
}
|
||||
});
|
||||
@@ -574,7 +614,7 @@ router.post('/:uid/guest-join', guestJoinLimiter, async (req, res) => {
|
||||
const joinUrl = await joinMeeting(room.uid, name.trim(), isModerator, guestAvatarURL);
|
||||
res.json({ joinUrl });
|
||||
} catch (err) {
|
||||
console.error('Guest join error:', err);
|
||||
log.rooms.error(`Guest join error: ${err.message}`);
|
||||
res.status(500).json({ error: 'Guest join failed' });
|
||||
}
|
||||
});
|
||||
@@ -665,7 +705,7 @@ router.post('/:uid/presentation', authenticateToken, async (req, res) => {
|
||||
const updated = await db.get('SELECT * FROM rooms WHERE uid = ?', [req.params.uid]);
|
||||
res.json({ room: updated });
|
||||
} catch (err) {
|
||||
console.error('Presentation upload error:', err);
|
||||
log.rooms.error(`Presentation upload error: ${err.message}`);
|
||||
res.status(500).json({ error: 'Presentation could not be uploaded' });
|
||||
}
|
||||
});
|
||||
@@ -687,7 +727,7 @@ router.delete('/:uid/presentation', authenticateToken, async (req, res) => {
|
||||
const updated = await db.get('SELECT * FROM rooms WHERE uid = ?', [req.params.uid]);
|
||||
res.json({ room: updated });
|
||||
} catch (err) {
|
||||
console.error('Presentation delete error:', err);
|
||||
log.rooms.error(`Presentation delete error: ${err.message}`);
|
||||
res.status(500).json({ error: 'Presentation could not be removed' });
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user