Checkpoint: Correction du bouton téléchargement BAP : la route /api/download-bap utilise maintenant un query param pdfPath pour accepter les chemins complets avec sous-dossiers (ex: /storage/2026-04/BAP_xxx.pdf). Ajout d'une route de compatibilité qui cherche dans tous les sous-dossiers. Sécurité anti-path-traversal maintenue.
This commit is contained in:
@@ -23,9 +23,11 @@ import {
|
|||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
// Helper : télécharge un PDF annoté BAP depuis son URL de stockage
|
// Helper : télécharge un PDF annoté BAP depuis son URL de stockage
|
||||||
|
// pdfUrl peut être : '/storage/2026-04/BAP_xxx.pdf' ou '/storage/BAP_xxx.pdf'
|
||||||
function downloadBapPdf(pdfUrl: string, supplierName?: string | null) {
|
function downloadBapPdf(pdfUrl: string, supplierName?: string | null) {
|
||||||
const filename = pdfUrl.split('/').pop() || 'BAP.pdf';
|
const filename = pdfUrl.split('/').pop() || 'BAP.pdf';
|
||||||
const downloadUrl = `/api/download-bap/${filename}`;
|
// Utiliser le query param pdfPath pour passer le chemin complet au serveur
|
||||||
|
const downloadUrl = `/api/download-bap?pdfPath=${encodeURIComponent(pdfUrl)}`;
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = downloadUrl;
|
a.href = downloadUrl;
|
||||||
a.download = supplierName ? `BAP_${supplierName.replace(/[^a-zA-Z0-9]/g, '_')}.pdf` : filename;
|
a.download = supplierName ? `BAP_${supplierName.replace(/[^a-zA-Z0-9]/g, '_')}.pdf` : filename;
|
||||||
|
|||||||
@@ -35,9 +35,11 @@ import { trpc } from "@/lib/trpc";
|
|||||||
import { Search, FileText, Download, FileSpreadsheet, Trash2, Edit, Trash, CheckCircle, CheckCircle2, ShieldCheck } from "lucide-react";
|
import { Search, FileText, Download, FileSpreadsheet, Trash2, Edit, Trash, CheckCircle, CheckCircle2, ShieldCheck } from "lucide-react";
|
||||||
|
|
||||||
// Helper : télécharge un PDF annoté BAP depuis son URL de stockage
|
// Helper : télécharge un PDF annoté BAP depuis son URL de stockage
|
||||||
|
// pdfUrl peut être : '/storage/2026-04/BAP_xxx.pdf' ou '/storage/BAP_xxx.pdf'
|
||||||
function downloadBapPdf(pdfUrl: string, supplierName?: string) {
|
function downloadBapPdf(pdfUrl: string, supplierName?: string) {
|
||||||
const filename = pdfUrl.split('/').pop() || 'BAP.pdf';
|
const filename = pdfUrl.split('/').pop() || 'BAP.pdf';
|
||||||
const downloadUrl = `/api/download-bap/${filename}`;
|
// Utiliser le query param pdfPath pour passer le chemin complet au serveur
|
||||||
|
const downloadUrl = `/api/download-bap?pdfPath=${encodeURIComponent(pdfUrl)}`;
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = downloadUrl;
|
a.href = downloadUrl;
|
||||||
a.download = supplierName ? `BAP_${supplierName.replace(/[^a-zA-Z0-9]/g, '_')}.pdf` : filename;
|
a.download = supplierName ? `BAP_${supplierName.replace(/[^a-zA-Z0-9]/g, '_')}.pdf` : filename;
|
||||||
|
|||||||
@@ -41,18 +41,57 @@ async function startServer() {
|
|||||||
app.use("/storage", express.static("storage"));
|
app.use("/storage", express.static("storage"));
|
||||||
|
|
||||||
// Route de téléchargement forcé du PDF annoté BAP
|
// Route de téléchargement forcé du PDF annoté BAP
|
||||||
app.get("/api/download-bap/:filename", (req, res) => {
|
// Accepte les chemins avec sous-dossiers : /api/download-bap/2026-04/filename.pdf
|
||||||
const filename = path.basename(req.params.filename); // sécurité : pas de path traversal
|
// ou via query param pdfPath : /api/download-bap/file.pdf?pdfPath=/storage/2026-04/file.pdf
|
||||||
const storagePath = path.resolve("storage", filename);
|
app.get("/api/download-bap", (req, res) => {
|
||||||
|
// Mode 1 : query param pdfPath (chemin complet depuis /storage/...)
|
||||||
|
const pdfPath = req.query.pdfPath as string | undefined;
|
||||||
|
if (!pdfPath) {
|
||||||
|
res.status(400).json({ error: "Paramètre pdfPath manquant" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Sécurité : s'assurer que le chemin est bien dans le dossier storage
|
||||||
|
const normalized = path.normalize(pdfPath).replace(/^\/+/, '');
|
||||||
|
if (normalized.startsWith('..') || !normalized.startsWith('storage')) {
|
||||||
|
res.status(403).json({ error: "Accès refusé" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const storagePath = path.resolve(normalized);
|
||||||
if (!fs.existsSync(storagePath)) {
|
if (!fs.existsSync(storagePath)) {
|
||||||
res.status(404).json({ error: "Fichier introuvable" });
|
res.status(404).json({ error: "Fichier introuvable" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
|
const filename = path.basename(storagePath);
|
||||||
|
res.setHeader("Content-Disposition", `attachment; filename="${encodeURIComponent(filename)}"`);
|
||||||
res.setHeader("Content-Type", "application/pdf");
|
res.setHeader("Content-Type", "application/pdf");
|
||||||
res.sendFile(storagePath);
|
res.sendFile(storagePath);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Compat. ancienne route avec :filename (sans sous-dossier)
|
||||||
|
app.get("/api/download-bap/:filename", (req, res) => {
|
||||||
|
const filename = path.basename(req.params.filename);
|
||||||
|
// Chercher dans tous les sous-dossiers de storage
|
||||||
|
const storageRoot = path.resolve("storage");
|
||||||
|
let found: string | null = null;
|
||||||
|
try {
|
||||||
|
const subdirs = fs.readdirSync(storageRoot);
|
||||||
|
for (const sub of subdirs) {
|
||||||
|
const candidate = path.join(storageRoot, sub, filename);
|
||||||
|
if (fs.existsSync(candidate)) { found = candidate; break; }
|
||||||
|
}
|
||||||
|
// Aussi essayer directement dans storage/
|
||||||
|
const direct = path.join(storageRoot, filename);
|
||||||
|
if (!found && fs.existsSync(direct)) found = direct;
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
if (!found) {
|
||||||
|
res.status(404).json({ error: "Fichier introuvable" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.setHeader("Content-Disposition", `attachment; filename="${encodeURIComponent(filename)}"`);
|
||||||
|
res.setHeader("Content-Type", "application/pdf");
|
||||||
|
res.sendFile(found);
|
||||||
|
});
|
||||||
|
|
||||||
// tRPC API
|
// tRPC API
|
||||||
app.use(
|
app.use(
|
||||||
"/api/trpc",
|
"/api/trpc",
|
||||||
|
|||||||
Reference in New Issue
Block a user