fix(api): export PDF a 0 byte — bufferizza chunks pdfkit prima di send
Il pattern \`reply.send(doc)\` con uno stream pdfkit ha una race con fastify: il body può chiudersi prima che pdfkit emetta tutti i chunks dopo \`doc.end()\`. Ora colleziono i chunks in memoria, attendo \`doc.end()\` via Promise sull'evento \`end\`, e mando il Buffer completo con \`Content-Length\` corretto. Applicato a entrambi gli export (piano settimanale + tracking). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
e015abff33
commit
42a6ec57e2
2 files changed
+28
-7
No files matched your search
@@ -52,9 +52,15 @@ export const planExportRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
}
|
||||
|
||||
const doc = new PDFDocument({ size: 'A4', margin: 50, bufferPages: true });
|
||||
reply.header('Content-Type', 'application/pdf');
|
||||
reply.header('Content-Disposition', 'attachment; filename="ketopath-piano.pdf"');
|
||||
reply.send(doc);
|
||||
// Bufferizziamo i chunks: fastify + pdfkit stream hanno una race se
|
||||
// facciamo `reply.send(doc)` e poi `doc.end()`. Con il buffer chiudiamo
|
||||
// il PDF prima di inviarlo e siamo sicuri che il body sia completo.
|
||||
const chunks: Buffer[] = [];
|
||||
doc.on('data', (c: Buffer) => chunks.push(c));
|
||||
const done = new Promise<Buffer>((resolve, reject) => {
|
||||
doc.on('end', () => resolve(Buffer.concat(chunks)));
|
||||
doc.on('error', reject);
|
||||
});
|
||||
|
||||
// ── Header ───────────────────────────────────────────────────────────
|
||||
doc
|
||||
@@ -225,6 +231,12 @@ export const planExportRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
}
|
||||
|
||||
doc.end();
|
||||
const pdfBuffer = await done;
|
||||
return reply
|
||||
.header('Content-Type', 'application/pdf')
|
||||
.header('Content-Disposition', 'attachment; filename="ketopath-piano.pdf"')
|
||||
.header('Content-Length', String(pdfBuffer.length))
|
||||
.send(pdfBuffer);
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -39,10 +39,13 @@ export const trackingExportRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
]);
|
||||
|
||||
const doc = new PDFDocument({ size: 'A4', margin: 50, bufferPages: true });
|
||||
|
||||
reply.header('Content-Type', 'application/pdf');
|
||||
reply.header('Content-Disposition', 'attachment; filename="ketopath-export.pdf"');
|
||||
reply.send(doc);
|
||||
// Bufferizziamo i chunks per evitare race con fastify (vedi plan export).
|
||||
const chunks: Buffer[] = [];
|
||||
doc.on('data', (c: Buffer) => chunks.push(c));
|
||||
const done = new Promise<Buffer>((resolve, reject) => {
|
||||
doc.on('end', () => resolve(Buffer.concat(chunks)));
|
||||
doc.on('error', reject);
|
||||
});
|
||||
|
||||
// ── Header ─────────────────────────────────────────────────────────
|
||||
doc
|
||||
@@ -151,5 +154,11 @@ export const trackingExportRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
);
|
||||
|
||||
doc.end();
|
||||
const pdfBuffer = await done;
|
||||
return reply
|
||||
.header('Content-Type', 'application/pdf')
|
||||
.header('Content-Disposition', 'attachment; filename="ketopath-export.pdf"')
|
||||
.header('Content-Length', String(pdfBuffer.length))
|
||||
.send(pdfBuffer);
|
||||
});
|
||||
};
|
||||
Reference in new issue
Block a user