// ======================================== // FICHIER: utils.js - Conversion Node.js // Description: Utilitaires génériques pour le workflow // ======================================== // Import du système de logging (assumant que logSh est disponible globalement) // const { logSh } = require('./logging'); // À décommenter si logSh est dans un module séparé /** * Créer une réponse de succès standardisée * @param {Object} data - Données à retourner * @returns {Object} Réponse formatée pour Express/HTTP */ function createSuccessResponse(data) { return { success: true, data: data, timestamp: new Date().toISOString() }; } /** * Créer une réponse d'erreur standardisée * @param {string|Error} error - Message d'erreur ou objet Error * @returns {Object} Réponse d'erreur formatée */ function createErrorResponse(error) { const errorMessage = error instanceof Error ? error.message : error.toString(); return { success: false, error: errorMessage, timestamp: new Date().toISOString(), stack: process.env.NODE_ENV === 'development' && error instanceof Error ? error.stack : undefined }; } /** * Middleware Express pour envoyer des réponses standardisées * Usage: res.success(data) ou res.error(error) */ function responseMiddleware(req, res, next) { // Méthode pour réponse de succès res.success = (data, statusCode = 200) => { res.status(statusCode).json(createSuccessResponse(data)); }; // Méthode pour réponse d'erreur res.error = (error, statusCode = 500) => { res.status(statusCode).json(createErrorResponse(error)); }; next(); } /** * HELPER : Nettoyer les instructions FAQ * Remplace les variables et nettoie le HTML * @param {string} instructions - Instructions à nettoyer * @param {Object} csvData - Données CSV pour remplacement variables * @returns {string} Instructions nettoyées */ function cleanFAQInstructions(instructions, csvData) { if (!instructions || !csvData) { return instructions || ''; } let clean = instructions.toString(); try { // Remplacer variables simples clean = clean.replace(/\{\{MC0\}\}/g, csvData.mc0 || ''); clean = clean.replace(/\{\{T0\}\}/g, csvData.t0 || ''); // Variables multiples si nécessaire if (csvData.mcPlus1) { const mcPlus1 = csvData.mcPlus1.split(',').map(s => s.trim()); for (let i = 1; i <= 6; i++) { const mcValue = mcPlus1[i-1] || `[MC+1_${i} non défini]`; clean = clean.replace(new RegExp(`\\{\\{MC\\+1_${i}\\}\\}`, 'g'), mcValue); } } // Variables T+1 et L+1 si disponibles if (csvData.tPlus1) { const tPlus1 = csvData.tPlus1.split(',').map(s => s.trim()); for (let i = 1; i <= 6; i++) { const tValue = tPlus1[i-1] || `[T+1_${i} non défini]`; clean = clean.replace(new RegExp(`\\{\\{T\\+1_${i}\\}\\}`, 'g'), tValue); } } if (csvData.lPlus1) { const lPlus1 = csvData.lPlus1.split(',').map(s => s.trim()); for (let i = 1; i <= 6; i++) { const lValue = lPlus1[i-1] || `[L+1_${i} non défini]`; clean = clean.replace(new RegExp(`\\{\\{L\\+1_${i}\\}\\}`, 'g'), lValue); } } // Nettoyer HTML clean = clean.replace(/<\/?[^>]+>/g, ''); // Nettoyer espaces en trop clean = clean.replace(/\s+/g, ' ').trim(); } catch (error) { if (typeof logSh === 'function') { logSh(`⚠️ Erreur nettoyage instructions FAQ: ${error.toString()}`, 'WARNING'); } // Retourner au moins la version partiellement nettoyée } return clean; } /** * Utilitaire pour attendre un délai (remplace Utilities.sleep de Google Apps Script) * @param {number} ms - Millisecondes à attendre * @returns {Promise} Promise qui se résout après le délai */ function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Utilitaire pour encoder en base64 * @param {string} text - Texte à encoder * @returns {string} Texte encodé en base64 */ function base64Encode(text) { return Buffer.from(text, 'utf8').toString('base64'); } /** * Utilitaire pour décoder du base64 * @param {string} base64Text - Texte base64 à décoder * @returns {string} Texte décodé */ function base64Decode(base64Text) { return Buffer.from(base64Text, 'base64').toString('utf8'); } /** * Valider et nettoyer un slug/filename * @param {string} slug - Slug à nettoyer * @returns {string} Slug nettoyé */ function cleanSlug(slug) { if (!slug) return ''; return slug .toString() .toLowerCase() .replace(/[^a-z0-9\-_]/g, '-') // Remplacer caractères spéciaux par - .replace(/-+/g, '-') // Éviter doubles tirets .replace(/^-+|-+$/g, ''); // Enlever tirets début/fin } /** * Compter les mots dans un texte * @param {string} text - Texte à analyser * @returns {number} Nombre de mots */ function countWords(text) { if (!text || typeof text !== 'string') return 0; return text .trim() .replace(/\s+/g, ' ') // Normaliser espaces .split(' ') .filter(word => word.length > 0) .length; } /** * Formater une durée en millisecondes en format lisible * @param {number} ms - Durée en millisecondes * @returns {string} Durée formatée (ex: "2.3s" ou "450ms") */ function formatDuration(ms) { if (ms < 1000) { return `${ms}ms`; } else if (ms < 60000) { return `${(ms / 1000).toFixed(1)}s`; } else { const minutes = Math.floor(ms / 60000); const seconds = ((ms % 60000) / 1000).toFixed(1); return `${minutes}m ${seconds}s`; } } /** * Utilitaire pour retry automatique d'une fonction * @param {Function} fn - Fonction à exécuter avec retry * @param {number} maxRetries - Nombre maximum de tentatives * @param {number} delay - Délai entre tentatives (ms) * @returns {Promise} Résultat de la fonction ou erreur finale */ async function withRetry(fn, maxRetries = 3, delay = 1000) { let lastError; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await fn(); } catch (error) { lastError = error; if (typeof logSh === 'function') { logSh(`⚠️ Tentative ${attempt}/${maxRetries} échouée: ${error.toString()}`, 'WARNING'); } if (attempt < maxRetries) { await sleep(delay * attempt); // Exponential backoff } } } throw lastError; } /** * Validation basique d'email * @param {string} email - Email à valider * @returns {boolean} True si email valide */ function isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } /** * Générer un ID unique simple * @returns {string} ID unique basé sur timestamp + random */ function generateId() { return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } /** * Truncate un texte à une longueur donnée * @param {string} text - Texte à tronquer * @param {number} maxLength - Longueur maximale * @param {string} suffix - Suffixe à ajouter si tronqué (défaut: '...') * @returns {string} Texte tronqué */ function truncate(text, maxLength, suffix = '...') { if (!text || text.length <= maxLength) { return text; } return text.substring(0, maxLength - suffix.length) + suffix; } // ============= EXPORTS ============= module.exports = { createSuccessResponse, createErrorResponse, responseMiddleware, cleanFAQInstructions, sleep, base64Encode, base64Decode, cleanSlug, countWords, formatDuration, withRetry, isValidEmail, generateId, truncate };