1
0
Files
OpenWall/server/routes/calendar.js

404 lines
13 KiB
JavaScript

const express = require('express');
const router = express.Router();
const { CalendarUser } = require('../models');
const CalendarService = require('../services/CalendarService');
// Get all calendar users
router.get('/users', async (req, res) => {
try {
const users = await CalendarUser.findAll({
where: { isActive: true },
attributes: ['id', 'name', 'nextcloudUrl', 'username', 'calendarName', 'isActive']
});
res.json(users);
} catch (error) {
console.error('Error fetching calendar users:', error);
res.status(500).json({ error: 'Failed to fetch calendar users' });
}
});
// Add a new calendar user
router.post('/users', async (req, res) => {
try {
const { name, nextcloudUrl, username, password, calendarName } = req.body;
if (!name || !nextcloudUrl || !username || !password) {
return res.status(400).json({
error: 'Missing required fields: name, nextcloudUrl, username, password'
});
}
// Test connection before saving
const testConfig = { name, nextcloudUrl, username, password, calendarName };
const connectionTest = await CalendarService.testConnection(testConfig);
if (!connectionTest) {
return res.status(400).json({
error: 'Failed to connect to Nextcloud instance. Please check your credentials.'
});
}
const user = await CalendarUser.create({
name,
nextcloudUrl,
username,
password,
calendarName
});
// Return user without password
const { password: _, ...userWithoutPassword } = user.toJSON();
res.status(201).json(userWithoutPassword);
} catch (error) {
console.error('Error creating calendar user:', error);
if (error.name === 'SequelizeUniqueConstraintError') {
res.status(400).json({ error: 'User name already exists' });
} else {
res.status(500).json({ error: 'Failed to create calendar user' });
}
}
});
// Update a calendar user
router.put('/users/:id', async (req, res) => {
try {
const { id } = req.params;
const { name, nextcloudUrl, username, password, calendarName, isActive } = req.body;
const user = await CalendarUser.findByPk(id);
if (!user) {
return res.status(404).json({ error: 'Calendar user not found' });
}
// Test connection if credentials are being updated
if (nextcloudUrl || username || password) {
const testConfig = {
name: name || user.name,
nextcloudUrl: nextcloudUrl || user.nextcloudUrl,
username: username || user.username,
password: password || user.password,
calendarName: calendarName || user.calendarName
};
const connectionTest = await CalendarService.testConnection(testConfig);
if (!connectionTest) {
return res.status(400).json({
error: 'Failed to connect to Nextcloud instance. Please check your credentials.'
});
}
}
await user.update({
...(name && { name }),
...(nextcloudUrl && { nextcloudUrl }),
...(username && { username }),
...(password && { password }),
...(calendarName !== undefined && { calendarName }),
...(isActive !== undefined && { isActive })
});
// Return user without password
const { password: _, ...userWithoutPassword } = user.toJSON();
res.json(userWithoutPassword);
} catch (error) {
console.error('Error updating calendar user:', error);
res.status(500).json({ error: 'Failed to update calendar user' });
}
});
// Delete a calendar user
router.delete('/users/:id', async (req, res) => {
try {
const { id } = req.params;
const user = await CalendarUser.findByPk(id);
if (!user) {
return res.status(404).json({ error: 'Calendar user not found' });
}
await user.destroy();
res.json({ message: 'Calendar user deleted successfully' });
} catch (error) {
console.error('Error deleting calendar user:', error);
res.status(500).json({ error: 'Failed to delete calendar user' });
}
});
// Get calendar events for a specific month
// GET /api/calendar/events/:year/:month?user=username&type=family|individual
router.get('/events/:year/:month', async (req, res) => {
try {
const { year, month } = req.params;
const { user, type } = 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 (type === 'family') {
// Get events from all active users
const users = await CalendarUser.findAll({
where: { isActive: true },
attributes: ['name', 'nextcloudUrl', 'username', 'password', 'calendarName']
});
if (users.length === 0) {
return res.json([]);
}
// 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
if (!user) {
return res.status(400).json({
error: 'User parameter required for individual calendar view'
});
}
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' });
}
// 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);
res.status(500).json({ error: 'Failed to retrieve calendar events' });
}
});
// Create a new calendar event
// POST /api/calendar/events
router.post('/events', async (req, res) => {
try {
const { user, date, text } = req.body;
if (!user || !date || !text) {
return res.status(400).json({
error: 'Missing required fields: user, date, text'
});
}
// Validate date format (YYYY-MM-DD)
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
return res.status(400).json({
error: 'Invalid date format. Use YYYY-MM-DD.'
});
}
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 event = await CalendarService.createEvent(userConfig.toJSON(), date, text);
res.status(201).json(event);
} catch (error) {
console.error('Error creating calendar event:', error.message);
res.status(500).json({ error: 'Failed to create calendar event' });
}
});
// Update a calendar event
// PUT /api/calendar/events/:eventId
router.put('/events/:eventId', async (req, res) => {
try {
const { eventId } = req.params;
const { user, date, text } = req.body;
if (!user || !date || !text) {
return res.status(400).json({
error: 'Missing required fields: user, date, text'
});
}
// Validate date format (YYYY-MM-DD)
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
return res.status(400).json({
error: 'Invalid date format. Use YYYY-MM-DD.'
});
}
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 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' });
}
}
});
// Delete a calendar event
// DELETE /api/calendar/events/:eventId
router.delete('/events/:eventId', async (req, res) => {
try {
const { eventId } = req.params;
const { user } = req.body;
if (!user) {
return res.status(400).json({
error: 'Missing required field: user'
});
}
// 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']
});
if (!userConfig) {
return res.status(404).json({ error: 'User not found or inactive' });
}
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. 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' });
}
}
});
// Test Nextcloud connection for a user
router.post('/users/:id/test-connection', async (req, res) => {
try {
const { id } = req.params;
const user = await CalendarUser.findByPk(id);
if (!user) {
return res.status(404).json({ error: 'Calendar user not found' });
}
const connectionTest = await CalendarService.testConnection({
name: user.name,
nextcloudUrl: user.nextcloudUrl,
username: user.username,
password: user.password,
calendarName: user.calendarName
});
res.json({
success: connectionTest,
message: connectionTest ? 'Connection successful' : 'Connection failed'
});
} catch (error) {
console.error('Error testing connection:', error);
res.status(500).json({ error: 'Failed to test connection' });
}
});
// 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;