404 lines
13 KiB
JavaScript
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;
|