import { createDAVClient } from 'tsdav'; export interface CalendarEvent { uid: string; summary: string; description?: string; location?: string; startDate: Date; endDate: Date; status?: string; } export class NextcloudCalendarService { private client: any; private initialized: boolean = false; private initPromise: Promise | null = null; async initialize() { // Wenn bereits am Initialisieren, warte auf Abschluss if (this.initPromise) { return this.initPromise; } // Wenn bereits initialisiert, fertig if (this.initialized && this.client) { return Promise.resolve(); } // Neue Initialisierung starten this.initPromise = this._doInitialize(); try { await this.initPromise; } finally { this.initPromise = null; } } private async _doInitialize() { const serverUrl = process.env.NEXTCLOUD_URL; const username = process.env.NEXTCLOUD_USERNAME; const password = process.env.NEXTCLOUD_PASSWORD; console.log('πŸ” Nextcloud credentials check:'); console.log(' URL:', serverUrl); console.log(' Username:', username); console.log(' Password length:', password?.length, 'chars'); if (!serverUrl || !username || !password) { throw new Error('Nextcloud credentials not configured'); } try { console.log('⏳ Creating Nextcloud CalDAV client...'); this.client = await createDAVClient({ serverUrl: `${serverUrl}/remote.php/dav`, credentials: { username, password, }, authMethod: 'Basic', defaultAccountType: 'caldav', }); this.initialized = true; console.log('βœ… Nextcloud CalDAV client initialized successfully'); } catch (error: any) { console.error('❌ Failed to initialize Nextcloud CalDAV client:', error); this.initialized = false; this.client = null; throw new Error(`Nextcloud initialization failed: ${error.message}`); } } async getCalendars() { await this.initialize(); try { const calendars = await this.client.fetchCalendars(); return calendars; } catch (error) { console.error('Error fetching calendars:', error); throw error; } } async createEvent(calendarUrl: string, event: CalendarEvent) { await this.initialize(); const icsContent = this.generateICS(event); try { const result = await this.client.createCalendarObject({ calendar: { url: calendarUrl }, filename: `${event.uid}.ics`, iCalString: icsContent, }); console.log('βœ… Event created in Nextcloud:', event.summary); return result; } catch (error) { console.error('Error creating event:', error); throw error; } } async updateEvent(calendarUrl: string, event: CalendarEvent) { await this.initialize(); const icsContent = this.generateICS(event); try { const result = await this.client.updateCalendarObject({ calendar: { url: calendarUrl }, filename: `${event.uid}.ics`, iCalString: icsContent, }); console.log('βœ… Event updated in Nextcloud:', event.summary); return result; } catch (error) { console.error('Error updating event:', error); throw error; } } async deleteEvent(calendarUrl: string, eventUid: string) { await this.initialize(); try { await this.client.deleteCalendarObject({ calendar: { url: calendarUrl }, filename: `${eventUid}.ics`, }); console.log('βœ… Event deleted from Nextcloud:', eventUid); } catch (error) { console.error('Error deleting event:', error); throw error; } } async syncBookingToCalendar(booking: any) { await this.initialize(); const calendars = await this.getCalendars(); if (calendars.length === 0) { throw new Error('No calendars found in Nextcloud'); } // Suche nach "Buchungen" Kalender, sonst verwende ersten let calendar = calendars.find((cal: any) => cal.displayName?.toLowerCase().includes('buchung') ); if (!calendar) { console.warn('⚠️ Kein "Buchungen"-Kalender gefunden, verwende:', calendars[0].displayName); calendar = calendars[0]; } else { console.log('βœ… Verwende Kalender:', calendar.displayName); } const event: CalendarEvent = { uid: `savethemoment-booking-${booking.id}`, summary: `${booking.customerName} - ${booking.location?.name || 'Unbekannt'}`, description: ` Buchung #${booking.bookingNumber || booking.id} Kunde: ${booking.customerName} E-Mail: ${booking.customerEmail} Telefon: ${booking.customerPhone || 'N/A'} Event-Location: ${booking.eventLocation || booking.eventAddress} Status: ${booking.status} Fotobox: ${booking.photobox?.model || 'Keine Box'} Standort: ${booking.location?.name || 'Unbekannt'} Preis: ${booking.calculatedPrice || 0}€ `.trim(), location: `${booking.eventAddress || ''}, ${booking.eventZip || ''} ${booking.eventCity || ''}`.trim(), startDate: new Date(booking.eventDate), endDate: new Date(new Date(booking.eventDate).getTime() + 4 * 60 * 60 * 1000), status: booking.status, }; try { await this.createEvent(calendar.url, event); console.log('βœ… Event in Nextcloud erstellt:', event.summary); return event; } catch (error: any) { if (error.message?.includes('already exists') || error.response?.status === 412) { await this.updateEvent(calendar.url, event); console.log('βœ… Event in Nextcloud aktualisiert:', event.summary); return event; } throw error; } } async removeBookingFromCalendar(bookingId: string) { await this.initialize(); const calendars = await this.getCalendars(); if (calendars.length === 0) { throw new Error('No calendars found in Nextcloud'); } // Suche nach "Buchungen" Kalender, sonst verwende ersten let calendar = calendars.find((cal: any) => cal.displayName?.toLowerCase().includes('buchung') ); if (!calendar) { calendar = calendars[0]; } const eventUid = `savethemoment-booking-${bookingId}`; try { await this.deleteEvent(calendar.url, eventUid); console.log('βœ… Event aus Nextcloud gelΓΆscht:', eventUid); } catch (error) { console.error('Error removing booking from calendar:', error); } } private generateICS(event: CalendarEvent): string { const formatDate = (date: Date) => { return date.toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z'; }; const now = new Date(); const dtstamp = formatDate(now); const dtstart = formatDate(event.startDate); const dtend = formatDate(event.endDate); return `BEGIN:VCALENDAR VERSION:2.0 PRODID:-//SaveTheMoment Atlas//DE BEGIN:VEVENT UID:${event.uid} DTSTAMP:${dtstamp} DTSTART:${dtstart} DTEND:${dtend} SUMMARY:${event.summary} DESCRIPTION:${event.description || ''} LOCATION:${event.location || ''} STATUS:${event.status === 'CONFIRMED' ? 'CONFIRMED' : 'TENTATIVE'} END:VEVENT END:VCALENDAR`; } } export const nextcloudCalendar = new NextcloudCalendarService();