mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-19 13:08:23 +02:00
Merge pull request #526 from R0m1k3/1.1
Remove Multer dependency and implement custom multipart form data par…
This commit is contained in:
1 file changed
+74
-17
+74
-17
@@ -4,7 +4,6 @@ import { storage } from "./storage";
|
|||||||
import { setupLocalAuth, requireAuth } from "./localAuth";
|
import { setupLocalAuth, requireAuth } from "./localAuth";
|
||||||
import { requireModulePermission, requireAdmin, requirePermission } from "./permissions";
|
import { requireModulePermission, requireAdmin, requirePermission } from "./permissions";
|
||||||
import { db, pool } from "./db";
|
import { db, pool } from "./db";
|
||||||
import multer from "multer";
|
|
||||||
|
|
||||||
console.log('🔍 Using development storage and authentication');
|
console.log('🔍 Using development storage and authentication');
|
||||||
|
|
||||||
@@ -789,14 +788,53 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configuration multer pour envoi de factures
|
// Parser multipart/form-data manuellement (sans busboy/multer)
|
||||||
const invoiceUpload = multer({
|
function parseMultipart(buffer: Buffer, boundary: string) {
|
||||||
storage: multer.memoryStorage(),
|
const parts: any = {};
|
||||||
limits: { fileSize: 10 * 1024 * 1024 } // 10MB max
|
const boundaryBuffer = Buffer.from(`--${boundary}`);
|
||||||
});
|
const sections = [];
|
||||||
|
|
||||||
|
let start = 0;
|
||||||
|
while (true) {
|
||||||
|
const boundaryIndex = buffer.indexOf(boundaryBuffer, start);
|
||||||
|
if (boundaryIndex === -1) break;
|
||||||
|
|
||||||
|
if (start > 0) {
|
||||||
|
sections.push(buffer.slice(start, boundaryIndex));
|
||||||
|
}
|
||||||
|
start = boundaryIndex + boundaryBuffer.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const section of sections) {
|
||||||
|
const headerEnd = section.indexOf('\r\n\r\n');
|
||||||
|
if (headerEnd === -1) continue;
|
||||||
|
|
||||||
|
const headers = section.slice(0, headerEnd).toString();
|
||||||
|
const content = section.slice(headerEnd + 4, section.length - 2);
|
||||||
|
|
||||||
|
const nameMatch = headers.match(/name="([^"]+)"/);
|
||||||
|
const filenameMatch = headers.match(/filename="([^"]+)"/);
|
||||||
|
const contentTypeMatch = headers.match(/Content-Type: ([^\r\n]+)/);
|
||||||
|
|
||||||
|
if (!nameMatch) continue;
|
||||||
|
const name = nameMatch[1];
|
||||||
|
|
||||||
|
if (filenameMatch) {
|
||||||
|
parts[name] = {
|
||||||
|
filename: filenameMatch[1],
|
||||||
|
contentType: contentTypeMatch ? contentTypeMatch[1] : 'application/octet-stream',
|
||||||
|
buffer: content
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
parts[name] = content.toString('utf-8');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
// Route proxy SIMPLE pour envoi de factures (admin + directeur)
|
// Route proxy SIMPLE pour envoi de factures (admin + directeur)
|
||||||
app.post('/api/reconciliation/send-invoice', isAuthenticated, invoiceUpload.single('file'), async (req: any, res) => {
|
app.post('/api/reconciliation/send-invoice', isAuthenticated, async (req: any, res) => {
|
||||||
try {
|
try {
|
||||||
const userId = req.user?.claims?.sub || req.user?.id;
|
const userId = req.user?.claims?.sub || req.user?.id;
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
@@ -808,15 +846,34 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
return res.status(403).json({ error: 'Accès refusé' });
|
return res.status(403).json({ error: 'Accès refusé' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!req.file || !req.body.webhookUrl) {
|
// Parser le multipart/form-data manuellement
|
||||||
|
const contentType = req.headers['content-type'] || '';
|
||||||
|
const boundaryMatch = contentType.match(/boundary=(.+)$/);
|
||||||
|
if (!boundaryMatch) {
|
||||||
|
return res.status(400).json({ error: 'Format multipart invalide' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const boundary = boundaryMatch[1];
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
req.on('data', (chunk: Buffer) => chunks.push(chunk));
|
||||||
|
req.on('end', () => resolve());
|
||||||
|
req.on('error', reject);
|
||||||
|
});
|
||||||
|
|
||||||
|
const buffer = Buffer.concat(chunks);
|
||||||
|
const parts = parseMultipart(buffer, boundary);
|
||||||
|
|
||||||
|
if (!parts.file || !parts.webhookUrl) {
|
||||||
return res.status(400).json({ error: 'Fichier ou webhook manquant' });
|
return res.status(400).json({ error: 'Fichier ou webhook manquant' });
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('📤 INVOICE PROXY: Envoi facture', {
|
console.log('📤 INVOICE PROXY: Envoi facture', {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
role: user.role,
|
role: user.role,
|
||||||
fileName: req.file.originalname,
|
fileName: parts.file.filename,
|
||||||
size: req.file.size
|
size: parts.file.buffer.length
|
||||||
});
|
});
|
||||||
|
|
||||||
// Importer form-data dynamiquement avec eval pour ESM
|
// Importer form-data dynamiquement avec eval pour ESM
|
||||||
@@ -824,15 +881,15 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
const FormData = FormDataModule.default;
|
const FormData = FormDataModule.default;
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', req.file.buffer, {
|
formData.append('file', parts.file.buffer, {
|
||||||
filename: req.file.originalname,
|
filename: parts.file.filename,
|
||||||
contentType: req.file.mimetype
|
contentType: parts.file.contentType
|
||||||
});
|
});
|
||||||
formData.append('supplier', req.body.supplier || '');
|
formData.append('supplier', parts.supplier || '');
|
||||||
formData.append('blNumber', req.body.blNumber || '');
|
formData.append('blNumber', parts.blNumber || '');
|
||||||
formData.append('type', req.body.type || 'Facture');
|
formData.append('type', parts.type || 'Facture');
|
||||||
|
|
||||||
const response = await fetch(req.body.webhookUrl, {
|
const response = await fetch(parts.webhookUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData as any,
|
body: formData as any,
|
||||||
headers: formData.getHeaders()
|
headers: formData.getHeaders()
|
||||||
|
|||||||
Reference in new issue
Block a user