UNPKG

coddyger

Version:

Coddyger est une bibliothèque JavaScript/TypeScript qui fournit des fonctions communes et des plugins pour la gestion des données, la communication entre services, et des utilitaires avancés pour le développement d'applications.

1,336 lines (1,184 loc) 42.7 kB
import * as bcrypt from "bcrypt"; import * as path from "path"; import * as fs from "fs"; import * as http from "http"; import { LogLevel, LoggerService } from "./services"; import env, { defines } from "./globals"; import mongoose from "mongoose"; import { format, parseISO, isValid, addDays, subDays, differenceInDays, startOfWeek, endOfWeek, startOfMonth, endOfMonth, isWithinInterval } from 'date-fns'; import { fr } from 'date-fns/locale'; import { parsePhoneNumberFromString, CountryCode } from 'libphonenumber-js'; import countriesData from './data/countries.json'; const coddyger = { /** * Gère les réponses API de manière standardisée * @param context - Le contexte de la requête HTTP * @param promise - La promesse à traiter */ api: (context: any, promise: Promise<any>) => { promise .then((res: any) => { let status = res.status; let message = res.message; let data = res.data; let isFile: any = res.isFile; // Extraire les champs supplémentaires (tout sauf status, message, data, isFile) const { status: _, message: __, data: ___, isFile: ____, ...additionalFields } = res; let response = { message: message, data: data !== undefined || true ? data : [], ...additionalFields // Inclure tous les champs supplémentaires }; if (isFile) { context.setHeader("Content-type", data.type); context.sendFile(data.file); } else { return context.status(status).send(response); } }) .catch((err: any) => { console.log(err); return context.status(500).send({}); }) .catch((err: any) => { // console.log(err) return context .status(500) .send({ error: true, message: "une erreur interne s'est produite veuillez réssayer plutard", }); }); }, /** * Affiche des messages de log formatés avec horodatage et niveau de log * @param msg - Le message à logger * @param error - 1 pour erreur, 0 pour info */ konsole: (msg: any, error = 0) => { let message = new Date().toISOString() + "[" + (error === 1 ? "error" : "info") + "]" + JSON.stringify(msg); if (error === 1) { console.error(message); } else { console.log(message); } }, string: { /** * Vérifie si une valeur est vide * @param value - La valeur à vérifier * @returns true si la valeur est vide, null ou undefined */ isEmpty: function (value: any) { return ( value === undefined || value === null || value.length <= 0 || value === "" ); }, /** * Vérifie si une chaîne est une adresse email valide * @param payload - L'email à vérifier * @returns true si l'email est valide */ isEmailAddress: function (payload: string): boolean { const regexp = new RegExp( /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ ); return regexp.test(payload); }, /** * Vérifie si une chaîne est un nombre valide * @param payload - La chaîne à vérifier * @returns true si la chaîne est un nombre */ isNumber: function (payload: string): boolean { return /^(?:-?\d+|-?\d{1, 3}(?:, \d{3})+)?(?:\.\d+)?$/.test(payload); }, /** * Vérifie si une chaîne est une date valide * @param payload - La chaîne à vérifier * @returns true si la chaîne est une date valide */ isDate: function (payload: string): boolean { const date = new Date(payload); return !isNaN(date.getTime()); }, /** * Vérifie si une chaîne est un ObjectId MongoDB valide * @param payload - La chaîne à vérifier * @returns true si la chaîne est un ObjectId valide */ isValidObjectId: (payload: string): boolean => { const objectIdRegex = /^[0-9a-fA-F]{24}$/; return objectIdRegex.test(payload); }, /** * Génère un nouvel ObjectId MongoDB * @returns Un nouvel ObjectId */ generateObjectId: () => { try { return new mongoose.Types.ObjectId(); } catch (error) { console.error(error); return error; } }, /** * Convertit une chaîne en ObjectId MongoDB * @param value - La valeur à convertir * @returns L'ObjectId correspondant */ toObjectId: (value: any) => { try { return new mongoose.Types.ObjectId(value); } catch (error) { console.error("ERREUR::", error); return error; } }, /** * Chiffre un mot de passe * @param payload - Le mot de passe à chiffrer * @returns Le mot de passe chiffré */ encryptPassword: async (payload: string): Promise<string> => { const saltRounds = 10; const hashedPassword = await bcrypt.hash(payload, saltRounds); return hashedPassword; }, /** * Vérifie un mot de passe par rapport à sa version chiffrée * @param payload - Le mot de passe en clair * @param hashedPayload - Le mot de passe chiffré * @returns true si les mots de passe correspondent */ decryptPassword: async ( payload: string, hashedPayload: string ): Promise<boolean> => { const result = await bcrypt.compare(payload, hashedPayload); return result; }, /** * Met en majuscule la première lettre de chaque mot * @param payload - Le texte à transformer * @returns Le texte avec les premières lettres en majuscule */ capitalizeEachWord: (payload: string): string => { return payload .split(" ") .map((word) => { const lowercasedWord = word.toLowerCase(); return ( lowercasedWord.charAt(0).toUpperCase() + lowercasedWord.slice(1) ); }) .join(" "); }, /** * Convertit un texte en vecteur numérique (vectorisation) * @param text - Le texte à vectoriser * @returns Un vecteur de fréquences des mots */ vectorizeText: (text: string): number[] => { // Convert text to lowercase and remove special characters const cleanText = text.toLowerCase().replace(/[^a-z0-9\s]/g, ""); // Split into words and remove empty strings const words = cleanText.split(/\s+/).filter((word) => word.length > 0); // Create a vocabulary (unique words) const vocabulary = Array.from(new Set(words)); // Create vector (word frequency) const vector = vocabulary.map((term) => { return words.filter((word) => word === term).length; }); return vector; }, /** * Calcule la similarité cosinus entre deux vecteurs * @param vector1 - Premier vecteur * @param vector2 - Second vecteur * @returns La similarité cosinus entre les deux vecteurs (entre 0 et 1) */ cosineSimilarity: (vector1: number[], vector2: number[]): number => { if (vector1.length !== vector2.length) { throw new Error("Vectors must have the same length"); } const dotProduct = vector1.reduce( (acc, val, i) => acc + val * vector2[i], 0 ); const magnitude1 = Math.sqrt( vector1.reduce((acc, val) => acc + val * val, 0) ); const magnitude2 = Math.sqrt( vector2.reduce((acc, val) => acc + val * val, 0) ); if (magnitude1 === 0 || magnitude2 === 0) { return 0; } return dotProduct / (magnitude1 * magnitude2); }, /** * Vérifie si une chaîne est un numéro de téléphone valide * @param phoneNumber - Le numéro de téléphone à vérifier * @param countryCode - Le code pays optionnel (ex: 'FR', 'US') * @returns true si le numéro est valide */ isValidPhoneNumber: (phoneNumber: string, countryCode?: CountryCode): boolean => { try { const parsedNumber = parsePhoneNumberFromString(phoneNumber, countryCode); return parsedNumber ? parsedNumber.isValid() : false; } catch (error) { return false; } }, /** * Obtient le code pays à partir d'un préfixe téléphonique * @param prefix - Le préfixe téléphonique (ex: +33, +1) * @returns Le code pays correspondant ou null si non trouvé */ getCountryCodeFromPrefix: (prefix: string): string | null => { // Remove '+' if present const cleanPrefix = prefix.replace('+', ''); return countriesData.prefixMap[cleanPrefix] || null; }, /** * Obtient la liste des pays avec leurs codes, noms et drapeaux * @returns Un tableau d'objets pays */ getCountryList: (): Array<{ code: string; name: string; flag: string; phoneCode: string; }> => { return countriesData.countries; }, /** * Génère un mot de passe aléatoire sécurisé * @param length - Longueur du mot de passe (défaut: 12) * @param includeSymbols - Inclure des symboles spéciaux (défaut: true) * @returns Un mot de passe aléatoire sécurisé */ generateSecurePassword: (length: number = 12, includeSymbols: boolean = true): string => { const lowercase = 'abcdefghijklmnopqrstuvwxyz'; const uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; const numbers = '0123456789'; const symbols = '!@#$%^&*()_+-=[]{}|;:,.<>?'; let charset = lowercase + uppercase + numbers; if (includeSymbols) { charset += symbols; } let password = ''; // Garantir au moins un caractère de chaque type password += lowercase[Math.floor(Math.random() * lowercase.length)]; password += uppercase[Math.floor(Math.random() * uppercase.length)]; password += numbers[Math.floor(Math.random() * numbers.length)]; if (includeSymbols) { password += symbols[Math.floor(Math.random() * symbols.length)]; } // Compléter avec des caractères aléatoires for (let i = password.length; i < length; i++) { password += charset[Math.floor(Math.random() * charset.length)]; } // Mélanger le mot de passe return password.split('').sort(() => Math.random() - 0.5).join(''); }, /** * Génère un code PIN numérique aléatoire * @param length - Longueur du code PIN (défaut: 6) * @returns Un code PIN numérique */ generatePIN: (length: number = 6): string => { let pin = ''; for (let i = 0; i < length; i++) { pin += Math.floor(Math.random() * 10).toString(); } return pin; }, /** * Génère un token aléatoire alphanumérique * @param length - Longueur du token (défaut: 32) * @returns Un token aléatoire */ generateToken: (length: number = 32): string => { const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let token = ''; for (let i = 0; i < length; i++) { token += charset[Math.floor(Math.random() * charset.length)]; } return token; }, /** * Nettoie et normalise une chaîne de caractères * @param text - Le texte à nettoyer * @returns Le texte nettoyé */ cleanText: (text: string): string => { return text .trim() .replace(/\s+/g, ' ') // Remplace les espaces multiples par un seul .replace(/[^\w\s\-_.@]/g, '') // Supprime les caractères spéciaux sauf quelques-uns .toLowerCase(); }, /** * Tronque un texte à une longueur spécifiée avec des points de suspension * @param text - Le texte à tronquer * @param maxLength - Longueur maximale (défaut: 100) * @param suffix - Suffixe à ajouter (défaut: "...") * @returns Le texte tronqué */ truncate: (text: string, maxLength: number = 100, suffix: string = "..."): string => { if (text.length <= maxLength) { return text; } return text.substring(0, maxLength - suffix.length) + suffix; }, /** * Convertit un texte en slug URL-friendly * @param text - Le texte à convertir * @returns Le slug généré */ slugify: (text: string): string => { return text .toLowerCase() .trim() .replace(/[àáäâ]/g, 'a') .replace(/[èéëê]/g, 'e') .replace(/[ìíïî]/g, 'i') .replace(/[òóöô]/g, 'o') .replace(/[ùúüû]/g, 'u') .replace(/[ñ]/g, 'n') .replace(/[ç]/g, 'c') .replace(/[^a-z0-9 -]/g, '') .replace(/\s+/g, '-') .replace(/-+/g, '-') .replace(/^-+/, '') .replace(/-+$/, ''); }, /** * Masque partiellement une chaîne (utile pour emails, téléphones) * @param text - Le texte à masquer * @param visibleStart - Nombre de caractères visibles au début (défaut: 2) * @param visibleEnd - Nombre de caractères visibles à la fin (défaut: 2) * @param maskChar - Caractère de masquage (défaut: "*") * @returns Le texte masqué */ maskString: (text: string, visibleStart: number = 2, visibleEnd: number = 2, maskChar: string = "*"): string => { if (text.length <= visibleStart + visibleEnd) { return text; } const start = text.substring(0, visibleStart); const end = text.substring(text.length - visibleEnd); const middle = maskChar.repeat(text.length - visibleStart - visibleEnd); return start + middle + end; }, }, array: { /** * Compare deux tableaux pour vérifier s'ils sont identiques * @param array1 - Premier tableau * @param array2 - Second tableau * @returns true si les tableaux sont identiques */ compare: function (array1: Array<any>, array2: Array<any>): boolean { if (array1.length !== array2.length) { return false; } for (let i = 0; i < array1.length; i++) { if (array1[i] !== array2[i]) { return false; } } return true; }, /** * Vérifie si une valeur existe dans un tableau * @param needle - La valeur à rechercher * @param haystack - Le tableau dans lequel chercher * @returns true si la valeur est trouvée */ inArray: function (needle: any, haystack: any) { let length = haystack.length; for (let i = 0; i < length; i++) { if (haystack[i] === needle) return true; } return false; }, /** * Supprime les doublons d'un tableau * @param array - Le tableau à dédupliquer * @returns Un nouveau tableau sans doublons */ unique: function (array: Array<any>): Array<any> { return [...new Set(array)]; }, /** * Mélange aléatoirement les éléments d'un tableau * @param array - Le tableau à mélanger * @returns Un nouveau tableau mélangé */ shuffle: function (array: Array<any>): Array<any> { const shuffled = [...array]; for (let i = shuffled.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; } return shuffled; }, /** * Divise un tableau en chunks de taille spécifiée * @param array - Le tableau à diviser * @param size - Taille de chaque chunk * @returns Un tableau de chunks */ chunk: function (array: Array<any>, size: number): Array<Array<any>> { const chunks = []; for (let i = 0; i < array.length; i += size) { chunks.push(array.slice(i, i + size)); } return chunks; }, /** * Trouve l'intersection de deux tableaux * @param array1 - Premier tableau * @param array2 - Second tableau * @returns Les éléments communs aux deux tableaux */ intersection: function (array1: Array<any>, array2: Array<any>): Array<any> { return array1.filter(value => array2.includes(value)); }, /** * Trouve la différence entre deux tableaux * @param array1 - Premier tableau * @param array2 - Second tableau * @returns Les éléments du premier tableau qui ne sont pas dans le second */ difference: function (array1: Array<any>, array2: Array<any>): Array<any> { return array1.filter(value => !array2.includes(value)); }, }, object: { /** * Clone profondément un objet * @param obj - L'objet à cloner * @returns Une copie profonde de l'objet */ deepClone: function (obj: any): any { if (obj === null || typeof obj !== 'object') { return obj; } if (obj instanceof Date) { return new Date(obj.getTime()); } if (obj instanceof Array) { return obj.map(item => this.deepClone(item)); } if (typeof obj === 'object') { const cloned: any = {}; for (const key in obj) { if (obj.hasOwnProperty(key)) { cloned[key] = this.deepClone(obj[key]); } } return cloned; } }, /** * Fusionne deux objets profondément * @param target - L'objet cible * @param source - L'objet source * @returns L'objet fusionné */ deepMerge: function (target: any, source: any): any { const result = { ...target }; for (const key in source) { if (source.hasOwnProperty(key)) { if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) { result[key] = this.deepMerge(result[key] || {}, source[key]); } else { result[key] = source[key]; } } } return result; }, /** * Obtient une valeur dans un objet par un chemin de propriétés * @param obj - L'objet source * @param path - Le chemin vers la propriété (ex: "user.profile.name") * @param defaultValue - Valeur par défaut si la propriété n'existe pas * @returns La valeur trouvée ou la valeur par défaut */ get: function (obj: any, path: string, defaultValue: any = undefined): any { const keys = path.split('.'); let result = obj; for (const key of keys) { if (result === null || result === undefined || !(key in result)) { return defaultValue; } result = result[key]; } return result; }, /** * Définit une valeur dans un objet par un chemin de propriétés * @param obj - L'objet cible * @param path - Le chemin vers la propriété (ex: "user.profile.name") * @param value - La valeur à définir * @returns L'objet modifié */ set: function (obj: any, path: string, value: any): any { const keys = path.split('.'); let current = obj; for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; if (!(key in current) || typeof current[key] !== 'object') { current[key] = {}; } current = current[key]; } current[keys[keys.length - 1]] = value; return obj; }, /** * Supprime les propriétés avec des valeurs nulles ou undefined * @param obj - L'objet à nettoyer * @returns L'objet nettoyé */ removeNullish: function (obj: any): any { const cleaned: any = {}; for (const key in obj) { if (obj.hasOwnProperty(key) && obj[key] !== null && obj[key] !== undefined) { if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) { cleaned[key] = this.removeNullish(obj[key]); } else { cleaned[key] = obj[key]; } } } return cleaned; }, }, number: { /** * Formate un nombre avec des séparateurs de milliers * @param num - Le nombre à formater * @param locale - La locale à utiliser (défaut: 'fr-FR') * @returns Le nombre formaté */ format: function (num: number, locale: string = 'fr-FR'): string { return new Intl.NumberFormat(locale).format(num); }, /** * Formate un nombre en devise * @param num - Le nombre à formater * @param currency - La devise (défaut: 'EUR') * @param locale - La locale à utiliser (défaut: 'fr-FR') * @returns Le nombre formaté en devise */ formatCurrency: function (num: number, currency: string = 'EUR', locale: string = 'fr-FR'): string { return new Intl.NumberFormat(locale, { style: 'currency', currency: currency }).format(num); }, /** * Génère un nombre aléatoire entre min et max (inclus) * @param min - Valeur minimale * @param max - Valeur maximale * @returns Un nombre aléatoire */ random: function (min: number, max: number): number { return Math.floor(Math.random() * (max - min + 1)) + min; }, /** * Arrondit un nombre à un nombre spécifié de décimales * @param num - Le nombre à arrondir * @param decimals - Nombre de décimales (défaut: 2) * @returns Le nombre arrondi */ round: function (num: number, decimals: number = 2): number { return Math.round(num * Math.pow(10, decimals)) / Math.pow(10, decimals); }, /** * Convertit des octets en format lisible (KB, MB, GB, etc.) * @param bytes - Nombre d'octets * @param decimals - Nombre de décimales (défaut: 2) * @returns La taille formatée */ formatBytes: function (bytes: number, decimals: number = 2): string { if (bytes === 0) return '0 Bytes'; const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; }, /** * Calcule le pourcentage d'une valeur par rapport à un total * @param value - La valeur * @param total - Le total * @param decimals - Nombre de décimales (défaut: 2) * @returns Le pourcentage */ percentage: function (value: number, total: number, decimals: number = 2): number { if (total === 0) return 0; return this.round((value / total) * 100, decimals); }, }, url: { /** * Vérifie si une chaîne est une URL valide * @param url - L'URL à vérifier * @returns true si l'URL est valide */ isValid: function (url: string): boolean { try { new URL(url); return true; } catch { return false; } }, /** * Extrait le domaine d'une URL * @param url - L'URL source * @returns Le domaine ou null si invalide */ getDomain: function (url: string): string | null { try { return new URL(url).hostname; } catch { return null; } }, /** * Construit une URL avec des paramètres de requête * @param baseUrl - L'URL de base * @param params - Les paramètres à ajouter * @returns L'URL complète avec paramètres */ buildWithParams: function (baseUrl: string, params: Record<string, any>): string { const url = new URL(baseUrl); Object.keys(params).forEach(key => { if (params[key] !== null && params[key] !== undefined) { url.searchParams.set(key, params[key].toString()); } }); return url.toString(); }, /** * Parse les paramètres de requête d'une URL * @param url - L'URL à parser * @returns Un objet avec les paramètres */ parseParams: function (url: string): Record<string, string> { try { const urlObj = new URL(url); const params: Record<string, string> = {}; urlObj.searchParams.forEach((value, key) => { params[key] = value; }); return params; } catch { return {}; } }, }, validation: { /** * Vérifie si une valeur est un JSON valide * @param str - La chaîne à vérifier * @returns true si c'est un JSON valide */ isValidJSON: function (str: string): boolean { try { JSON.parse(str); return true; } catch { return false; } }, /** * Vérifie la force d'un mot de passe * @param password - Le mot de passe à vérifier * @returns Un objet avec le score et les détails */ passwordStrength: function (password: string): { score: number; strength: string; feedback: string[]; } { let score = 0; const feedback: string[] = []; // Longueur if (password.length >= 8) score += 1; else feedback.push('Au moins 8 caractères'); if (password.length >= 12) score += 1; // Minuscules if (/[a-z]/.test(password)) score += 1; else feedback.push('Au moins une minuscule'); // Majuscules if (/[A-Z]/.test(password)) score += 1; else feedback.push('Au moins une majuscule'); // Chiffres if (/\d/.test(password)) score += 1; else feedback.push('Au moins un chiffre'); // Caractères spéciaux if (/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)) score += 1; else feedback.push('Au moins un caractère spécial'); let strength = 'Très faible'; if (score >= 2) strength = 'Faible'; if (score >= 4) strength = 'Moyen'; if (score >= 5) strength = 'Fort'; if (score >= 6) strength = 'Très fort'; return { score, strength, feedback }; }, /** * Vérifie si une chaîne contient uniquement des caractères alphanumériques * @param str - La chaîne à vérifier * @returns true si alphanumérique uniquement */ isAlphanumeric: function (str: string): boolean { return /^[a-zA-Z0-9]+$/.test(str); }, /** * Vérifie si une chaîne est un code postal français valide * @param postalCode - Le code postal à vérifier * @returns true si valide */ isFrenchPostalCode: function (postalCode: string): boolean { return /^[0-9]{5}$/.test(postalCode); }, /** * Vérifie si une chaîne est un numéro SIRET français valide * @param siret - Le numéro SIRET à vérifier * @returns true si valide */ isSIRET: function (siret: string): boolean { if (!/^\d{14}$/.test(siret)) return false; // Algorithme de validation SIRET let sum = 0; for (let i = 0; i < 14; i++) { let digit = parseInt(siret[i]); if (i % 2 === 1) { digit *= 2; if (digit > 9) digit -= 9; } sum += digit; } return sum % 10 === 0; }, /** * Vérifie si une chaîne est un IBAN valide * @param iban - L'IBAN à vérifier * @returns true si valide */ isIBAN: function (iban: string): boolean { // Supprime les espaces et met en majuscules const cleanIban = iban.replace(/\s/g, '').toUpperCase(); // Vérifie le format de base if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]+$/.test(cleanIban)) return false; // Réorganise l'IBAN pour la validation const rearranged = cleanIban.slice(4) + cleanIban.slice(0, 4); // Convertit les lettres en nombres const numericString = rearranged.replace(/[A-Z]/g, (char) => (char.charCodeAt(0) - 55).toString() ); // Calcule le modulo 97 let remainder = 0; for (let i = 0; i < numericString.length; i++) { remainder = (remainder * 10 + parseInt(numericString[i])) % 97; } return remainder === 1; }, }, /** * Construit un objet d'erreur API standardisé * @param payload - Les données d'erreur * @returns L'objet d'erreur formaté */ buildApiError: (payload: any) => { let error = { errors: { value: coddyger.string.isEmpty(payload.value) ? "" : payload.value, msg: payload.msg, param: coddyger.string.isEmpty(payload.param) ? "" : payload.param, location: coddyger.string.isEmpty(payload.location) ? "" : payload.location, }, }; return error; }, /** * Obtient la date courante ou la formate * @param type - 'string' pour format chaîne, sinon objet Date * @returns La date formatée */ getDate: (type: string = "") => { var d = new Date(), month = "" + (d.getMonth() + 1), day = "" + d.getDate(), year = d.getFullYear(); if (month.length < 2) month = "0" + month; if (day.length < 2) day = "0" + day; return type === "string" ? [year, month, day].join("-") : new Date([year, month, day].join("-")); }, file: { /** * Supprime un fichier * @param filePath - Chemin du fichier à supprimer */ remove: function (filePath: string) { return fs.unlinkSync(filePath); }, /** * Récupère l'extension d'un fichier * @param filename - Nom du fichier * @returns L'extension en minuscules */ extension: function (filename: string) { return path.extname(filename).toLowerCase(); }, /** * Convertit un fichier en Base64 * @param filename - Nom du fichier * @returns Le contenu du fichier en Base64 */ toBase64: (filename: string) => { return fs.readFileSync(filename, { encoding: "base64" }); }, /** * Vérifie si un fichier existe * @param filePath - Chemin du fichier * @returns true si le fichier existe */ exists: (filePath: string) => { return fs.existsSync(filePath); }, /** * Télécharge un fichier depuis une URL * @param url - URL du fichier * @param filename - Nom du fichier de destination * @returns Une promesse résolue quand le téléchargement est terminé */ download: (url: string, filename: string) => { return new Promise((resolve, reject) => { let file = fs.createWriteStream( coddyger.root() + process.env.DOWNLOAD_PATH + "/" + filename ); http.get(url, function (response: any) { response.pipe(file); file.on("finish", function () { resolve(file); file.close(); }); }); }).catch((e) => { return { error: true, data: e, messsage: "" }; }); }, }, /** * Calcule le nombre de jours entre une date et aujourd'hui * @param startDate - Date de début * @returns Nombre de jours écoulés */ calculateDaysBetween(startDate: Date): number { const startDateObj: Date = startDate; const currentDateObj: Date = new Date(); // Calculate the time difference in milliseconds const timeDifference: number = currentDateObj.getTime() - startDateObj.getTime(); // Convert milliseconds to days const daysDifference = Math.floor(timeDifference / (1000 * 60 * 60 * 24)); return daysDifference; }, /** * Génère un slug unique avec préfixe et date * @param prefix - Préfixe du slug * @param lastReference - Dernière référence utilisée * @returns Le nouveau slug généré */ buildSlug(prefix: string, lastReference?: string | null): string { let newIndex = 1; if (lastReference) { const lastCodeParts = lastReference.split("-"); const lastCodeIndex = parseInt( lastCodeParts[lastCodeParts.length - 1], 10 ); if (!isNaN(lastCodeIndex)) { newIndex = lastCodeIndex + 1; } else { throw new Error("Invalid reference format. Cannot extract index."); } } const currentDate = new Date(); const year = currentDate.getFullYear().toString().slice(-2); const month = ("0" + (currentDate.getMonth() + 1)).slice(-2); const day = ("0" + currentDate.getDate()).slice(-2); const newIndexStr = ("000" + newIndex).slice(-3); let uniq: any = new mongoose.Types.ObjectId(); uniq = uniq.toString(); uniq = uniq.substring(uniq.length - 12); const code = `${prefix}-${year}${month}${day}-${uniq}-${newIndexStr}`; return code; }, /** * Obtient le nombre de jours dans un mois donné * @param month - Numéro du mois (1-12) * @param year - Année * @returns Nombre de jours dans le mois */ getDaysInMonth: (month: number, year: number) => { // Validate the month number (1-12) if (month < 1 || month > 12) { throw new Error( "Invalid month number. Month number should be between 1 and 12." ); } const date: any = new Date(year, month - 1, 1); // On utilise month - 1 car les mois commencent à partir de 0 dans JavaScript const days: any = []; while (date.getMonth() === month - 1) { const day: any = date.getDate(); days.push(day); date.setDate(day + 1); } return days; }, /** * Obtient les semaines d'un mois donné * @param month - Numéro du mois (1-12) * @param year - Année * @returns Tableau des semaines avec leurs dates de début et fin */ getWeeksInMonth: (month: number, year: number) => { const weeks = []; const firstDay = new Date(year, month - 1, 1); const lastDay = new Date(year, month, 0); const daysInMonth = lastDay.getDate(); // Initialiser les variables pour suivre le début et la fin d'une semaine let start = 1; let dayOfWeek = firstDay.getDay(); // Si le premier jour du mois n'est pas un lundi, ajuster le début de la première semaine if (dayOfWeek !== 1) { start = 1; } for (let i = 1; i <= daysInMonth; i++) { dayOfWeek = new Date(year, month - 1, i).getDay(); // Si le jour est un dimanche (dernier jour de la semaine) ou le dernier jour du mois, c'est la fin de la semaine if (dayOfWeek === 0 || i === daysInMonth) { const end = i; // Ajouter la semaine au tableau weeks.push({ start, end }); // Le début de la nouvelle semaine est le jour suivant start = i + 1; } } return weeks; }, /** * Formate une date en format YYYY-MM-DD * @param date - La date à formater * @returns La date formatée en chaîne YYYY-MM-DD */ dateOnlyFormat: (date: Date | string): string => { const dateObj = typeof date === 'string' ? new Date(date) : date; return format(dateObj, 'yyyy-MM-dd'); }, dates: { /** * Formate une date selon un format spécifique * @param date - La date à formater * @param formatStr - Le format désiré (ex: 'dd/MM/yyyy') * @param locale - La locale à utiliser (par défaut: fr) * @returns La date formatée selon le format spécifié */ format: (date: Date | string, formatStr: string = 'dd/MM/yyyy', locale = fr): string => { const dateObj = typeof date === 'string' ? parseISO(date) : date; return format(dateObj, formatStr, { locale }); }, /** * Vérifie si une date est valide * @param date - La date à vérifier * @returns true si la date est valide */ isValid: (date: Date | string): boolean => { const dateObj = typeof date === 'string' ? parseISO(date) : date; return isValid(dateObj); }, /** * Ajoute un nombre de jours à une date * @param date - La date de départ * @param days - Nombre de jours à ajouter * @returns La nouvelle date */ addDays: (date: Date | string, days: number): Date => { const dateObj = typeof date === 'string' ? parseISO(date) : date; return addDays(dateObj, days); }, /** * Soustrait un nombre de jours d'une date * @param date - La date de départ * @param days - Nombre de jours à soustraire * @returns La nouvelle date */ subDays: (date: Date | string, days: number): Date => { const dateObj = typeof date === 'string' ? parseISO(date) : date; return subDays(dateObj, days); }, /** * Calcule la différence en jours entre deux dates * @param dateLeft - Première date * @param dateRight - Deuxième date * @returns Nombre de jours de différence */ diffInDays: (dateLeft: Date | string, dateRight: Date | string): number => { const dateLeftObj = typeof dateLeft === 'string' ? parseISO(dateLeft) : dateLeft; const dateRightObj = typeof dateRight === 'string' ? parseISO(dateRight) : dateRight; return differenceInDays(dateLeftObj, dateRightObj); }, /** * Obtient le début de la semaine pour une date donnée * @param date - La date * @returns Date du début de la semaine */ startOfWeek: (date: Date | string): Date => { const dateObj = typeof date === 'string' ? parseISO(date) : date; return startOfWeek(dateObj, { locale: fr }); }, /** * Obtient la fin de la semaine pour une date donnée * @param date - La date * @returns Date de fin de la semaine */ endOfWeek: (date: Date | string): Date => { const dateObj = typeof date === 'string' ? parseISO(date) : date; return endOfWeek(dateObj, { locale: fr }); }, /** * Obtient le début du mois pour une date donnée * @param date - La date * @returns Date du début du mois */ startOfMonth: (date: Date | string): Date => { const dateObj = typeof date === 'string' ? parseISO(date) : date; return startOfMonth(dateObj); }, /** * Obtient la fin du mois pour une date donnée * @param date - La date * @returns Date de fin du mois */ endOfMonth: (date: Date | string): Date => { const dateObj = typeof date === 'string' ? parseISO(date) : date; return endOfMonth(dateObj); }, /** * Vérifie si une date est dans un intervalle * @param date - La date à vérifier * @param startDate - Date de début de l'intervalle * @param endDate - Date de fin de l'intervalle * @returns true si la date est dans l'intervalle */ isWithinInterval: (date: Date | string, startDate: Date | string, endDate: Date | string): boolean => { const dateObj = typeof date === 'string' ? parseISO(date) : date; const start = typeof startDate === 'string' ? parseISO(startDate) : startDate; const end = typeof endDate === 'string' ? parseISO(endDate) : endDate; return isWithinInterval(dateObj, { start, end }); }, /** * Obtient le nombre de jours dans un mois donné * @param month - Numéro du mois (1-12) * @param year - Année * @returns Nombre de jours dans le mois */ getDaysInMonthCount: (month: number, year: number): number => { // Valider le numéro du mois (1-12) if (month < 1 || month > 12) { throw new Error('Le numéro du mois doit être compris entre 1 et 12'); } // Le jour 0 du mois suivant nous donne le dernier jour du mois actuel return new Date(year, month, 0).getDate(); }, }, /** * Retourne le chemin absolu du répertoire racine de l'application * @returns Le chemin absolu de la racine du projet */ root: (): string => { return path.resolve(process.cwd()); }, /** * Inclut rcursivement les fichiers d'un répertoire selon une extension * @param dir - Le répertoire à scanner * @param payload - L'extension des fichiers à inclure (sans le point) * @returns Un tableau des classes exportées depuis les fichiers */ filesInclude(dir: any, payload: string) { const exportedFiles: any[] = []; const files = fs.readdirSync(dir); for (const file of files) { // Exclure le fichier index.ts lui-même if (file !== 'index.js' && file.endsWith('.' + payload + '.js')) { const routeModule = require(path.join(dir, file)); for (const moduleExport of Object.values(routeModule)) { // Vérifiez si c'est une classe (vous pouvez ajouter des vérifications supplémentaires si nécessaire) if (typeof moduleExport === 'function') { exportedFiles.push(moduleExport); } } } } // Exportez toutes les classes de route découvertes return exportedFiles; }, /** * Gère les retours d'erreur de manière standardisée * @param e - L'erreur à traiter * @param location - L'emplacement où l'erreur s'est produite * @param method - La méthode où l'erreur s'est produite * @returns Un objet d'erreur formaté */ catchReturn(e: any, location: string, method: string) { // Log any errors that occur during the process LoggerService.log({ type: LogLevel.Error, content: JSON.stringify(e), location, method }); // Return a predefined error object for the catch block if (env.mode === 'dev') { return { status: defines.status.serverError, message: defines.message.tryCatch, data: e }; } else { return defines.controlerTryCatchObject; } }, }; export default coddyger;