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
text/typescript
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;