1
0

Add birthday support for mobile calendar

This commit is contained in:
2025-07-18 14:12:57 +02:00
parent ddb3c682f5
commit b7e8d1c921
5 changed files with 575 additions and 65 deletions

View File

@@ -149,7 +149,10 @@ router.get('/events/:year/:month', async (req, res) => {
return res.json([]);
}
const events = await CalendarService.getFamilyEventsForMonth(users, year, month);
// Convert Sequelize instances to plain objects
const userConfigs = users.map(user => user.toJSON());
const events = await CalendarService.getFamilyEventsForMonth(userConfigs, year, month);
res.json(events);
} else {
// Get events for a specific user
@@ -168,8 +171,31 @@ router.get('/events/:year/:month', async (req, res) => {
return res.status(404).json({ error: 'User not found or inactive' });
}
const events = await CalendarService.getEventsForMonth(userConfig, year, month);
res.json(events);
// Convert Sequelize instance to plain object
const userConfigPlain = userConfig.toJSON();
const events = await CalendarService.getEventsForMonth(userConfigPlain, year, month);
const birthdayEvents = await CalendarService.getContactBirthdaysForMonth(userConfigPlain, year, month);
// Combine regular events and birthday events
const allEvents = [...events, ...birthdayEvents];
// Sort events by date string first (YYYY-MM-DD format) - this ensures proper chronological ordering
allEvents.sort((a, b) => {
// First, compare dates as strings to avoid timezone issues
if (a.date !== b.date) {
return a.date.localeCompare(b.date);
}
// If dates are the same, sort by type (birthdays first)
if (a.type === 'birthday' && b.type !== 'birthday') return -1;
if (a.type !== 'birthday' && b.type === 'birthday') return 1;
// Then sort by event text for consistent ordering
return (a.text || '').localeCompare(b.text || '');
});
res.json(allEvents);
}
} catch (error) {
console.error('Error retrieving calendar events:', error.message);
@@ -205,7 +231,7 @@ router.post('/events', async (req, res) => {
return res.status(404).json({ error: 'User not found or inactive' });
}
const event = await CalendarService.createEvent(userConfig, date, text);
const event = await CalendarService.createEvent(userConfig.toJSON(), date, text);
res.status(201).json(event);
} catch (error) {
console.error('Error creating calendar event:', error.message);
@@ -242,14 +268,18 @@ router.put('/events/:eventId', async (req, res) => {
return res.status(404).json({ error: 'User not found or inactive' });
}
const event = await CalendarService.updateEvent(userConfig, eventId, date, text);
const event = await CalendarService.updateEvent(userConfig.toJSON(), eventId, date, text);
res.json(event);
} catch (error) {
console.error('Error updating calendar event:', error);
if (error.message === 'Event not found') {
res.status(404).json({ error: 'Event not found' });
} else if (error.message.includes('You can only edit events that belong to you')) {
res.status(403).json({ error: 'You can only edit events that belong to you' });
} else if (error.message.includes('Permission denied')) {
res.status(403).json({ error: error.message });
} else if (error.message.includes('CalDAV authentication failed')) {
res.status(401).json({ error: 'Calendar authentication failed' });
} else {
res.status(500).json({ error: 'Failed to update calendar event' });
}
@@ -269,6 +299,13 @@ router.delete('/events/:eventId', async (req, res) => {
});
}
// Check if this is a birthday event (birthday events should not be deletable)
if (eventId && eventId.includes('birthday-')) {
return res.status(403).json({
error: 'Birthday events cannot be deleted from the calendar interface'
});
}
const userConfig = await CalendarUser.findOne({
where: { name: user, isActive: true },
attributes: ['name', 'nextcloudUrl', 'username', 'password', 'calendarName']
@@ -278,14 +315,20 @@ router.delete('/events/:eventId', async (req, res) => {
return res.status(404).json({ error: 'User not found or inactive' });
}
await CalendarService.deleteEvent(userConfig, eventId);
await CalendarService.deleteEvent(userConfig.toJSON(), eventId);
res.json({ message: 'Event deleted successfully' });
} catch (error) {
console.error('Error deleting calendar event:', error);
if (error.message === 'Event not found') {
res.status(404).json({ error: 'Event not found' });
} else if (error.message.includes('Permission denied')) {
res.status(404).json({
error: 'Event not found. This event may belong to a different user or have already been deleted.'
});
} else if (error.message.includes('You can only delete events that belong to you')) {
res.status(403).json({ error: 'You can only delete events that belong to you' });
} else if (error.message === 'Birthday events cannot be deleted from the calendar interface') {
res.status(403).json({ error: error.message });
} else if (error.message.includes('Permission denied') || error.message.includes('authentication failed')) {
res.status(403).json({ error: 'Permission denied. You may not have access to delete this event.' });
} else {
res.status(500).json({ error: 'Failed to delete calendar event' });
}
@@ -320,4 +363,41 @@ router.post('/users/:id/test-connection', async (req, res) => {
}
});
// Get contact birthdays for a specific month
// GET /api/calendar/birthdays/:year/:month?user=username
router.get('/birthdays/:year/:month', async (req, res) => {
try {
const { year, month } = req.params;
const { user } = req.query;
// Validate year and month
if (!year || !month || !/^\d{4}$/.test(year) || !/^(0[1-9]|1[0-2])$/.test(month)) {
return res.status(400).json({
error: 'Invalid year or month format. Use YYYY for year and MM for month.'
});
}
if (!user) {
return res.status(400).json({
error: 'User parameter required for birthday calendar access'
});
}
const userConfig = await CalendarUser.findOne({
where: { name: user, isActive: true },
attributes: ['name', 'nextcloudUrl', 'username', 'password', 'calendarName']
});
if (!userConfig) {
return res.status(404).json({ error: 'User not found or inactive' });
}
const birthdayEvents = await CalendarService.getContactBirthdaysForMonth(userConfig.toJSON(), year, month);
res.json(birthdayEvents);
} catch (error) {
console.error('Error retrieving contact birthdays:', error.message);
res.status(500).json({ error: 'Failed to retrieve contact birthdays' });
}
});
module.exports = router;