import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription, IHttpRequestMethods, NodeApiError, NodeOperationError, } from 'n8n-workflow'; interface LibreBookingSession { sessionToken: string; userId: number; sessionExpires: string; } /** * Authentifizierung bei LibreBooking */ async function authenticate( executeFunctions: IExecuteFunctions, baseUrl: string, username: string, password: string, ): Promise { try { const response = await executeFunctions.helpers.httpRequest({ method: 'POST', url: `${baseUrl}/Web/Services/index.php/Authentication/Authenticate`, headers: { 'Content-Type': 'application/json' }, body: { username, password }, json: true, }); if (!response.isAuthenticated) { throw new NodeOperationError( executeFunctions.getNode(), 'Authentifizierung fehlgeschlagen. Bitte überprüfen Sie Ihre Credentials.', ); } return { sessionToken: response.sessionToken, userId: response.userId, sessionExpires: response.sessionExpires, }; } catch (error: any) { throw new NodeApiError(executeFunctions.getNode(), error, { message: 'Authentifizierung fehlgeschlagen', description: 'Überprüfen Sie die LibreBooking URL und Ihre Zugangsdaten.', }); } } /** * Abmeldung von LibreBooking */ async function signOut( executeFunctions: IExecuteFunctions, baseUrl: string, session: LibreBookingSession, ): Promise { try { await executeFunctions.helpers.httpRequest({ method: 'POST', url: `${baseUrl}/Web/Services/index.php/Authentication/SignOut`, headers: { 'Content-Type': 'application/json' }, body: { userId: session.userId, sessionToken: session.sessionToken, }, json: true, }); } catch (error) { // Ignoriere SignOut-Fehler } } /** * API-Request mit Session-Authentifizierung */ async function makeApiRequest( executeFunctions: IExecuteFunctions, baseUrl: string, session: LibreBookingSession, method: IHttpRequestMethods, endpoint: string, body?: any, qs?: any, ): Promise { const options: any = { method, url: `${baseUrl}/Web/Services/index.php${endpoint}`, headers: { 'Content-Type': 'application/json', 'X-Booked-SessionToken': session.sessionToken, 'X-Booked-UserId': session.userId.toString(), }, json: true, }; if (body && Object.keys(body).length > 0) { options.body = body; } if (qs && Object.keys(qs).length > 0) { options.qs = qs; } try { return await executeFunctions.helpers.httpRequest(options); } catch (error: any) { if (error.statusCode === 401) { throw new NodeApiError(executeFunctions.getNode(), error, { message: 'Authentifizierung abgelaufen', description: 'Der Session-Token ist abgelaufen. Bitte erneut ausführen.', }); } else if (error.statusCode === 403) { throw new NodeApiError(executeFunctions.getNode(), error, { message: 'Zugriff verweigert', description: 'Sie haben keine Berechtigung für diese Operation. Admin-Rechte erforderlich?', }); } else if (error.statusCode === 404) { throw new NodeApiError(executeFunctions.getNode(), error, { message: 'Nicht gefunden', description: 'Die angeforderte Ressource wurde nicht gefunden.', }); } throw new NodeApiError(executeFunctions.getNode(), error, { message: `API-Fehler: ${error.message}`, }); } } /** * Hilfsfunktion: String zu Array von Zahlen */ function parseIdList(value: string | undefined): number[] { if (!value || value.trim() === '') return []; return value.split(',').map(id => parseInt(id.trim(), 10)).filter(id => !isNaN(id)); } /** * LibreBooking n8n Node * * Vollständige Integration für die LibreBooking API. * Unterstützt alle wichtigen Ressourcen und Operationen. */ export class LibreBooking implements INodeType { description: INodeTypeDescription = { displayName: 'LibreBooking', name: 'libreBooking', icon: 'file:librebooking.svg', group: ['transform'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Verwalten Sie Reservierungen, Ressourcen, Benutzer und mehr mit LibreBooking', defaults: { name: 'LibreBooking', }, inputs: ['main'], outputs: ['main'], credentials: [ { name: 'libreBookingApi', required: true, }, ], properties: [ // ===================================================== // RESOURCE SELECTOR // ===================================================== { displayName: 'Ressource', name: 'resource', type: 'options', noDataExpression: true, options: [ { name: 'Reservierung', value: 'reservation', description: 'Reservierungen verwalten', }, { name: 'Ressource', value: 'resource', description: 'Ressourcen (Räume, Equipment) verwalten', }, { name: 'Zeitplan', value: 'schedule', description: 'Zeitpläne abrufen', }, { name: 'Benutzer', value: 'user', description: 'Benutzer verwalten (Admin-Rechte erforderlich)', }, { name: 'Konto', value: 'account', description: 'Eigenes Konto verwalten', }, { name: 'Gruppe', value: 'group', description: 'Benutzergruppen verwalten', }, { name: 'Zubehör', value: 'accessory', description: 'Zubehör abrufen', }, { name: 'Attribut', value: 'attribute', description: 'Benutzerdefinierte Attribute verwalten', }, ], default: 'reservation', }, // ===================================================== // RESERVATION OPERATIONS // ===================================================== { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['reservation'], }, }, options: [ { name: 'Erstellen', value: 'create', description: 'Neue Reservierung erstellen', action: 'Reservierung erstellen' }, { name: 'Abrufen', value: 'get', description: 'Reservierung abrufen', action: 'Reservierung abrufen' }, { name: 'Alle Abrufen', value: 'getAll', description: 'Alle Reservierungen abrufen', action: 'Alle Reservierungen abrufen' }, { name: 'Aktualisieren', value: 'update', description: 'Reservierung aktualisieren', action: 'Reservierung aktualisieren' }, { name: 'Löschen', value: 'delete', description: 'Reservierung löschen', action: 'Reservierung löschen' }, { name: 'Genehmigen', value: 'approve', description: 'Ausstehende Reservierung genehmigen', action: 'Reservierung genehmigen' }, { name: 'Check-In', value: 'checkIn', description: 'In Reservierung einchecken', action: 'In Reservierung einchecken' }, { name: 'Check-Out', value: 'checkOut', description: 'Aus Reservierung auschecken', action: 'Aus Reservierung auschecken' }, ], default: 'getAll', }, // ===================================================== // RESOURCE OPERATIONS // ===================================================== { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['resource'] } }, options: [ { name: 'Alle Abrufen', value: 'getAll', description: 'Alle Ressourcen abrufen', action: 'Alle Ressourcen abrufen' }, { name: 'Abrufen', value: 'get', description: 'Ressource abrufen', action: 'Ressource abrufen' }, { name: 'Verfügbarkeit Prüfen', value: 'getAvailability', description: 'Verfügbarkeit von Ressourcen prüfen', action: 'Verfügbarkeit prüfen' }, { name: 'Gruppen Abrufen', value: 'getGroups', description: 'Ressourcen-Gruppen abrufen', action: 'Ressourcen-Gruppen abrufen' }, { name: 'Typen Abrufen', value: 'getTypes', description: 'Ressourcen-Typen abrufen', action: 'Ressourcen-Typen abrufen' }, { name: 'Status Abrufen', value: 'getStatuses', description: 'Verfügbare Status abrufen', action: 'Status abrufen' }, { name: 'Erstellen', value: 'create', description: 'Neue Ressource erstellen (Admin)', action: 'Ressource erstellen' }, { name: 'Aktualisieren', value: 'update', description: 'Ressource aktualisieren (Admin)', action: 'Ressource aktualisieren' }, { name: 'Löschen', value: 'delete', description: 'Ressource löschen (Admin)', action: 'Ressource löschen' }, ], default: 'getAll', }, // ===================================================== // SCHEDULE OPERATIONS // ===================================================== { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['schedule'] } }, options: [ { name: 'Alle Abrufen', value: 'getAll', description: 'Alle Zeitpläne abrufen', action: 'Alle Zeitpläne abrufen' }, { name: 'Abrufen', value: 'get', description: 'Zeitplan abrufen', action: 'Zeitplan abrufen' }, { name: 'Slots Abrufen', value: 'getSlots', description: 'Verfügbare Slots abrufen', action: 'Slots abrufen' }, ], default: 'getAll', }, // ===================================================== // USER OPERATIONS // ===================================================== { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['user'] } }, options: [ { name: 'Alle Abrufen', value: 'getAll', description: 'Alle Benutzer abrufen', action: 'Alle Benutzer abrufen' }, { name: 'Abrufen', value: 'get', description: 'Benutzer abrufen', action: 'Benutzer abrufen' }, { name: 'Erstellen', value: 'create', description: 'Neuen Benutzer erstellen (Admin)', action: 'Benutzer erstellen' }, { name: 'Aktualisieren', value: 'update', description: 'Benutzer aktualisieren (Admin)', action: 'Benutzer aktualisieren' }, { name: 'Passwort Ändern', value: 'updatePassword', description: 'Benutzer-Passwort ändern (Admin)', action: 'Passwort ändern' }, { name: 'Löschen', value: 'delete', description: 'Benutzer löschen (Admin)', action: 'Benutzer löschen' }, ], default: 'getAll', }, // ===================================================== // ACCOUNT OPERATIONS // ===================================================== { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['account'] } }, options: [ { name: 'Abrufen', value: 'get', description: 'Eigene Kontoinformationen abrufen', action: 'Konto abrufen' }, { name: 'Erstellen', value: 'create', description: 'Neues Konto erstellen (Registrierung)', action: 'Konto erstellen' }, { name: 'Aktualisieren', value: 'update', description: 'Eigenes Konto aktualisieren', action: 'Konto aktualisieren' }, { name: 'Passwort Ändern', value: 'updatePassword', description: 'Eigenes Passwort ändern', action: 'Passwort ändern' }, ], default: 'get', }, // ===================================================== // GROUP OPERATIONS // ===================================================== { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['group'] } }, options: [ { name: 'Alle Abrufen', value: 'getAll', description: 'Alle Gruppen abrufen', action: 'Alle Gruppen abrufen' }, { name: 'Abrufen', value: 'get', description: 'Gruppe abrufen', action: 'Gruppe abrufen' }, { name: 'Erstellen', value: 'create', description: 'Neue Gruppe erstellen (Admin)', action: 'Gruppe erstellen' }, { name: 'Aktualisieren', value: 'update', description: 'Gruppe aktualisieren (Admin)', action: 'Gruppe aktualisieren' }, { name: 'Löschen', value: 'delete', description: 'Gruppe löschen (Admin)', action: 'Gruppe löschen' }, { name: 'Rollen Ändern', value: 'changeRoles', description: 'Gruppenrollen ändern (Admin)', action: 'Rollen ändern' }, { name: 'Berechtigungen Ändern', value: 'changePermissions', description: 'Gruppenberechtigungen ändern (Admin)', action: 'Berechtigungen ändern' }, { name: 'Benutzer Ändern', value: 'changeUsers', description: 'Gruppenbenutzer ändern (Admin)', action: 'Benutzer ändern' }, ], default: 'getAll', }, // ===================================================== // ACCESSORY OPERATIONS // ===================================================== { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['accessory'] } }, options: [ { name: 'Alle Abrufen', value: 'getAll', description: 'Alle Zubehörteile abrufen', action: 'Alle Zubehörteile abrufen' }, { name: 'Abrufen', value: 'get', description: 'Zubehörteil abrufen', action: 'Zubehörteil abrufen' }, ], default: 'getAll', }, // ===================================================== // ATTRIBUTE OPERATIONS // ===================================================== { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['attribute'] } }, options: [ { name: 'Abrufen', value: 'get', description: 'Attribut abrufen', action: 'Attribut abrufen' }, { name: 'Nach Kategorie Abrufen', value: 'getByCategory', description: 'Attribute einer Kategorie abrufen', action: 'Attribute nach Kategorie abrufen' }, { name: 'Erstellen', value: 'create', description: 'Neues Attribut erstellen (Admin)', action: 'Attribut erstellen' }, { name: 'Aktualisieren', value: 'update', description: 'Attribut aktualisieren (Admin)', action: 'Attribut aktualisieren' }, { name: 'Löschen', value: 'delete', description: 'Attribut löschen (Admin)', action: 'Attribut löschen' }, ], default: 'getByCategory', }, // ===================================================== // RESERVATION PARAMETERS // ===================================================== { displayName: 'Referenznummer', name: 'referenceNumber', type: 'string', required: true, displayOptions: { show: { resource: ['reservation'], operation: ['get', 'update', 'delete', 'approve', 'checkIn', 'checkOut'] } }, default: '', description: 'Die eindeutige Referenznummer der Reservierung', }, { displayName: 'Ressourcen-ID', name: 'resourceId', type: 'number', required: true, displayOptions: { show: { resource: ['reservation'], operation: ['create'] } }, default: 1, description: 'Die ID der zu reservierenden Ressource', }, { displayName: 'Startzeit', name: 'startDateTime', type: 'dateTime', required: true, displayOptions: { show: { resource: ['reservation'], operation: ['create', 'update'] } }, default: '', description: 'Startzeitpunkt der Reservierung (ISO 8601 Format)', }, { displayName: 'Endzeit', name: 'endDateTime', type: 'dateTime', required: true, displayOptions: { show: { resource: ['reservation'], operation: ['create', 'update'] } }, default: '', description: 'Endzeitpunkt der Reservierung (ISO 8601 Format)', }, { displayName: 'Titel', name: 'title', type: 'string', displayOptions: { show: { resource: ['reservation'], operation: ['create', 'update'] } }, default: '', description: 'Titel der Reservierung', }, { displayName: 'Aktualisierungsbereich', name: 'updateScope', type: 'options', displayOptions: { show: { resource: ['reservation'], operation: ['update', 'delete'] } }, options: [ { name: 'Nur Diese', value: 'this', description: 'Nur diese Instanz ändern' }, { name: 'Zukünftige', value: 'future', description: 'Diese und alle zukünftigen Instanzen ändern' }, { name: 'Alle', value: 'full', description: 'Alle Instanzen der Serie ändern' }, ], default: 'this', }, { displayName: 'Zusätzliche Felder', name: 'additionalFields', type: 'collection', placeholder: 'Feld hinzufügen', default: {}, displayOptions: { show: { resource: ['reservation'], operation: ['create', 'update'] } }, options: [ { displayName: 'Beschreibung', name: 'description', type: 'string', default: '' }, { displayName: 'Benutzer-ID', name: 'userId', type: 'number', default: '' }, { displayName: 'Zusätzliche Ressourcen', name: 'resources', type: 'string', default: '', description: 'Komma-getrennte Liste' }, { displayName: 'Teilnehmer', name: 'participants', type: 'string', default: '', description: 'Komma-getrennte Benutzer-IDs' }, { displayName: 'Eingeladene', name: 'invitees', type: 'string', default: '', description: 'Komma-getrennte Benutzer-IDs' }, { displayName: 'Teilnahme Erlauben', name: 'allowParticipation', type: 'boolean', default: true }, { displayName: 'Nutzungsbedingungen Akzeptiert', name: 'termsAccepted', type: 'boolean', default: true }, ], }, { displayName: 'Filter', name: 'filters', type: 'collection', placeholder: 'Filter hinzufügen', default: {}, displayOptions: { show: { resource: ['reservation'], operation: ['getAll'] } }, options: [ { displayName: 'Benutzer-ID', name: 'userId', type: 'number', default: '' }, { displayName: 'Ressourcen-ID', name: 'resourceId', type: 'number', default: '' }, { displayName: 'Zeitplan-ID', name: 'scheduleId', type: 'number', default: '' }, { displayName: 'Startzeit', name: 'startDateTime', type: 'dateTime', default: '' }, { displayName: 'Endzeit', name: 'endDateTime', type: 'dateTime', default: '' }, ], }, // ===================================================== // RESOURCE PARAMETERS // ===================================================== { displayName: 'Ressourcen-ID', name: 'resourceIdParam', type: 'number', required: true, displayOptions: { show: { resource: ['resource'], operation: ['get', 'update', 'delete'] } }, default: 1, }, { displayName: 'Ressourcen-ID (Optional)', name: 'resourceIdOptional', type: 'number', displayOptions: { show: { resource: ['resource'], operation: ['getAvailability'] } }, default: '', }, { displayName: 'Datum/Zeit', name: 'availabilityDateTime', type: 'dateTime', displayOptions: { show: { resource: ['resource'], operation: ['getAvailability'] } }, default: '', }, { displayName: 'Ressourcen-Name', name: 'resourceName', type: 'string', required: true, displayOptions: { show: { resource: ['resource'], operation: ['create', 'update'] } }, default: '', }, { displayName: 'Zeitplan-ID', name: 'scheduleIdForResource', type: 'number', required: true, displayOptions: { show: { resource: ['resource'], operation: ['create'] } }, default: 1, }, { displayName: 'Ressourcen-Optionen', name: 'resourceOptions', type: 'collection', placeholder: 'Option hinzufügen', default: {}, displayOptions: { show: { resource: ['resource'], operation: ['create', 'update'] } }, options: [ { displayName: 'Standort', name: 'location', type: 'string', default: '' }, { displayName: 'Kontakt', name: 'contact', type: 'string', default: '' }, { displayName: 'Beschreibung', name: 'description', type: 'string', default: '' }, { displayName: 'Notizen', name: 'notes', type: 'string', default: '' }, { displayName: 'Max. Teilnehmer', name: 'maxParticipants', type: 'number', default: 0 }, { displayName: 'Genehmigung Erforderlich', name: 'requiresApproval', type: 'boolean', default: false }, { displayName: 'Mehrtägig Erlauben', name: 'allowMultiday', type: 'boolean', default: false }, { displayName: 'Check-In Erforderlich', name: 'requiresCheckIn', type: 'boolean', default: false }, { displayName: 'Auto-Release Minuten', name: 'autoReleaseMinutes', type: 'number', default: 0 }, { displayName: 'Farbe', name: 'color', type: 'string', default: '' }, { displayName: 'Status-ID', name: 'statusId', type: 'options', options: [{ name: 'Versteckt', value: 0 }, { name: 'Verfügbar', value: 1 }, { name: 'Nicht Verfügbar', value: 2 }], default: 1 }, ], }, // ===================================================== // SCHEDULE PARAMETERS // ===================================================== { displayName: 'Zeitplan-ID', name: 'scheduleId', type: 'number', required: true, displayOptions: { show: { resource: ['schedule'], operation: ['get', 'getSlots'] } }, default: 1, }, { displayName: 'Slots-Filter', name: 'slotsFilters', type: 'collection', placeholder: 'Filter hinzufügen', default: {}, displayOptions: { show: { resource: ['schedule'], operation: ['getSlots'] } }, options: [ { displayName: 'Ressourcen-ID', name: 'resourceId', type: 'number', default: '' }, { displayName: 'Startzeit', name: 'startDateTime', type: 'dateTime', default: '' }, { displayName: 'Endzeit', name: 'endDateTime', type: 'dateTime', default: '' }, ], }, // ===================================================== // USER PARAMETERS // ===================================================== { displayName: 'Benutzer-ID', name: 'userId', type: 'number', required: true, displayOptions: { show: { resource: ['user'], operation: ['get', 'update', 'updatePassword', 'delete'] } }, default: 1, }, { displayName: 'E-Mail', name: 'emailAddress', type: 'string', required: true, displayOptions: { show: { resource: ['user'], operation: ['create'] } }, default: '', }, { displayName: 'Benutzername', name: 'userName', type: 'string', required: true, displayOptions: { show: { resource: ['user'], operation: ['create'] } }, default: '', }, { displayName: 'Passwort', name: 'password', type: 'string', typeOptions: { password: true }, required: true, displayOptions: { show: { resource: ['user'], operation: ['create', 'updatePassword'] } }, default: '', }, { displayName: 'Vorname', name: 'firstName', type: 'string', required: true, displayOptions: { show: { resource: ['user'], operation: ['create', 'update'] } }, default: '', }, { displayName: 'Nachname', name: 'lastName', type: 'string', required: true, displayOptions: { show: { resource: ['user'], operation: ['create', 'update'] } }, default: '', }, { displayName: 'Benutzer-Filter', name: 'userFilters', type: 'collection', placeholder: 'Filter hinzufügen', default: {}, displayOptions: { show: { resource: ['user'], operation: ['getAll'] } }, options: [ { displayName: 'Benutzername', name: 'username', type: 'string', default: '' }, { displayName: 'E-Mail', name: 'email', type: 'string', default: '' }, { displayName: 'Vorname', name: 'firstName', type: 'string', default: '' }, { displayName: 'Nachname', name: 'lastName', type: 'string', default: '' }, { displayName: 'Organisation', name: 'organization', type: 'string', default: '' }, ], }, { displayName: 'Benutzer-Optionen', name: 'userOptions', type: 'collection', placeholder: 'Option hinzufügen', default: {}, displayOptions: { show: { resource: ['user'], operation: ['create', 'update'] } }, options: [ { displayName: 'Zeitzone', name: 'timezone', type: 'string', default: 'Europe/Berlin' }, { displayName: 'Sprache', name: 'language', type: 'string', default: 'de_de' }, { displayName: 'Telefon', name: 'phone', type: 'string', default: '' }, { displayName: 'Organisation', name: 'organization', type: 'string', default: '' }, { displayName: 'Position', name: 'position', type: 'string', default: '' }, { displayName: 'Gruppen', name: 'groups', type: 'string', default: '', description: 'Komma-getrennte Gruppen-IDs' }, ], }, // ===================================================== // ACCOUNT PARAMETERS // ===================================================== { displayName: 'Benutzer-ID', name: 'accountUserId', type: 'number', required: true, displayOptions: { show: { resource: ['account'], operation: ['get', 'update', 'updatePassword'] } }, default: '', }, { displayName: 'Account-Daten', name: 'accountData', type: 'collection', placeholder: 'Feld hinzufügen', default: {}, displayOptions: { show: { resource: ['account'], operation: ['create', 'update'] } }, options: [ { displayName: 'E-Mail', name: 'emailAddress', type: 'string', default: '' }, { displayName: 'Benutzername', name: 'userName', type: 'string', default: '' }, { displayName: 'Passwort', name: 'password', type: 'string', typeOptions: { password: true }, default: '' }, { displayName: 'Vorname', name: 'firstName', type: 'string', default: '' }, { displayName: 'Nachname', name: 'lastName', type: 'string', default: '' }, { displayName: 'Zeitzone', name: 'timezone', type: 'string', default: 'Europe/Berlin' }, { displayName: 'Sprache', name: 'language', type: 'string', default: 'de_de' }, { displayName: 'Telefon', name: 'phone', type: 'string', default: '' }, { displayName: 'Organisation', name: 'organization', type: 'string', default: '' }, { displayName: 'Position', name: 'position', type: 'string', default: '' }, { displayName: 'AGB Akzeptiert', name: 'acceptTermsOfService', type: 'boolean', default: true }, ], }, { displayName: 'Passwort-Änderung', name: 'passwordChange', type: 'fixedCollection', default: {}, displayOptions: { show: { resource: ['account'], operation: ['updatePassword'] } }, options: [ { name: 'passwords', displayName: 'Passwörter', values: [ { displayName: 'Aktuelles Passwort', name: 'currentPassword', type: 'string', typeOptions: { password: true }, default: '' }, { displayName: 'Neues Passwort', name: 'newPassword', type: 'string', typeOptions: { password: true }, default: '' }, ], }, ], }, // ===================================================== // GROUP PARAMETERS // ===================================================== { displayName: 'Gruppen-ID', name: 'groupId', type: 'number', required: true, displayOptions: { show: { resource: ['group'], operation: ['get', 'update', 'delete', 'changeRoles', 'changePermissions', 'changeUsers'] } }, default: 1, }, { displayName: 'Gruppen-Name', name: 'groupName', type: 'string', required: true, displayOptions: { show: { resource: ['group'], operation: ['create', 'update'] } }, default: '', }, { displayName: 'Standard-Gruppe', name: 'isDefault', type: 'boolean', displayOptions: { show: { resource: ['group'], operation: ['create', 'update'] } }, default: false, }, { displayName: 'Rollen-IDs', name: 'roleIds', type: 'string', displayOptions: { show: { resource: ['group'], operation: ['changeRoles'] } }, default: '', description: '1=Gruppenadmin, 2=App-Admin, 3=Ressourcen-Admin, 4=Zeitplan-Admin', }, { displayName: 'Ressourcen-IDs', name: 'permissionResourceIds', type: 'string', displayOptions: { show: { resource: ['group'], operation: ['changePermissions'] } }, default: '', }, { displayName: 'Benutzer-IDs', name: 'groupUserIds', type: 'string', displayOptions: { show: { resource: ['group'], operation: ['changeUsers'] } }, default: '', }, // ===================================================== // ACCESSORY PARAMETERS // ===================================================== { displayName: 'Zubehör-ID', name: 'accessoryId', type: 'number', required: true, displayOptions: { show: { resource: ['accessory'], operation: ['get'] } }, default: 1, }, // ===================================================== // ATTRIBUTE PARAMETERS // ===================================================== { displayName: 'Attribut-ID', name: 'attributeId', type: 'number', required: true, displayOptions: { show: { resource: ['attribute'], operation: ['get', 'update', 'delete'] } }, default: 1, }, { displayName: 'Kategorie-ID', name: 'categoryId', type: 'options', required: true, displayOptions: { show: { resource: ['attribute'], operation: ['getByCategory', 'create'] } }, options: [ { name: 'Reservierung', value: 1 }, { name: 'Benutzer', value: 2 }, { name: 'Ressource', value: 4 }, { name: 'Ressourcen-Typ', value: 5 }, ], default: 1, }, { displayName: 'Attribut-Label', name: 'attributeLabel', type: 'string', required: true, displayOptions: { show: { resource: ['attribute'], operation: ['create', 'update'] } }, default: '', }, { displayName: 'Attribut-Typ', name: 'attributeType', type: 'options', required: true, displayOptions: { show: { resource: ['attribute'], operation: ['create', 'update'] } }, options: [ { name: 'Einzeilig', value: 1 }, { name: 'Mehrzeilig', value: 2 }, { name: 'Auswahlliste', value: 3 }, { name: 'Checkbox', value: 4 }, { name: 'Datum/Zeit', value: 5 }, ], default: 1, }, { displayName: 'Attribut-Optionen', name: 'attributeOptions', type: 'collection', placeholder: 'Option hinzufügen', default: {}, displayOptions: { show: { resource: ['attribute'], operation: ['create', 'update'] } }, options: [ { displayName: 'Erforderlich', name: 'required', type: 'boolean', default: false }, { displayName: 'Nur Admin', name: 'adminOnly', type: 'boolean', default: false }, { displayName: 'Privat', name: 'isPrivate', type: 'boolean', default: false }, { displayName: 'Sortierung', name: 'sortOrder', type: 'number', default: 0 }, { displayName: 'Regex-Validierung', name: 'regex', type: 'string', default: '' }, { displayName: 'Mögliche Werte', name: 'possibleValues', type: 'string', default: '', description: 'Komma-getrennt' }, ], }, ], }; async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); const returnData: INodeExecutionData[] = []; const credentials = await this.getCredentials('libreBookingApi'); const baseUrl = (credentials.url as string).replace(/\/$/, ''); const username = credentials.username as string; const pw = credentials.password as string; const session = await authenticate(this, baseUrl, username, pw); try { for (let i = 0; i < items.length; i++) { try { const resource = this.getNodeParameter('resource', i) as string; const operation = this.getNodeParameter('operation', i) as string; let responseData: any; // RESERVATION if (resource === 'reservation') { if (operation === 'getAll') { const filters = this.getNodeParameter('filters', i, {}) as any; const qs: any = {}; if (filters.userId) qs.userId = filters.userId; if (filters.resourceId) qs.resourceId = filters.resourceId; if (filters.scheduleId) qs.scheduleId = filters.scheduleId; if (filters.startDateTime) qs.startDateTime = filters.startDateTime; if (filters.endDateTime) qs.endDateTime = filters.endDateTime; responseData = await makeApiRequest(this, baseUrl, session, 'GET', '/Reservations/', undefined, qs); } else if (operation === 'get') { const referenceNumber = this.getNodeParameter('referenceNumber', i) as string; responseData = await makeApiRequest(this, baseUrl, session, 'GET', `/Reservations/${referenceNumber}`); } else if (operation === 'create') { const resourceId = this.getNodeParameter('resourceId', i) as number; const startDateTime = this.getNodeParameter('startDateTime', i) as string; const endDateTime = this.getNodeParameter('endDateTime', i) as string; const title = this.getNodeParameter('title', i, '') as string; const additionalFields = this.getNodeParameter('additionalFields', i, {}) as any; const body: any = { resourceId, startDateTime: new Date(startDateTime).toISOString(), endDateTime: new Date(endDateTime).toISOString() }; if (title) body.title = title; if (additionalFields.description) body.description = additionalFields.description; if (additionalFields.userId) body.userId = additionalFields.userId; if (additionalFields.resources) body.resources = parseIdList(additionalFields.resources); if (additionalFields.participants) body.participants = parseIdList(additionalFields.participants); if (additionalFields.invitees) body.invitees = parseIdList(additionalFields.invitees); if (additionalFields.allowParticipation !== undefined) body.allowParticipation = additionalFields.allowParticipation; if (additionalFields.termsAccepted !== undefined) body.termsAccepted = additionalFields.termsAccepted; responseData = await makeApiRequest(this, baseUrl, session, 'POST', '/Reservations/', body); } else if (operation === 'update') { const referenceNumber = this.getNodeParameter('referenceNumber', i) as string; const startDateTime = this.getNodeParameter('startDateTime', i) as string; const endDateTime = this.getNodeParameter('endDateTime', i) as string; const title = this.getNodeParameter('title', i, '') as string; const updateScope = this.getNodeParameter('updateScope', i, 'this') as string; const additionalFields = this.getNodeParameter('additionalFields', i, {}) as any; const body: any = { startDateTime: new Date(startDateTime).toISOString(), endDateTime: new Date(endDateTime).toISOString() }; if (title) body.title = title; if (additionalFields.description) body.description = additionalFields.description; if (additionalFields.resourceId) body.resourceId = additionalFields.resourceId; if (additionalFields.userId) body.userId = additionalFields.userId; if (additionalFields.resources) body.resources = parseIdList(additionalFields.resources); if (additionalFields.participants) body.participants = parseIdList(additionalFields.participants); if (additionalFields.invitees) body.invitees = parseIdList(additionalFields.invitees); responseData = await makeApiRequest(this, baseUrl, session, 'POST', `/Reservations/${referenceNumber}?updateScope=${updateScope}`, body); } else if (operation === 'delete') { const referenceNumber = this.getNodeParameter('referenceNumber', i) as string; const updateScope = this.getNodeParameter('updateScope', i, 'this') as string; responseData = await makeApiRequest(this, baseUrl, session, 'DELETE', `/Reservations/${referenceNumber}?updateScope=${updateScope}`); } else if (operation === 'approve') { const referenceNumber = this.getNodeParameter('referenceNumber', i) as string; responseData = await makeApiRequest(this, baseUrl, session, 'POST', `/Reservations/${referenceNumber}/Approval`); } else if (operation === 'checkIn') { const referenceNumber = this.getNodeParameter('referenceNumber', i) as string; responseData = await makeApiRequest(this, baseUrl, session, 'POST', `/Reservations/${referenceNumber}/CheckIn`); } else if (operation === 'checkOut') { const referenceNumber = this.getNodeParameter('referenceNumber', i) as string; responseData = await makeApiRequest(this, baseUrl, session, 'POST', `/Reservations/${referenceNumber}/CheckOut`); } } // RESOURCE else if (resource === 'resource') { if (operation === 'getAll') { responseData = await makeApiRequest(this, baseUrl, session, 'GET', '/Resources/'); } else if (operation === 'get') { const resourceIdParam = this.getNodeParameter('resourceIdParam', i) as number; responseData = await makeApiRequest(this, baseUrl, session, 'GET', `/Resources/${resourceIdParam}`); } else if (operation === 'getAvailability') { const resourceIdOptional = this.getNodeParameter('resourceIdOptional', i, '') as number | ''; const availabilityDateTime = this.getNodeParameter('availabilityDateTime', i, '') as string; let endpoint = '/Resources/Availability'; if (resourceIdOptional) endpoint = `/Resources/${resourceIdOptional}/Availability`; const qs: any = {}; if (availabilityDateTime) qs.dateTime = new Date(availabilityDateTime).toISOString(); responseData = await makeApiRequest(this, baseUrl, session, 'GET', endpoint, undefined, qs); } else if (operation === 'getGroups') { responseData = await makeApiRequest(this, baseUrl, session, 'GET', '/Resources/Groups'); } else if (operation === 'getTypes') { responseData = await makeApiRequest(this, baseUrl, session, 'GET', '/Resources/Types'); } else if (operation === 'getStatuses') { responseData = await makeApiRequest(this, baseUrl, session, 'GET', '/Resources/Status'); } else if (operation === 'create') { const resourceName = this.getNodeParameter('resourceName', i) as string; const scheduleIdForResource = this.getNodeParameter('scheduleIdForResource', i) as number; const resourceOptions = this.getNodeParameter('resourceOptions', i, {}) as any; const body: any = { name: resourceName, scheduleId: scheduleIdForResource }; if (resourceOptions.location) body.location = resourceOptions.location; if (resourceOptions.contact) body.contact = resourceOptions.contact; if (resourceOptions.description) body.description = resourceOptions.description; if (resourceOptions.notes) body.notes = resourceOptions.notes; if (resourceOptions.maxParticipants) body.maxParticipants = resourceOptions.maxParticipants; if (resourceOptions.requiresApproval !== undefined) body.requiresApproval = resourceOptions.requiresApproval; if (resourceOptions.allowMultiday !== undefined) body.allowMultiday = resourceOptions.allowMultiday; if (resourceOptions.requiresCheckIn !== undefined) body.requiresCheckIn = resourceOptions.requiresCheckIn; if (resourceOptions.autoReleaseMinutes) body.autoReleaseMinutes = resourceOptions.autoReleaseMinutes; if (resourceOptions.color) body.color = resourceOptions.color; if (resourceOptions.statusId !== undefined) body.statusId = resourceOptions.statusId; responseData = await makeApiRequest(this, baseUrl, session, 'POST', '/Resources/', body); } else if (operation === 'update') { const resourceIdParam = this.getNodeParameter('resourceIdParam', i) as number; const resourceName = this.getNodeParameter('resourceName', i) as string; const resourceOptions = this.getNodeParameter('resourceOptions', i, {}) as any; const body: any = { name: resourceName }; if (resourceOptions.location) body.location = resourceOptions.location; if (resourceOptions.contact) body.contact = resourceOptions.contact; if (resourceOptions.description) body.description = resourceOptions.description; if (resourceOptions.notes) body.notes = resourceOptions.notes; if (resourceOptions.maxParticipants) body.maxParticipants = resourceOptions.maxParticipants; if (resourceOptions.requiresApproval !== undefined) body.requiresApproval = resourceOptions.requiresApproval; if (resourceOptions.allowMultiday !== undefined) body.allowMultiday = resourceOptions.allowMultiday; if (resourceOptions.requiresCheckIn !== undefined) body.requiresCheckIn = resourceOptions.requiresCheckIn; if (resourceOptions.autoReleaseMinutes) body.autoReleaseMinutes = resourceOptions.autoReleaseMinutes; if (resourceOptions.color) body.color = resourceOptions.color; if (resourceOptions.statusId !== undefined) body.statusId = resourceOptions.statusId; responseData = await makeApiRequest(this, baseUrl, session, 'POST', `/Resources/${resourceIdParam}`, body); } else if (operation === 'delete') { const resourceIdParam = this.getNodeParameter('resourceIdParam', i) as number; responseData = await makeApiRequest(this, baseUrl, session, 'DELETE', `/Resources/${resourceIdParam}`); } } // SCHEDULE else if (resource === 'schedule') { if (operation === 'getAll') { responseData = await makeApiRequest(this, baseUrl, session, 'GET', '/Schedules/'); } else if (operation === 'get') { const scheduleId = this.getNodeParameter('scheduleId', i) as number; responseData = await makeApiRequest(this, baseUrl, session, 'GET', `/Schedules/${scheduleId}`); } else if (operation === 'getSlots') { const scheduleId = this.getNodeParameter('scheduleId', i) as number; const slotsFilters = this.getNodeParameter('slotsFilters', i, {}) as any; const qs: any = {}; if (slotsFilters.resourceId) qs.resourceId = slotsFilters.resourceId; if (slotsFilters.startDateTime) qs.startDateTime = new Date(slotsFilters.startDateTime).toISOString(); if (slotsFilters.endDateTime) qs.endDateTime = new Date(slotsFilters.endDateTime).toISOString(); responseData = await makeApiRequest(this, baseUrl, session, 'GET', `/Schedules/${scheduleId}/Slots`, undefined, qs); } } // USER else if (resource === 'user') { if (operation === 'getAll') { const userFilters = this.getNodeParameter('userFilters', i, {}) as any; const qs: any = {}; if (userFilters.username) qs.username = userFilters.username; if (userFilters.email) qs.email = userFilters.email; if (userFilters.firstName) qs.firstName = userFilters.firstName; if (userFilters.lastName) qs.lastName = userFilters.lastName; if (userFilters.organization) qs.organization = userFilters.organization; responseData = await makeApiRequest(this, baseUrl, session, 'GET', '/Users/', undefined, qs); } else if (operation === 'get') { const userId = this.getNodeParameter('userId', i) as number; responseData = await makeApiRequest(this, baseUrl, session, 'GET', `/Users/${userId}`); } else if (operation === 'create') { const emailAddress = this.getNodeParameter('emailAddress', i) as string; const userName = this.getNodeParameter('userName', i) as string; const pw = this.getNodeParameter('password', i) as string; const firstName = this.getNodeParameter('firstName', i) as string; const lastName = this.getNodeParameter('lastName', i) as string; const userOptions = this.getNodeParameter('userOptions', i, {}) as any; const body: any = { emailAddress, userName, password: pw, firstName, lastName }; if (userOptions.timezone) body.timezone = userOptions.timezone; if (userOptions.language) body.language = userOptions.language; if (userOptions.phone) body.phone = userOptions.phone; if (userOptions.organization) body.organization = userOptions.organization; if (userOptions.position) body.position = userOptions.position; if (userOptions.groups) body.groups = parseIdList(userOptions.groups); responseData = await makeApiRequest(this, baseUrl, session, 'POST', '/Users/', body); } else if (operation === 'update') { const userId = this.getNodeParameter('userId', i) as number; const firstName = this.getNodeParameter('firstName', i) as string; const lastName = this.getNodeParameter('lastName', i) as string; const userOptions = this.getNodeParameter('userOptions', i, {}) as any; const body: any = { firstName, lastName }; if (userOptions.timezone) body.timezone = userOptions.timezone; if (userOptions.language) body.language = userOptions.language; if (userOptions.phone) body.phone = userOptions.phone; if (userOptions.organization) body.organization = userOptions.organization; if (userOptions.position) body.position = userOptions.position; if (userOptions.groups) body.groups = parseIdList(userOptions.groups); responseData = await makeApiRequest(this, baseUrl, session, 'POST', `/Users/${userId}`, body); } else if (operation === 'updatePassword') { const userId = this.getNodeParameter('userId', i) as number; const pw = this.getNodeParameter('password', i) as string; responseData = await makeApiRequest(this, baseUrl, session, 'POST', `/Users/${userId}/Password`, { password: pw }); } else if (operation === 'delete') { const userId = this.getNodeParameter('userId', i) as number; responseData = await makeApiRequest(this, baseUrl, session, 'DELETE', `/Users/${userId}`); } } // ACCOUNT else if (resource === 'account') { if (operation === 'get') { const accountUserId = this.getNodeParameter('accountUserId', i) as number; responseData = await makeApiRequest(this, baseUrl, session, 'GET', `/Accounts/${accountUserId}`); } else if (operation === 'create') { const accountData = this.getNodeParameter('accountData', i, {}) as any; const body: any = {}; if (accountData.emailAddress) body.emailAddress = accountData.emailAddress; if (accountData.userName) body.userName = accountData.userName; if (accountData.password) body.password = accountData.password; if (accountData.firstName) body.firstName = accountData.firstName; if (accountData.lastName) body.lastName = accountData.lastName; if (accountData.timezone) body.timezone = accountData.timezone; if (accountData.language) body.language = accountData.language; if (accountData.phone) body.phone = accountData.phone; if (accountData.organization) body.organization = accountData.organization; if (accountData.position) body.position = accountData.position; if (accountData.acceptTermsOfService !== undefined) body.acceptTermsOfService = accountData.acceptTermsOfService; responseData = await makeApiRequest(this, baseUrl, session, 'POST', '/Accounts/', body); } else if (operation === 'update') { const accountUserId = this.getNodeParameter('accountUserId', i) as number; const accountData = this.getNodeParameter('accountData', i, {}) as any; const body: any = {}; if (accountData.emailAddress) body.emailAddress = accountData.emailAddress; if (accountData.userName) body.userName = accountData.userName; if (accountData.firstName) body.firstName = accountData.firstName; if (accountData.lastName) body.lastName = accountData.lastName; if (accountData.timezone) body.timezone = accountData.timezone; if (accountData.language) body.language = accountData.language; if (accountData.phone) body.phone = accountData.phone; if (accountData.organization) body.organization = accountData.organization; if (accountData.position) body.position = accountData.position; responseData = await makeApiRequest(this, baseUrl, session, 'POST', `/Accounts/${accountUserId}`, body); } else if (operation === 'updatePassword') { const accountUserId = this.getNodeParameter('accountUserId', i) as number; const passwordChange = this.getNodeParameter('passwordChange', i, {}) as any; const passwords = passwordChange.passwords || {}; responseData = await makeApiRequest(this, baseUrl, session, 'POST', `/Accounts/${accountUserId}/Password`, { currentPassword: passwords.currentPassword, newPassword: passwords.newPassword, }); } } // GROUP else if (resource === 'group') { if (operation === 'getAll') { responseData = await makeApiRequest(this, baseUrl, session, 'GET', '/Groups/'); } else if (operation === 'get') { const groupId = this.getNodeParameter('groupId', i) as number; responseData = await makeApiRequest(this, baseUrl, session, 'GET', `/Groups/${groupId}`); } else if (operation === 'create') { const groupName = this.getNodeParameter('groupName', i) as string; const isDefault = this.getNodeParameter('isDefault', i, false) as boolean; responseData = await makeApiRequest(this, baseUrl, session, 'POST', '/Groups/', { name: groupName, isDefault }); } else if (operation === 'update') { const groupId = this.getNodeParameter('groupId', i) as number; const groupName = this.getNodeParameter('groupName', i) as string; const isDefault = this.getNodeParameter('isDefault', i, false) as boolean; responseData = await makeApiRequest(this, baseUrl, session, 'POST', `/Groups/${groupId}`, { name: groupName, isDefault }); } else if (operation === 'delete') { const groupId = this.getNodeParameter('groupId', i) as number; responseData = await makeApiRequest(this, baseUrl, session, 'DELETE', `/Groups/${groupId}`); } else if (operation === 'changeRoles') { const groupId = this.getNodeParameter('groupId', i) as number; const roleIds = this.getNodeParameter('roleIds', i, '') as string; responseData = await makeApiRequest(this, baseUrl, session, 'POST', `/Groups/${groupId}/Roles`, { roleIds: parseIdList(roleIds) }); } else if (operation === 'changePermissions') { const groupId = this.getNodeParameter('groupId', i) as number; const permissionResourceIds = this.getNodeParameter('permissionResourceIds', i, '') as string; responseData = await makeApiRequest(this, baseUrl, session, 'POST', `/Groups/${groupId}/Permissions`, { resourceIds: parseIdList(permissionResourceIds) }); } else if (operation === 'changeUsers') { const groupId = this.getNodeParameter('groupId', i) as number; const groupUserIds = this.getNodeParameter('groupUserIds', i, '') as string; responseData = await makeApiRequest(this, baseUrl, session, 'POST', `/Groups/${groupId}/Users`, { userIds: parseIdList(groupUserIds) }); } } // ACCESSORY else if (resource === 'accessory') { if (operation === 'getAll') { responseData = await makeApiRequest(this, baseUrl, session, 'GET', '/Accessories/'); } else if (operation === 'get') { const accessoryId = this.getNodeParameter('accessoryId', i) as number; responseData = await makeApiRequest(this, baseUrl, session, 'GET', `/Accessories/${accessoryId}`); } } // ATTRIBUTE else if (resource === 'attribute') { if (operation === 'get') { const attributeId = this.getNodeParameter('attributeId', i) as number; responseData = await makeApiRequest(this, baseUrl, session, 'GET', `/Attributes/${attributeId}`); } else if (operation === 'getByCategory') { const categoryId = this.getNodeParameter('categoryId', i) as number; responseData = await makeApiRequest(this, baseUrl, session, 'GET', `/Attributes/Category/${categoryId}`); } else if (operation === 'create') { const attributeLabel = this.getNodeParameter('attributeLabel', i) as string; const attributeType = this.getNodeParameter('attributeType', i) as number; const categoryId = this.getNodeParameter('categoryId', i) as number; const attributeOptions = this.getNodeParameter('attributeOptions', i, {}) as any; const body: any = { label: attributeLabel, type: attributeType, categoryId }; if (attributeOptions.required !== undefined) body.required = attributeOptions.required; if (attributeOptions.adminOnly !== undefined) body.adminOnly = attributeOptions.adminOnly; if (attributeOptions.isPrivate !== undefined) body.isPrivate = attributeOptions.isPrivate; if (attributeOptions.sortOrder !== undefined) body.sortOrder = attributeOptions.sortOrder; if (attributeOptions.regex) body.regex = attributeOptions.regex; if (attributeOptions.possibleValues) body.possibleValues = attributeOptions.possibleValues.split(',').map((v: string) => v.trim()); responseData = await makeApiRequest(this, baseUrl, session, 'POST', '/Attributes/', body); } else if (operation === 'update') { const attributeId = this.getNodeParameter('attributeId', i) as number; const attributeLabel = this.getNodeParameter('attributeLabel', i) as string; const attributeType = this.getNodeParameter('attributeType', i) as number; const attributeOptions = this.getNodeParameter('attributeOptions', i, {}) as any; const body: any = { label: attributeLabel, type: attributeType }; if (attributeOptions.required !== undefined) body.required = attributeOptions.required; if (attributeOptions.adminOnly !== undefined) body.adminOnly = attributeOptions.adminOnly; if (attributeOptions.isPrivate !== undefined) body.isPrivate = attributeOptions.isPrivate; if (attributeOptions.sortOrder !== undefined) body.sortOrder = attributeOptions.sortOrder; if (attributeOptions.regex) body.regex = attributeOptions.regex; if (attributeOptions.possibleValues) body.possibleValues = attributeOptions.possibleValues.split(',').map((v: string) => v.trim()); responseData = await makeApiRequest(this, baseUrl, session, 'POST', `/Attributes/${attributeId}`, body); } else if (operation === 'delete') { const attributeId = this.getNodeParameter('attributeId', i) as number; responseData = await makeApiRequest(this, baseUrl, session, 'DELETE', `/Attributes/${attributeId}`); } } // Process response if (responseData) { if (Array.isArray(responseData)) { returnData.push(...responseData.map(item => ({ json: item }))); } else if (responseData.reservations) { returnData.push(...responseData.reservations.map((item: any) => ({ json: item }))); } else if (responseData.resources) { returnData.push(...responseData.resources.map((item: any) => ({ json: item }))); } else if (responseData.schedules) { returnData.push(...responseData.schedules.map((item: any) => ({ json: item }))); } else if (responseData.users) { returnData.push(...responseData.users.map((item: any) => ({ json: item }))); } else if (responseData.groups) { returnData.push(...responseData.groups.map((item: any) => ({ json: item }))); } else if (responseData.accessories) { returnData.push(...responseData.accessories.map((item: any) => ({ json: item }))); } else if (responseData.attributes) { returnData.push(...responseData.attributes.map((item: any) => ({ json: item }))); } else { returnData.push({ json: responseData }); } } } catch (error: any) { if (this.continueOnFail()) { returnData.push({ json: { error: error.message } }); continue; } throw error; } } } finally { await signOut(this, baseUrl, session); } return [returnData]; } }