diff --git a/scratch/test_webhook.js b/scratch/test_webhook.js new file mode 100644 index 0000000..a83d0f9 --- /dev/null +++ b/scratch/test_webhook.js @@ -0,0 +1,33 @@ +import fetch from 'node-fetch'; +import FormData from 'form-data'; + +const webhookUrl = 'https://workflow.ffnancy.fr/webhook-test/acf9cbf7-040a-4cf5-a43d-80210420d30a'; + +async function testWebhook() { + console.log('🚀 Testing webhook:', webhookUrl); + + const formData = new FormData(); + formData.append('file', Buffer.from('test content'), { + filename: 'test.txt', + contentType: 'text/plain' + }); + formData.append('supplier', 'Test Supplier'); + formData.append('blNumber', '12345'); + formData.append('type', 'Facture'); + + try { + const response = await fetch(webhookUrl, { + method: 'POST', + body: formData, + headers: formData.getHeaders() + }); + + console.log('📡 Response status:', response.status); + const text = await response.text(); + console.log('📄 Response body:', text); + } catch (error) { + console.error('❌ Error calling webhook:', error); + } +} + +testWebhook(); diff --git a/scratch/test_webhook_native.js b/scratch/test_webhook_native.js new file mode 100644 index 0000000..5597ce0 --- /dev/null +++ b/scratch/test_webhook_native.js @@ -0,0 +1,27 @@ +const webhookUrl = 'https://workflow.ffnancy.fr/webhook-test/acf9cbf7-040a-4cf5-a43d-80210420d30a'; + +async function testWebhook() { + console.log('🚀 Testing webhook (Native Fetch/FormData):', webhookUrl); + + const formData = new FormData(); + const fileBlob = new Blob(['test content'], { type: 'text/plain' }); + formData.append('file', fileBlob, 'test.txt'); + formData.append('supplier', 'Test Supplier'); + formData.append('blNumber', '12345'); + formData.append('type', 'Facture'); + + try { + const response = await fetch(webhookUrl, { + method: 'POST', + body: formData + }); + + console.log('📡 Response status:', response.status); + const text = await response.text(); + console.log('📄 Response body:', text); + } catch (error) { + console.error('❌ Error calling webhook:', error); + } +} + +testWebhook(); diff --git a/server/routes.ts b/server/routes.ts index 1a08b9f..f1f19a2 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -6,7 +6,8 @@ import { requireModulePermission, requireAdmin, requirePermission } from "./perm import { db, pool } from "./db"; import { createRequire } from "module"; const require = createRequire(import.meta.url); -const FormData = require('form-data'); +// Suppression de l'import form-data car nous utilisons le FormData natif de Node.js 18+ +// const FormData = require('form-data'); console.log('🔍 Using development storage and authentication'); @@ -861,8 +862,10 @@ export async function registerRoutes(app: Express): Promise { // Parser le multipart/form-data manuellement const contentType = req.headers['content-type'] || ''; - const boundaryMatch = contentType.match(/boundary=(.+)$/); + // Gestion plus robuste du boundary (avec ou sans guillemets) + const boundaryMatch = contentType.match(/boundary="?([^";]+)"?/i); if (!boundaryMatch) { + console.error('❌ INVOICE PROXY: Boundary manquant dans Content-Type', contentType); return res.status(400).json({ error: 'Format multipart invalide' }); } @@ -889,27 +892,22 @@ export async function registerRoutes(app: Express): Promise { size: parts.file.buffer.length }); - // Import dynamique standard pour ESM (sans eval) - console.log('🔍 INVOICE PROXY: FormData loaded', { - type: typeof FormData, - isConstructor: typeof FormData === 'function' - }); - + // Utilisation du FormData natif de Node.js (global) + // On utilise Blob pour transformer le Buffer en objet compatible const formData = new FormData(); - formData.append('file', parts.file.buffer, { - filename: parts.file.filename, - contentType: parts.file.contentType - }); + const fileBlob = new Blob([parts.file.buffer], { type: parts.file.contentType }); + + formData.append('file', fileBlob, parts.file.filename); formData.append('supplier', parts.supplier || ''); formData.append('blNumber', parts.blNumber || ''); formData.append('type', parts.type || 'Facture'); console.log('🔗 INVOICE PROXY: Calling webhook URL:', parts.webhookUrl); + // En utilisant le FormData natif avec fetch, pas besoin de headers manuels (boundary géré automatiquement) const response = await fetch(parts.webhookUrl, { method: 'POST', - body: formData as any, - headers: formData.getHeaders() + body: formData as any }); if (!response.ok) { diff --git a/task.md b/task.md index 78f51b9..bbced43 100644 --- a/task.md +++ b/task.md @@ -1,38 +1,30 @@ -# Task: Correction blocage API Publicités +# Task: Debugging Webhook Error 500 on Invoice Sending ## Context -L'utilisateur rencontre une erreur `net::ERR_BLOCKED_BY_CLIENT` lors de l'appel à l'API `/api/publicities`. Cette erreur est typique des bloqueurs de publicité (AdBlock, uBlock) qui filtrent les requêtes contenant des mots-clés comme "publicity" ou "ads". +L'utilisateur a redémarré son serveur et rencontre une erreur 500 lors de l'envoi d'une facture via le proxy `/api/reconciliation/send-invoice`. L'appel vers le webhook N8N (`https://workflow.ffnancy.fr/webhook/...`) échoue avec un code 500. ## Current Focus -Renommer les endpoints API pour éviter le blocage par les bloqueurs de publicité. +Identifier la cause de l'erreur 500 lors de l'appel au webhook N8N et corriger le comportement du proxy de facture. ## Master Plan -- [x] **Remplacement des API calls (Client-side)** - - [x] `client/src/pages/Calendar.tsx` - - [x] `client/src/pages/Dashboard.tsx` - - [x] `client/src/components/PublicityForm.tsx` - - [x] `client/src/pages/Publicities.tsx` - - [x] `client/src/pages/mobile/PublicitiesPage.tsx` - -- [x] **Revue du sélecteur d'année Mobile** - - [x] Vérifier la logique de génération des années dans `client/src/pages/mobile/PublicitiesPage.tsx` pour inclure les années passées (ex: 2024 en 2026). - - [x] Aligner avec la correction desktop si nécessaire. - -- [x] **Correction des erreurs Lint TypeScript** - - [x] Installer les paquets `@types` manquants (`node`, `express`, `react`, etc.). - - [x] Fixer les paramètres `req` et `res` (implicitement `any`) dans `server/routes.ts`. - - [x] Corriger l'erreur de propriété `invoiceAmountTTC` dans `server/routes.ts`. - - [x] Résoudre `Property 'env' does not exist on type 'ImportMeta'`. - - [x] Résoudre `JSX element implicitly has type 'any'`. - -- [x] **Tests et Validation** - - [x] Vérifier que les appels API utilisent bien `/api/ad-campaigns`. - - [x] Tester le sélecteur d'année sur mobile. - - [x] Compiler le projet (`npm run check`) pour s'assurer que les erreurs critiques sont résolues. +- [x] **Analyse du code source** + - [x] Examiner `server/routes.ts` pour trouver l'endpoint `/api/reconciliation/send-invoice`. + - [x] Analyser la construction de la requête vers N8N (utilisation de `FormData` natif vs bibliothèque `form-data`). +- [x] **Investigation technique** + - [x] Vérifier la version de Node.js (v24.11.1 détectée). + - [x] Identifier pourquoi N8N renvoie 500 (problème de format multipart et de headers avec l'ancienne bibliothèque). + - [x] Prendre en compte l'URL de test fournie : `https://workflow.ffnancy.fr/webhook-test/...`. +- [x] **Correction et Test** + - [x] Remplacer l'implémentation personnalisée par une utilisation plus propre des APIs natives Node.js. + - [x] Améliorer le parsing du boundary pour gérer les guillemets. + - [x] Utiliser les APIs natives `fetch` et `FormData` de Node.js pour plus de robustesse. + - [ ] Demander à l'utilisateur de tester après avoir reconstruit son image Docker. ## Progress Log -- Création de la tâche suite aux logs fournis. +- Initialisation de la tâche pour déboguer l'erreur 500 sur l'envoi de facture. +- Analyse du code : le proxy utilisait une ancienne bibliothèque `form-data` manuellement importée via `require`, ce qui peut causer des problèmes de compatibilité en Node 24. +- Correction appliquée : passage aux APIs natives `fetch` et `FormData` de Node.js et amélioration du parsing du boundary.