273 lines
7.5 KiB
JavaScript
273 lines
7.5 KiB
JavaScript
// ========================================
|
|
// 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
|
|
}; |