UNPKG

n8n-nodes-nextcloud-tables

Version:

Production-Ready n8n Node für Nextcloud Tables - Vollständige API-Abdeckung mit erweiterten Filtern, Multi-Column-Sorting, CSV-Import und professioneller Datenvalidierung

512 lines 23.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ApiHelper = exports.NextcloudTablesLogger = void 0; const n8n_workflow_1 = require("n8n-workflow"); const axios_1 = __importDefault(require("axios")); const data_formatter_1 = require("./data.formatter"); /** * Zentrale Logging-Klasse für bessere Log-Kennzeichnung und Grep-Fähigkeit */ class NextcloudTablesLogger { /** * Debug-Level Logging (nur in Development/Verbose) */ static debug(context, message, data) { const timestamp = new Date().toISOString(); const logMessage = `${this.NODE_PREFIX} [DEBUG] [${context}] ${message}`; console.debug(`${timestamp} ${logMessage}`, data ? JSON.stringify(data, null, 2) : ''); } /** * Info-Level Logging für wichtige Operationen */ static info(context, message, data) { const timestamp = new Date().toISOString(); const logMessage = `${this.NODE_PREFIX} [INFO] [${context}] ${message}`; console.log(`${timestamp} ${logMessage}`, data ? JSON.stringify(data, null, 2) : ''); } /** * Warning-Level Logging für potentielle Probleme */ static warn(context, message, data) { const timestamp = new Date().toISOString(); const logMessage = `${this.NODE_PREFIX} [WARN] [${context}] ${message}`; console.warn(`${timestamp} ${logMessage}`, data ? JSON.stringify(data, null, 2) : ''); } /** * Error-Level Logging für Fehler */ static error(context, message, error, data) { const timestamp = new Date().toISOString(); const logMessage = `${this.NODE_PREFIX} [ERROR] [${context}] ${message}`; const errorInfo = error ? { message: error.message, stack: error.stack, statusCode: error.statusCode || error.response?.status } : null; console.error(`${timestamp} ${logMessage}`, { error: errorInfo, additionalData: data }); } /** * API-Request Logging für Debugging */ static apiRequest(method, endpoint, body) { this.debug('API-REQUEST', `${method} ${endpoint}`, { method, endpoint, hasBody: !!body, bodySize: body ? JSON.stringify(body).length : 0 }); } /** * API-Response Logging für Debugging */ static apiResponse(method, endpoint, statusCode, duration) { this.debug('API-RESPONSE', `${method} ${endpoint} -> ${statusCode || 'unknown'}`, { method, endpoint, statusCode, duration: duration ? `${duration}ms` : 'unknown' }); } /** * Operation-Start Logging */ static operationStart(resource, operation, context) { this.info('OPERATION-START', `${resource}.${operation}`, { resource, operation, context }); } /** * Operation-Success Logging */ static operationSuccess(resource, operation, duration, result) { this.info('OPERATION-SUCCESS', `${resource}.${operation} completed`, { resource, operation, duration: duration ? `${duration}ms` : 'unknown', resultType: result ? typeof result : 'none', resultSize: result && typeof result === 'object' ? Object.keys(result).length : 0 }); } /** * Operation-Error Logging */ static operationError(resource, operation, error, duration) { this.error('OPERATION-ERROR', `${resource}.${operation} failed`, error, { resource, operation, duration: duration ? `${duration}ms` : 'unknown' }); } /** * Validation-Error Logging */ static validationError(context, field, value, reason) { this.warn('VALIDATION-ERROR', `${field} validation failed: ${reason}`, { context, field, value: typeof value === 'string' ? value : JSON.stringify(value), reason }); } } exports.NextcloudTablesLogger = NextcloudTablesLogger; NextcloudTablesLogger.NODE_PREFIX = '[N8N-NEXTCLOUD-TABLES]'; class ApiHelper { /** * Macht einen API-Request an die Nextcloud Tables API */ static async makeApiRequest(context, method, endpoint, body, useQueryParams = false) { const startTime = Date.now(); // Log API Request NextcloudTablesLogger.apiRequest(method, endpoint, body); const credentials = await context.getCredentials('nextcloudTablesApi'); const serverUrl = credentials.serverUrl.replace(/\/$/, ''); let url = `${serverUrl}/index.php/apps/tables/api/2${endpoint}`; if (useQueryParams && body) { const queryParams = new URLSearchParams(); for (const [key, value] of Object.entries(body)) { if (value !== undefined && value !== null) { queryParams.append(key, String(value)); } } url += `?${queryParams.toString()}`; body = undefined; } const options = { method: method, url, headers: { 'Authorization': `Basic ${Buffer.from(`${credentials.username}:${credentials.password}`).toString('base64')}`, 'Content-Type': 'application/json', 'Accept': 'application/json', 'OCS-APIRequest': 'true', }, json: true, rejectUnauthorized: false, }; if (body && !useQueryParams) { options.body = body; } try { const response = await context.helpers.request(options); const duration = Date.now() - startTime; // Log successful response NextcloudTablesLogger.apiResponse(method, endpoint, 200, duration); return response; } catch (error) { const duration = Date.now() - startTime; const statusCode = error.statusCode || error.response?.status; // Log error response NextcloudTablesLogger.apiResponse(method, endpoint, statusCode, duration); NextcloudTablesLogger.error('API-ERROR', `${method} ${endpoint} failed`, error, { statusCode, duration, url: options.url }); if (error.statusCode) { switch (error.statusCode) { case 400: throw new Error(`[N8N-NEXTCLOUD-TABLES] Ungültige Anfrage (400). Prüfen Sie die übermittelten Daten.`); case 401: throw new Error(`[N8N-NEXTCLOUD-TABLES] Authentifizierung fehlgeschlagen (401). Prüfen Sie Benutzername und Passwort.`); case 403: throw new Error(`[N8N-NEXTCLOUD-TABLES] Zugriff verweigert (403). Prüfen Sie die Berechtigung für diese Aktion.`); case 404: throw new Error(`[N8N-NEXTCLOUD-TABLES] Ressource nicht gefunden (404). Prüfen Sie die URL und ob die Nextcloud Tables App installiert ist.`); case 409: throw new Error(`[N8N-NEXTCLOUD-TABLES] Konflikt (409). Die Ressource existiert bereits oder ist in Verwendung.`); case 422: throw new Error(`[N8N-NEXTCLOUD-TABLES] Daten können nicht verarbeitet werden (422). Prüfen Sie die Eingabevalidierung.`); case 429: throw new Error(`[N8N-NEXTCLOUD-TABLES] Zu viele Anfragen (429). Versuchen Sie es später erneut.`); case 500: throw new Error(`[N8N-NEXTCLOUD-TABLES] Serverfehler (500). Prüfen Sie die Nextcloud-Logs.`); case 502: throw new Error(`[N8N-NEXTCLOUD-TABLES] Bad Gateway (502). Nextcloud-Server nicht erreichbar.`); case 503: throw new Error(`[N8N-NEXTCLOUD-TABLES] Service nicht verfügbar (503). Nextcloud ist temporär nicht erreichbar.`); case 504: throw new Error(`[N8N-NEXTCLOUD-TABLES] Gateway Timeout (504). Anfrage dauerte zu lange.`); default: throw new Error(`[N8N-NEXTCLOUD-TABLES] API-Fehler (${error.statusCode}): ${error.message}`); } } throw new Error(`[N8N-NEXTCLOUD-TABLES] Unbekannter API-Fehler: ${error.message}`); } } /** * Erstellt die Basis-URL für API-Endpunkte */ static getBaseUrl(serverUrl) { return `${serverUrl.replace(/\/$/, '')}/ocs/v2.php/apps/tables/api/2`; } /** * Validiert eine Tabellen-ID */ static validateTableId(tableId) { const id = parseInt(tableId, 10); if (isNaN(id) || id <= 0) { NextcloudTablesLogger.validationError('TABLE-ID', 'tableId', tableId, 'Ungültige Tabellen-ID'); throw new Error('[N8N-NEXTCLOUD-TABLES] Ungültige Tabellen-ID'); } return id; } /** * Validiert eine View-ID */ static validateViewId(viewId) { const id = parseInt(viewId, 10); if (isNaN(id) || id <= 0) { NextcloudTablesLogger.validationError('VIEW-ID', 'viewId', viewId, 'Ungültige View-ID'); throw new Error('[N8N-NEXTCLOUD-TABLES] Ungültige View-ID'); } return id; } /** * Validiert eine Spalten-ID */ static validateColumnId(columnId) { const id = parseInt(columnId, 10); if (isNaN(id) || id <= 0) { NextcloudTablesLogger.validationError('COLUMN-ID', 'columnId', columnId, 'Ungültige Spalten-ID'); throw new Error('[N8N-NEXTCLOUD-TABLES] Ungültige Spalten-ID'); } return id; } /** * Validiert eine Zeilen-ID */ static validateRowId(rowId) { const id = parseInt(rowId, 10); if (isNaN(id) || id <= 0) { NextcloudTablesLogger.validationError('ROW-ID', 'rowId', rowId, 'Ungültige Zeilen-ID'); throw new Error('[N8N-NEXTCLOUD-TABLES] Ungültige Zeilen-ID'); } return id; } /** * Formatiert Zeilendaten für API-Requests */ static formatRowData(data, columns, options = {}) { return data_formatter_1.DataFormatter.formatRowData(data, columns, options); } /** * Formatiert Zeilendaten mit einfacher Fallback-Logik (Legacy) */ static formatRowDataSimple(data) { const formattedData = {}; for (const [key, value] of Object.entries(data)) { if (value !== undefined && value !== null && value !== '') { formattedData[key] = value; } } return formattedData; } /** * Lädt Spalten-Informationen für eine Tabelle (Helper für Formatierung) */ static async getTableColumns(context, tableId) { try { return await this.makeApiRequest(context, 'GET', `/tables/${tableId}/columns`); } catch (error) { return []; } } /** * Konvertiert Resource Locator zu ID - Production Version */ static getResourceId(resourceLocator) { // Log validation attempt NextcloudTablesLogger.debug('RESOURCE-VALIDATION', 'Validating resource locator', { type: typeof resourceLocator, value: resourceLocator }); // Robuste Validierung - Alle möglichen NaN-Quellen abfangen if (resourceLocator === null || resourceLocator === undefined || resourceLocator === 'null' || resourceLocator === 'undefined' || resourceLocator === 'NaN' || (typeof resourceLocator === 'number' && isNaN(resourceLocator))) { NextcloudTablesLogger.validationError('RESOURCE-LOCATOR', 'resourceLocator', resourceLocator, 'Resource Locator ist null, undefined oder NaN'); throw new Error('[N8N-NEXTCLOUD-TABLES] Resource Locator ist erforderlich aber nicht gesetzt oder ungültig'); } if (typeof resourceLocator === 'number') { if (resourceLocator <= 0 || isNaN(resourceLocator)) { NextcloudTablesLogger.validationError('RESOURCE-LOCATOR', 'resourceLocator', resourceLocator, 'Nummer muss positiv sein'); throw new Error('[N8N-NEXTCLOUD-TABLES] Ungültige ID: Muss eine positive Zahl sein'); } NextcloudTablesLogger.debug('RESOURCE-VALIDATION', 'Valid number resource locator', { id: resourceLocator }); return resourceLocator; } if (typeof resourceLocator === 'string') { if (resourceLocator.trim() === '') { NextcloudTablesLogger.validationError('RESOURCE-LOCATOR', 'resourceLocator', resourceLocator, 'String ist leer'); throw new Error('[N8N-NEXTCLOUD-TABLES] Resource Locator ist leer - ID ist erforderlich'); } const id = parseInt(resourceLocator, 10); if (isNaN(id) || id <= 0) { NextcloudTablesLogger.validationError('RESOURCE-LOCATOR', 'resourceLocator', resourceLocator, 'String kann nicht zu positiver Zahl konvertiert werden'); throw new Error(`[N8N-NEXTCLOUD-TABLES] Ungültige ID: "${resourceLocator}" ist keine gültige Zahl`); } NextcloudTablesLogger.debug('RESOURCE-VALIDATION', 'Valid string resource locator converted', { original: resourceLocator, converted: id }); return id; } if (resourceLocator && typeof resourceLocator === 'object') { // Prüfe __rl Struktur if (resourceLocator.__rl === true) { const value = resourceLocator.value; const mode = resourceLocator.mode; NextcloudTablesLogger.debug('RESOURCE-VALIDATION', 'Processing __rl resource locator', { mode, value }); if (!value || value === '') { NextcloudTablesLogger.validationError('RESOURCE-LOCATOR', 'resourceLocator.value', value, `Value ist leer bei mode: ${mode}`); throw new Error(`[N8N-NEXTCLOUD-TABLES] Resource Locator Value ist leer (mode: ${mode}) - Eine ID ist erforderlich`); } if (mode === 'id' || mode === 'list') { const id = parseInt(value, 10); if (isNaN(id) || id <= 0) { NextcloudTablesLogger.validationError('RESOURCE-LOCATOR', 'resourceLocator.value', value, `Value kann nicht zu positiver Zahl konvertiert werden (mode: ${mode})`); throw new Error(`[N8N-NEXTCLOUD-TABLES] Ungültige ID in Resource Locator: "${value}" ist keine gültige Zahl`); } NextcloudTablesLogger.debug('RESOURCE-VALIDATION', 'Valid __rl resource locator', { mode, value, converted: id }); return id; } else { NextcloudTablesLogger.validationError('RESOURCE-LOCATOR', 'resourceLocator.mode', mode, 'Unbekannter Mode'); throw new Error(`[N8N-NEXTCLOUD-TABLES] Unbekannter Resource Locator Mode: ${mode}`); } } // Legacy Format Support if (resourceLocator.mode && resourceLocator.value) { const value = resourceLocator.value; const mode = resourceLocator.mode; NextcloudTablesLogger.debug('RESOURCE-VALIDATION', 'Processing legacy resource locator', { mode, value }); if (!value || value === '') { NextcloudTablesLogger.validationError('RESOURCE-LOCATOR', 'resourceLocator.value', value, 'Legacy value ist leer'); throw new Error('[N8N-NEXTCLOUD-TABLES] Resource Locator Value ist leer - Eine ID ist erforderlich'); } const id = parseInt(value, 10); if (isNaN(id) || id <= 0) { NextcloudTablesLogger.validationError('RESOURCE-LOCATOR', 'resourceLocator.value', value, 'Legacy value kann nicht zu positiver Zahl konvertiert werden'); throw new Error(`[N8N-NEXTCLOUD-TABLES] Ungültige ID: "${value}" ist keine gültige Zahl`); } NextcloudTablesLogger.debug('RESOURCE-VALIDATION', 'Valid legacy resource locator', { mode, value, converted: id }); return id; } } NextcloudTablesLogger.validationError('RESOURCE-LOCATOR', 'resourceLocator', resourceLocator, 'Unbekanntes Format'); throw new Error(`[N8N-NEXTCLOUD-TABLES] Ungültiger Resource Locator Format: ${JSON.stringify(resourceLocator)}`); } /** * Prüft ob ein Fehler wiederholbar ist */ static isNonRetryableError(error) { const statusCode = error.response?.status || error.statusCode; if (statusCode >= 400 && statusCode < 500 && statusCode !== 408 && statusCode !== 429) { return true; } const errorMessage = error.message?.toLowerCase() || ''; if (errorMessage.includes('unauthorized') || errorMessage.includes('forbidden') || errorMessage.includes('not found')) { return true; } return false; } /** * Sleep-Hilfsfunktion für Retry-Delays */ static sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Verbesserte Fehlerbehandlung mit spezifischen HTTP-Status-Codes */ static handleApiError(error) { const statusCode = error.response?.status || error.statusCode; const errorMessage = error.response?.body?.ocs?.meta?.message || error.message; // Log the error for debugging NextcloudTablesLogger.error('API-ERROR-HANDLER', `HTTP ${statusCode} Error`, error, { statusCode, errorMessage }); switch (statusCode) { case 400: throw new Error(`[N8N-NEXTCLOUD-TABLES] Ungültige Anfrage: ${errorMessage || 'Überprüfen Sie Ihre Eingabedaten'}`); case 401: throw new Error(`[N8N-NEXTCLOUD-TABLES] Nicht autorisiert: ${errorMessage || 'Überprüfen Sie Ihre Anmeldedaten'}`); case 403: throw new Error(`[N8N-NEXTCLOUD-TABLES] Zugriff verweigert: ${errorMessage || 'Keine Berechtigung für diese Operation'}`); case 404: throw new Error(`[N8N-NEXTCLOUD-TABLES] Nicht gefunden: ${errorMessage || 'Die angeforderte Ressource existiert nicht'}`); case 409: throw new Error(`[N8N-NEXTCLOUD-TABLES] Konflikt: ${errorMessage || 'Die Ressource ist bereits vorhanden oder wird verwendet'}`); case 422: throw new Error(`[N8N-NEXTCLOUD-TABLES] Validierungsfehler: ${errorMessage || 'Die Eingabedaten sind ungültig'}`); case 429: throw new Error(`[N8N-NEXTCLOUD-TABLES] Zu viele Anfragen: ${errorMessage || 'Versuchen Sie es später erneut'}`); case 500: throw new Error(`[N8N-NEXTCLOUD-TABLES] Serverfehler: ${errorMessage || 'Ein interner Fehler ist aufgetreten'}`); case 502: case 503: case 504: throw new Error(`[N8N-NEXTCLOUD-TABLES] Dienst nicht verfügbar: ${errorMessage || 'Der Server ist vorübergehend nicht erreichbar'}`); default: throw new Error(`[N8N-NEXTCLOUD-TABLES] API-Fehler: ${errorMessage || 'Ein unbekannter Fehler ist aufgetreten'}`); } } /** * Lädt alle verfügbaren Tabellen */ static async getTables(context) { try { const tables = await this.makeApiRequest(context, 'GET', '/tables'); return tables || []; } catch (error) { return []; } } /** * Nextcloud Sharee API Request (für Benutzer-/Gruppensuche) */ static async nextcloudShareeApiRequest(context, method, endpoint, body = {}) { const credentials = await context.getCredentials('nextcloudTablesApi'); const serverUrl = credentials.serverUrl.replace(/\/$/, ''); const username = credentials.username; const password = credentials.password; const url = `${serverUrl}/ocs/v2.php/apps/files_sharing/api/v1${endpoint}`; const config = { method, url, headers: { 'OCS-APIRequest': 'true', 'Content-Type': 'application/json', 'Accept': 'application/json', }, auth: { username, password, }, }; if (Object.keys(body).length > 0) { config.data = body; } try { const response = await (0, axios_1.default)(config); const data = response.data; return data?.ocs?.data || response.data; } catch (error) { const axiosError = error; NextcloudTablesLogger.error('SHAREE-API-ERROR', 'Nextcloud Sharee API request failed', axiosError); throw new n8n_workflow_1.NodeOperationError(context.getNode(), `[N8N-NEXTCLOUD-TABLES] Sharee API-Fehler: ${axiosError.response?.data?.ocs?.meta?.message || 'Unbekannter Fehler'}`); } } /** * Nextcloud OCS Users API Request (für Benutzer-/Gruppensuche) */ static async nextcloudOcsUsersApiRequest(context, method, endpoint, body = {}) { const credentials = await context.getCredentials('nextcloudTablesApi'); const serverUrl = credentials.serverUrl.replace(/\/$/, ''); const username = credentials.username; const password = credentials.password; const url = `${serverUrl}/ocs/v2.php/cloud${endpoint}`; const config = { method, url, headers: { 'OCS-APIRequest': 'true', 'Content-Type': 'application/json', 'Accept': 'application/json', }, auth: { username, password, }, }; if (Object.keys(body).length > 0) { config.data = body; } try { const response = await (0, axios_1.default)(config); const data = response.data; return data?.ocs?.data || response.data; } catch (error) { const axiosError = error; NextcloudTablesLogger.error('OCS-USERS-API-ERROR', 'Nextcloud OCS Users API request failed', axiosError); throw new n8n_workflow_1.NodeOperationError(context.getNode(), `[N8N-NEXTCLOUD-TABLES] OCS Users API-Fehler: ${axiosError.response?.data?.ocs?.meta?.message || 'Unbekannter Fehler'}`); } } } exports.ApiHelper = ApiHelper; //# sourceMappingURL=api.helper.js.map