Files
Atlas/lib/nextcloud-calendar.ts
Julia Wehden 5af1a65815
Some checks failed
Preview Deploy / deploy (push) Failing after 2m5s
fix: Nextcloud Kalender auf 'Veranstaltungen' umgestellt
2026-03-24 12:53:42 +01:00

254 lines
7.1 KiB
TypeScript

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<void> | 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('veranstaltung')
);
if (!calendar) {
console.warn('⚠️ Kein "Veranstaltungen"-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();