// Calendar Service for Nextcloud CalDAV integration // This is a basic implementation that can be enhanced with the @nextcloud/cdav-library const crypto = require('crypto'); const https = require('https'); const http = require('http'); const { URL } = require('url'); class CalendarService { constructor() { this.events = new Map(); // In-memory storage for demo purposes // TODO: Replace with actual CalDAV operations when library is available } /** * Create a calendar event * @param {Object} userConfig - Nextcloud user configuration * @param {string} date - Date in YYYY-MM-DD format * @param {string} text - Event description * @returns {Promise} Created event */ async createEvent(userConfig, date, text) { try { // TODO: Implement actual CalDAV event creation // For now, simulate the operation with local storage const eventId = crypto.randomUUID(); const event = { id: eventId, date: date, text: text, user: userConfig.name, summary: text, dtstart: this._formatDateForCalDAV(date), dtend: this._formatDateForCalDAV(date), createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; this.events.set(eventId, event); console.log(`Calendar event created for ${userConfig.name}: ${text} on ${date}`); // TODO: When CalDAV library is available, replace with: // const caldavEvent = await this._createCalDAVEvent(userConfig, event); return event; } catch (error) { console.error('Error creating calendar event:', error); throw new Error('Failed to create calendar event'); } } /** * Get calendar events for a specific month * @param {Object} userConfig - Nextcloud user configuration * @param {string} year - Year (YYYY) * @param {string} month - Month (MM) * @returns {Promise} Array of events */ async getEventsForMonth(userConfig, year, month) { try { // TODO: Implement actual CalDAV event retrieval // For now, filter local events const events = Array.from(this.events.values()); const filteredEvents = events.filter(event => { if (event.user !== userConfig.name) return false; const eventDate = new Date(event.date); return eventDate.getFullYear() === parseInt(year) && (eventDate.getMonth() + 1) === parseInt(month); }); console.log(`Retrieved ${filteredEvents.length} events for ${userConfig.name} in ${year}-${month}`); // TODO: When CalDAV library is available, replace with: // const caldavEvents = await this._fetchCalDAVEvents(userConfig, year, month); return filteredEvents; } catch (error) { console.error('Error retrieving calendar events:', error); throw new Error('Failed to retrieve calendar events'); } } /** * Get calendar events for all users (family view) * @param {Array} userConfigs - Array of user configurations * @param {string} year - Year (YYYY) * @param {string} month - Month (MM) * @returns {Promise} Array of events from all users */ async getFamilyEventsForMonth(userConfigs, year, month) { try { const allEvents = []; for (const userConfig of userConfigs) { const userEvents = await this.getEventsForMonth(userConfig, year, month); allEvents.push(...userEvents); } // Sort events by date allEvents.sort((a, b) => new Date(a.date) - new Date(b.date)); console.log(`Retrieved ${allEvents.length} family events for ${year}-${month}`); return allEvents; } catch (error) { console.error('Error retrieving family calendar events:', error); throw new Error('Failed to retrieve family calendar events'); } } /** * Update a calendar event * @param {Object} userConfig - Nextcloud user configuration * @param {string} eventId - Event ID * @param {string} date - New date * @param {string} text - New text * @returns {Promise} Updated event */ async updateEvent(userConfig, eventId, date, text) { try { const event = this.events.get(eventId); if (!event) { throw new Error('Event not found'); } if (event.user !== userConfig.name) { throw new Error('Permission denied: You can only edit your own events'); } event.date = date; event.text = text; event.summary = text; event.dtstart = this._formatDateForCalDAV(date); event.dtend = this._formatDateForCalDAV(date); event.updatedAt = new Date().toISOString(); this.events.set(eventId, event); console.log(`Calendar event updated for ${userConfig.name}: ${eventId}`); // TODO: When CalDAV library is available, replace with: // await this._updateCalDAVEvent(userConfig, event); return event; } catch (error) { console.error('Error updating calendar event:', error); throw error; } } /** * Delete a calendar event * @param {Object} userConfig - Nextcloud user configuration * @param {string} eventId - Event ID * @returns {Promise} Success status */ async deleteEvent(userConfig, eventId) { try { const event = this.events.get(eventId); if (!event) { throw new Error('Event not found'); } if (event.user !== userConfig.name) { throw new Error('Permission denied: You can only delete your own events'); } this.events.delete(eventId); console.log(`Calendar event deleted for ${userConfig.name}: ${eventId}`); // TODO: When CalDAV library is available, replace with: // await this._deleteCalDAVEvent(userConfig, eventId); return true; } catch (error) { console.error('Error deleting calendar event:', error); throw error; } } /** * Test connection to Nextcloud instance * @param {Object} userConfig - Nextcloud user configuration * @returns {Promise} Connection status */ async testConnection(userConfig) { try { // Basic HTTP test to verify Nextcloud instance is reachable const url = new URL(userConfig.nextcloudUrl); const options = { hostname: url.hostname, port: url.port || (url.protocol === 'https:' ? 443 : 80), path: '/status.php', method: 'GET', timeout: 5000, headers: { 'User-Agent': 'OpenWall/1.0' } }; const protocol = url.protocol === 'https:' ? https : http; return new Promise((resolve) => { const req = protocol.request(options, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { try { const status = JSON.parse(data); const isNextcloud = status.productname && status.productname.toLowerCase().includes('nextcloud'); console.log(`Connection test for ${userConfig.name}: ${isNextcloud ? 'SUCCESS' : 'NOT_NEXTCLOUD'}`); resolve(isNextcloud); } catch (e) { console.log(`Connection test for ${userConfig.name}: INVALID_RESPONSE`); resolve(false); } }); }); req.on('error', (error) => { console.log(`Connection test for ${userConfig.name}: ERROR - ${error.message}`); resolve(false); }); req.on('timeout', () => { console.log(`Connection test for ${userConfig.name}: TIMEOUT`); req.destroy(); resolve(false); }); req.end(); }); } catch (error) { console.error('Error testing connection:', error); return false; } } /** * Format date for CalDAV (YYYYMMDD format for all-day events) * @private */ _formatDateForCalDAV(dateString) { const date = new Date(dateString); return date.toISOString().slice(0, 10).replace(/-/g, ''); } // TODO: Implement these methods when @nextcloud/cdav-library is available /* async _createCalDAVEvent(userConfig, event) { const { createClient } = require('@nextcloud/cdav-library'); const client = createClient({ url: userConfig.nextcloudUrl, username: userConfig.username, password: userConfig.password }); // Create ICS event content const icsContent = this._generateICSEvent(event); // Create event on Nextcloud const result = await client.createEvent(icsContent); return result; } async _fetchCalDAVEvents(userConfig, year, month) { const { createClient } = require('@nextcloud/cdav-library'); const client = createClient({ url: userConfig.nextcloudUrl, username: userConfig.username, password: userConfig.password }); const startDate = new Date(year, month - 1, 1); const endDate = new Date(year, month, 0); const events = await client.fetchCalendarEvents({ start: startDate, end: endDate }); return events; } _generateICSEvent(event) { return `BEGIN:VCALENDAR VERSION:2.0 PRODID:-//OpenWall//Calendar//EN BEGIN:VEVENT UID:${event.id} DTSTART;VALUE=DATE:${event.dtstart} DTEND;VALUE=DATE:${event.dtend} SUMMARY:${event.summary} DESCRIPTION:${event.text} CREATED:${new Date(event.createdAt).toISOString().replace(/[-:]/g, '').split('.')[0]}Z LAST-MODIFIED:${new Date(event.updatedAt).toISOString().replace(/[-:]/g, '').split('.')[0]}Z END:VEVENT END:VCALENDAR`; } */ } module.exports = new CalendarService();