mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-22 06:28:19 +02:00
feat(api): API externe de rapprochement BL / factures
Nouvelle API sous /api/ext/v1, authentifiée par clé (EXTERNAL_API_KEYS, en-tête X-API-Key ou Authorization: Bearer), pour qu'un outil tiers lise magasins, fournisseurs et livraisons livrées (n° BL) puis écrive la référence, les montants et l'échéance de la facture, avec validation/dévalidation optionnelle du rapprochement. - normalizeDateString extrait dans server/dateUtils.ts pour être partagé - /api/ext/ exempté du CSRF (pas de cookie de session) - EXTERNAL_API_KEYS ajouté à .env.example et docker-compose.yml - documentation : docs/API-RAPPROCHEMENT.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2274c9bb4a
commit
6f1a39e4b2
7 files changed
+575
-73
No files matched your search
+5
-73
@@ -4,6 +4,8 @@ import { storage } from "./storage";
|
||||
import { setupLocalAuth, requireAuth } from "./localAuth";
|
||||
import { requireModulePermission, requireAdmin, requirePermission } from "./permissions";
|
||||
import { stripSmtpPassword } from "./sanitize";
|
||||
import { normalizeDateString } from "./dateUtils";
|
||||
import { registerExternalApi } from "./externalApi";
|
||||
|
||||
// Corps de requête sans les champs secrets : pour les logs uniquement
|
||||
function redactBody(body: any): any {
|
||||
@@ -29,79 +31,6 @@ const require = createRequire(import.meta.url);
|
||||
console.log('🔍 Using development storage and authentication');
|
||||
|
||||
|
||||
// Fonction de normalisation des dates pour gérer différents formats de NocoDB
|
||||
function normalizeDateString(dateString: string | null | undefined): string | null {
|
||||
if (!dateString || typeof dateString !== 'string') return null;
|
||||
|
||||
const trimmed = dateString.trim();
|
||||
if (!trimmed) return null;
|
||||
|
||||
try {
|
||||
// Si déjà au format ISO (YYYY-MM-DD), le retourner tel quel
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
// Format slash ou tiret : DD/MM/YYYY, MM/DD/YYYY, DD-MM-YYYY, MM-DD-YYYY
|
||||
const slashMatch = trimmed.match(/^(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})$/);
|
||||
if (slashMatch) {
|
||||
const [, first, second, year] = slashMatch;
|
||||
const firstNum = parseInt(first);
|
||||
const secondNum = parseInt(second);
|
||||
|
||||
// Validation basique des valeurs
|
||||
if (firstNum > 31 || secondNum > 31 || firstNum === 0 || secondNum === 0) {
|
||||
console.warn(`⚠️ Date invalide (valeurs hors limites): ${trimmed}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Déterminer le format en fonction des valeurs
|
||||
let day: string, month: string;
|
||||
|
||||
if (firstNum > 12) {
|
||||
// first > 12 → forcément DD/MM (format français/européen)
|
||||
day = first.padStart(2, '0');
|
||||
month = second.padStart(2, '0');
|
||||
} else if (secondNum > 12) {
|
||||
// second > 12 → forcément MM/DD (format américain)
|
||||
day = second.padStart(2, '0');
|
||||
month = first.padStart(2, '0');
|
||||
} else {
|
||||
// Ambiguïté (les deux < 12) → on assume format français DD/MM par défaut
|
||||
// Pour être plus sûr, on pourrait vérifier la configuration du groupe/locale
|
||||
day = first.padStart(2, '0');
|
||||
month = second.padStart(2, '0');
|
||||
}
|
||||
|
||||
// Validation finale : mois entre 1-12, jour entre 1-31
|
||||
const monthNum = parseInt(month);
|
||||
const dayNum = parseInt(day);
|
||||
if (monthNum < 1 || monthNum > 12 || dayNum < 1 || dayNum > 31) {
|
||||
console.warn(`⚠️ Date invalide après parsing: ${trimmed} → month=${month}, day=${day}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
// Essayer de parser avec Date (format ISO complet avec heures)
|
||||
const date = new Date(trimmed);
|
||||
if (!isNaN(date.getTime())) {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
// Si aucun format reconnu, retourner null (ne pas persister une date invalide)
|
||||
console.warn(`⚠️ Format de date non reconnu: ${trimmed}`);
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('❌ Erreur normalisation date:', error);
|
||||
return null; // Retourner null en cas d'erreur pour éviter de persister des données invalides
|
||||
}
|
||||
}
|
||||
|
||||
// Simple hash password function using crypto
|
||||
async function hashPasswordSimple(password: string) {
|
||||
const crypto = await import('crypto');
|
||||
@@ -165,6 +94,9 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
const environment = process.env.NODE_ENV || 'development';
|
||||
console.log('🌍 Environment detected:', environment);
|
||||
|
||||
// API externe de rapprochement (clé d'API, hors session) : docs/API-RAPPROCHEMENT.md
|
||||
registerExternalApi(app);
|
||||
|
||||
// Le mot de passe SMTP des magasins ne doit jamais sortir du serveur.
|
||||
// Les objets "group" sont joints à de nombreuses réponses (livraisons,
|
||||
// commandes, utilisateurs...) : plutôt que de filtrer chaque requête, on
|
||||
|
||||
Reference in new issue
Block a user