Skip to main content
Glama
CalendarIntegrationValidator.ts14.9 kB
import WorkflowValidator from '../resources/WorkflowValidator.js'; /** * Validateur pour les intégrations avec Google Calendar dans les workflows n8n * Vérifie la configuration des nœuds Google Calendar, la gestion des quotas, * les mécanismes de rafraîchissement OAuth et la validation des formats de date/heure */ class CalendarIntegrationValidator implements WorkflowValidator { /** * Valide les aspects d'intégration avec Google Calendar dans un workflow n8n * @param workflow Les données du workflow à valider * @param strictness Le niveau de rigueur de la validation * @returns Résultat de la validation avec les problèmes détectés */ validate(workflow: any, strictness: 'low' | 'medium' | 'high') { const issues: Array<{ message: string; recommendation: string; severity: 'low' | 'medium' | 'high'; location?: string; }> = []; // Vérifier si le workflow contient des nœuds Google Calendar if (!workflow.nodes || !Array.isArray(workflow.nodes)) { return { valid: true, issues: [] }; } const calendarNodes = workflow.nodes.filter((node: any) => (node.type || '').toLowerCase().includes('googlecalendar') || (node.name || '').toLowerCase().includes('calendar') ); if (calendarNodes.length === 0) { return { valid: true, issues: [] }; } // Vérifier chaque nœud Google Calendar calendarNodes.forEach((node: any) => { // 1. Vérifier la configuration des nœuds Google Calendar this.checkCalendarNodeConfiguration(node, issues); // 2. Vérifier la gestion des quotas d'API this.checkApiQuotaManagement(node, workflow, issues, strictness); // 3. Vérifier les mécanismes de rafraîchissement des tokens OAuth this.checkOAuthRefreshMechanism(node, workflow, issues); // 4. Vérifier la validation des formats de date/heure this.checkDateTimeFormats(node, issues); }); // 5. Vérifier la présence de nœuds de gestion d'erreurs pour les appels Calendar this.checkErrorHandlingForCalendarNodes(workflow, calendarNodes, issues, strictness); return { valid: issues.length === 0, issues, }; } /** * Vérifie la configuration correcte des nœuds Google Calendar */ private checkCalendarNodeConfiguration(node: any, issues: any[]): void { // Vérifier si les paramètres requis sont présents if (!node.parameters) { issues.push({ message: `Le nœud "${node.name}" n'a pas de paramètres configurés`, recommendation: 'Configurez les paramètres requis pour le nœud Google Calendar', severity: 'high', location: node.id, }); return; } // Vérifier si le calendrier est spécifié if (node.parameters.calendarId === undefined || node.parameters.calendarId === '') { issues.push({ message: `Le nœud "${node.name}" n'a pas d'ID de calendrier spécifié`, recommendation: 'Spécifiez un ID de calendrier valide ou utilisez "primary" pour le calendrier principal', severity: 'high', location: node.id, }); } // Vérifier si les opérations sont correctement configurées if (node.parameters.operation === undefined) { issues.push({ message: `Le nœud "${node.name}" n'a pas d'opération spécifiée`, recommendation: 'Spécifiez une opération valide (ex: create, update, get, list, delete)', severity: 'high', location: node.id, }); } // Vérifier si les credentials sont configurées if (!node.credentials || !node.credentials.googleCalendarOAuth2Api) { issues.push({ message: `Le nœud "${node.name}" n'a pas de credentials OAuth configurées`, recommendation: 'Configurez les credentials OAuth pour Google Calendar', severity: 'high', location: node.id, }); } } /** * Vérifie la gestion des quotas d'API Google Calendar */ private checkApiQuotaManagement(node: any, workflow: any, issues: any[], strictness: string): void { // Vérifier si le workflow a des mécanismes de limitation de débit const hasRateLimiting = workflow.nodes.some((n: any) => (n.type || '').toLowerCase().includes('limit') || (n.name || '').toLowerCase().includes('throttle') || (n.name || '').toLowerCase().includes('limit') || (n.name || '').toLowerCase().includes('delay') ); if (!hasRateLimiting && strictness !== 'low') { issues.push({ message: `Le workflow utilisant Google Calendar n'a pas de mécanisme de limitation de débit`, recommendation: 'Ajoutez des nœuds de limitation de débit pour éviter de dépasser les quotas de l\'API Google Calendar (ex: Limit, Delay)', severity: strictness === 'high' ? 'high' : 'medium', location: node.id, }); } // Vérifier si le workflow a des mécanismes de mise en cache const hasCaching = workflow.nodes.some((n: any) => (n.name || '').toLowerCase().includes('cache') || (n.name || '').toLowerCase().includes('store') ); if (!hasCaching && strictness === 'high') { issues.push({ message: `Le workflow n'utilise pas de mise en cache pour les données Google Calendar`, recommendation: 'Implémentez un mécanisme de mise en cache pour réduire le nombre d\'appels à l\'API Google Calendar', severity: 'medium', location: node.id, }); } // Vérifier si le workflow a des nœuds de traitement par lots pour les opérations en masse if (this.isListOperation(node) && !this.hasBatchProcessing(workflow)) { issues.push({ message: `Le nœud "${node.name}" effectue une opération de liste sans traitement par lots`, recommendation: 'Utilisez des nœuds de traitement par lots pour gérer efficacement les grandes quantités de données', severity: strictness === 'low' ? 'low' : 'medium', location: node.id, }); } } /** * Vérifie si un nœud effectue une opération de liste */ private isListOperation(node: any): boolean { return node.parameters?.operation === 'list' || (node.parameters?.resource === 'event' && node.parameters?.operation === 'getAll'); } /** * Vérifie si le workflow a des nœuds de traitement par lots */ private hasBatchProcessing(workflow: any): boolean { return workflow.nodes.some((n: any) => (n.type || '').toLowerCase().includes('split') || (n.name || '').toLowerCase().includes('batch') || (n.name || '').toLowerCase().includes('split') || (n.name || '').toLowerCase().includes('chunk') ); } /** * Vérifie les mécanismes de rafraîchissement des tokens OAuth */ private checkOAuthRefreshMechanism(node: any, workflow: any, issues: any[]): void { // Vérifier si le workflow a un mécanisme de gestion des erreurs d'authentification const hasAuthErrorHandling = workflow.nodes.some((n: any) => { if ((n.type || '').toLowerCase().includes('if') || (n.type || '').toLowerCase().includes('switch')) { const nodeString = JSON.stringify(n); return nodeString.includes('401') || nodeString.includes('403') || nodeString.toLowerCase().includes('unauthorized') || nodeString.toLowerCase().includes('authentication') || nodeString.toLowerCase().includes('token expired'); } return false; }); if (!hasAuthErrorHandling) { issues.push({ message: `Le workflow n'a pas de mécanisme pour gérer les erreurs d'authentification ou les tokens expirés`, recommendation: 'Ajoutez une gestion des erreurs 401/403 et un mécanisme de rafraîchissement des tokens OAuth', severity: 'high', location: node.id, }); } // Vérifier si le workflow a un nœud pour rafraîchir les tokens const hasRefreshNode = workflow.nodes.some((n: any) => (n.name || '').toLowerCase().includes('refresh') && (n.name || '').toLowerCase().includes('token') ); if (!hasRefreshNode) { issues.push({ message: `Le workflow n'a pas de nœud explicite pour rafraîchir les tokens OAuth`, recommendation: 'Ajoutez un nœud dédié au rafraîchissement des tokens OAuth pour Google Calendar', severity: 'medium', location: node.id, }); } } /** * Vérifie la validation des formats de date/heure */ private checkDateTimeFormats(node: any, issues: any[]): void { // Vérifier si le nœud manipule des dates/heures if (this.isEventCreationOrUpdate(node)) { // Vérifier si les dates sont correctement formatées pour Google Calendar const nodeString = JSON.stringify(node); // Vérifier si le workflow utilise des fonctions de formatage de date const hasDateFormatting = nodeString.includes('new Date') || nodeString.includes('moment') || nodeString.includes('format') || nodeString.includes('toISOString') || nodeString.includes('$formatDate'); if (!hasDateFormatting) { issues.push({ message: `Le nœud "${node.name}" manipule des événements sans formater explicitement les dates`, recommendation: 'Utilisez des fonctions de formatage de date (ex: $formatDate, toISOString()) pour assurer la compatibilité avec Google Calendar', severity: 'medium', location: node.id, }); } // Vérifier si le fuseau horaire est géré const hasTimezoneHandling = nodeString.includes('timeZone') || nodeString.includes('timezone') || nodeString.includes('utc'); if (!hasTimezoneHandling) { issues.push({ message: `Le nœud "${node.name}" ne gère pas explicitement les fuseaux horaires`, recommendation: 'Spécifiez explicitement les fuseaux horaires pour éviter les problèmes de conversion de date/heure', severity: 'medium', location: node.id, }); } } } /** * Vérifie si un nœud crée ou met à jour des événements */ private isEventCreationOrUpdate(node: any): boolean { if (!node.parameters) return false; return (node.parameters.resource === 'event' && (node.parameters.operation === 'create' || node.parameters.operation === 'update')) || node.parameters.operation === 'insert' || node.parameters.operation === 'patch' || node.parameters.operation === 'update'; } /** * Vérifie la présence de nœuds de gestion d'erreurs pour les appels Calendar */ private checkErrorHandlingForCalendarNodes(workflow: any, calendarNodes: any[], issues: any[], strictness: string): void { // Vérifier si chaque nœud Calendar a une gestion d'erreur associée calendarNodes.forEach((calendarNode: any) => { const hasErrorHandling = this.hasErrorHandlingForNode(workflow, calendarNode); if (!hasErrorHandling) { issues.push({ message: `Le nœud "${calendarNode.name}" n'a pas de gestion d'erreur associée`, recommendation: 'Ajoutez des nœuds de gestion d\'erreur (Error Trigger) pour gérer les échecs d\'appels à l\'API Google Calendar', severity: strictness === 'low' ? 'medium' : 'high', location: calendarNode.id, }); } }); // Vérifier si le workflow a un mécanisme de retry pour les erreurs temporaires const hasRetryMechanism = workflow.nodes.some((n: any) => (n.name || '').toLowerCase().includes('retry') || (n.parameters?.continueOnFail === true) ); if (!hasRetryMechanism && strictness !== 'low') { issues.push({ message: 'Le workflow n\'a pas de mécanisme de retry pour les erreurs temporaires de l\'API Google Calendar', recommendation: 'Implémentez un mécanisme de retry avec backoff exponentiel pour gérer les erreurs temporaires de l\'API', severity: 'medium', }); } } /** * Vérifie si un nœud a une gestion d'erreur associée */ private hasErrorHandlingForNode(workflow: any, node: any): boolean { // Vérifier s'il y a un nœud Error Trigger connecté à ce nœud const hasErrorTrigger = workflow.nodes.some((n: any) => (n.type || '').toLowerCase().includes('error') && this.isConnectedToNode(workflow, n, node) ); // Vérifier si le nœud a l'option continueOnFail activée const hasContinueOnFail = node.parameters?.continueOnFail === true; // Vérifier s'il y a un nœud IF qui vérifie les erreurs après ce nœud const hasErrorCheckingIf = workflow.nodes.some((n: any) => { if ((n.type || '').toLowerCase().includes('if')) { const nodeString = JSON.stringify(n); return (nodeString.includes('error') || nodeString.includes('fail')) && this.isNodeAfter(workflow, n, node); } return false; }); return hasErrorTrigger || hasContinueOnFail || hasErrorCheckingIf; } /** * Vérifie si un nœud est connecté à un autre nœud */ private isConnectedToNode(workflow: any, node1: any, node2: any): boolean { if (!workflow.connections) return false; // Parcourir toutes les connexions pour voir si node1 est connecté à node2 for (const sourceNode in workflow.connections) { if (sourceNode === node2.id) { for (const sourceOutput in workflow.connections[sourceNode]) { for (const connection of workflow.connections[sourceNode][sourceOutput]) { if (connection.node === node1.id) { return true; } } } } } return false; } /** * Vérifie si un nœud est exécuté après un autre nœud */ private isNodeAfter(workflow: any, node1: any, node2: any): boolean { if (!workflow.connections) return false; // Fonction récursive pour vérifier si node1 est atteignable depuis node2 const isReachable = (currentNodeId: string, visited = new Set<string>()): boolean => { if (currentNodeId === node1.id) return true; if (visited.has(currentNodeId)) return false; visited.add(currentNodeId); if (!workflow.connections[currentNodeId]) return false; for (const output in workflow.connections[currentNodeId]) { for (const connection of workflow.connections[currentNodeId][output]) { if (isReachable(connection.node, visited)) { return true; } } } return false; }; return isReachable(node2.id); } } export default CalendarIntegrationValidator;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/lowprofix/n8n-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server