feat: implement calendar invitation system with creation, acceptance, and management features
All checks were successful
Build & Push Docker Image / build (push) Successful in 6m26s
All checks were successful
Build & Push Docker Image / build (push) Successful in 6m26s
This commit is contained in:
@@ -261,12 +261,20 @@ router.get('/invitations', authenticateToken, async (req, res) => {
|
||||
router.get('/invitations/pending-count', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const db = getDb();
|
||||
const result = await db.get(
|
||||
const roomResult = await db.get(
|
||||
`SELECT COUNT(*) as count FROM federation_invitations
|
||||
WHERE to_user_id = ? AND status = 'pending'`,
|
||||
[req.user.id]
|
||||
);
|
||||
res.json({ count: result?.count || 0 });
|
||||
let calResult = { count: 0 };
|
||||
try {
|
||||
calResult = await db.get(
|
||||
`SELECT COUNT(*) as count FROM calendar_invitations
|
||||
WHERE to_user_id = ? AND status = 'pending'`,
|
||||
[req.user.id]
|
||||
);
|
||||
} catch { /* table may not exist yet */ }
|
||||
res.json({ count: (roomResult?.count || 0) + (calResult?.count || 0) });
|
||||
} catch (err) {
|
||||
res.json({ count: 0 });
|
||||
}
|
||||
@@ -338,6 +346,94 @@ router.delete('/invitations/:id', authenticateToken, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ── GET /api/federation/calendar-invitations — List calendar invitations ─────
|
||||
router.get('/calendar-invitations', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const db = getDb();
|
||||
const invitations = await db.all(
|
||||
`SELECT * FROM calendar_invitations
|
||||
WHERE to_user_id = ?
|
||||
ORDER BY created_at DESC`,
|
||||
[req.user.id]
|
||||
);
|
||||
res.json({ invitations });
|
||||
} catch (err) {
|
||||
log.federation.error('List calendar invitations error:', err);
|
||||
res.status(500).json({ error: 'Failed to load calendar invitations' });
|
||||
}
|
||||
});
|
||||
|
||||
// ── POST /api/federation/calendar-invitations/:id/accept ─────────────────────
|
||||
router.post('/calendar-invitations/:id/accept', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const db = getDb();
|
||||
const inv = await db.get(
|
||||
`SELECT * FROM calendar_invitations WHERE id = ? AND to_user_id = ?`,
|
||||
[req.params.id, req.user.id]
|
||||
);
|
||||
if (!inv) return res.status(404).json({ error: 'Calendar invitation not found' });
|
||||
if (inv.status === 'accepted') return res.status(400).json({ error: 'Already accepted' });
|
||||
|
||||
await db.run(
|
||||
`UPDATE calendar_invitations SET status = 'accepted' WHERE id = ?`,
|
||||
[inv.id]
|
||||
);
|
||||
|
||||
// Check if event was already previously accepted (duplicate guard)
|
||||
const existing = await db.get(
|
||||
'SELECT id FROM calendar_events WHERE uid = ? AND user_id = ?',
|
||||
[inv.event_uid, req.user.id]
|
||||
);
|
||||
if (!existing) {
|
||||
await db.run(`
|
||||
INSERT INTO calendar_events (uid, title, description, start_time, end_time, room_uid, user_id, color, federated_from, federated_join_url)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, [
|
||||
inv.event_uid,
|
||||
inv.title,
|
||||
inv.description || null,
|
||||
inv.start_time,
|
||||
inv.end_time,
|
||||
inv.room_uid || null,
|
||||
req.user.id,
|
||||
inv.color || '#6366f1',
|
||||
inv.from_user,
|
||||
inv.join_url || null,
|
||||
]);
|
||||
}
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
log.federation.error('Accept calendar invitation error:', err);
|
||||
res.status(500).json({ error: 'Failed to accept calendar invitation' });
|
||||
}
|
||||
});
|
||||
|
||||
// ── DELETE /api/federation/calendar-invitations/:id — Decline/dismiss ────────
|
||||
router.delete('/calendar-invitations/:id', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const db = getDb();
|
||||
const inv = await db.get(
|
||||
`SELECT * FROM calendar_invitations WHERE id = ? AND to_user_id = ?`,
|
||||
[req.params.id, req.user.id]
|
||||
);
|
||||
if (!inv) return res.status(404).json({ error: 'Calendar invitation not found' });
|
||||
|
||||
if (inv.status === 'pending') {
|
||||
// mark as declined
|
||||
await db.run(`UPDATE calendar_invitations SET status = 'declined' WHERE id = ?`, [inv.id]);
|
||||
} else {
|
||||
// accepted or declined — permanently remove from inbox
|
||||
await db.run('DELETE FROM calendar_invitations WHERE id = ?', [inv.id]);
|
||||
}
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
log.federation.error('Delete calendar invitation error:', err);
|
||||
res.status(500).json({ error: 'Failed to remove calendar invitation' });
|
||||
}
|
||||
});
|
||||
|
||||
// ── GET /api/federation/federated-rooms — List saved federated rooms ────────
|
||||
router.get('/federated-rooms', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user