From 52008f5b43f6905e8d28c8480efeec7a85f3ac36 Mon Sep 17 00:00:00 2001 From: Corey Blais Date: Tue, 2 Jun 2026 18:26:51 -0400 Subject: [PATCH] Fix adoption report photos and section order --- backend/src/app.ts | 36 +++++++++++++++++++++-- backend/src/reports/adoptionReport.ts | 42 +++++++++++++++------------ 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/backend/src/app.ts b/backend/src/app.ts index d7e9e3f..b55bf56 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -1353,6 +1353,37 @@ const deleteBirdPhotoObjectIfNeeded = async (objectKey: string | null) => { } }; +const loadBirdReportPhotoBuffer = async (bird: BirdRow) => { + if (!bird.photo_object_key) { + return null; + } + + const s3Config = getS3ImageStorageConfig(); + + if (!s3Config) { + return null; + } + + const signedUrl = getSignedS3ObjectUrl({ + config: s3Config, + objectKey: bird.photo_object_key, + expiresInSeconds: 5 * 60, + }); + const imageResponse = await fetch(signedUrl); + + if (!imageResponse.ok) { + return null; + } + + const contentType = imageResponse.headers.get('content-type') || bird.photo_content_type || ''; + + if (!/^image\/(?:png|jpe?g)$/i.test(contentType)) { + return null; + } + + return Buffer.from(await imageResponse.arrayBuffer()); +}; + const getDefaultBirdPhotoAttachment = () => { const defaultPhotoPath = path.join(process.cwd(), 'assets', 'yoda-default.png'); @@ -3408,10 +3439,11 @@ app.post( throw new Error('Unable to create bird transfer code.'); } - const [weights, vetVisits, notes] = await Promise.all([ + const [weights, vetVisits, notes, birdPhotoBuffer] = await Promise.all([ listWeightsForBird(sourceBird.id, req.auth!.workspace.id, adoptionReportWeightHistoryDays), listVetVisitsForBird(sourceBird.id, req.auth!.workspace.id), listFlockNotes(req.auth!.workspace.id), + loadBirdReportPhotoBuffer(sourceBird), ]); const birdNotes = notes.filter((note) => note.bird_id === sourceBird.id); const pdf = await renderAdoptionReportPdf({ @@ -3419,8 +3451,8 @@ app.post( weights, vetVisits, notes: birdNotes, - workspace: req.auth!.workspace, transferCode: transferCode.code, + birdPhotoBuffer, printFriendly: req.query.printFriendly === 'true', assets: { logoPath: path.join(process.cwd(), 'assets', 'flockpal-logo.png'), diff --git a/backend/src/reports/adoptionReport.ts b/backend/src/reports/adoptionReport.ts index 4786a09..aebf9e8 100644 --- a/backend/src/reports/adoptionReport.ts +++ b/backend/src/reports/adoptionReport.ts @@ -2,15 +2,15 @@ import fs from 'fs'; import PDFDocument from 'pdfkit'; import QRCode from 'qrcode'; -import type { BirdRow, FlockNoteRow, VetVisitRow, WeightRow, WorkspaceRow } from '../types.js'; +import type { BirdRow, FlockNoteRow, VetVisitRow, WeightRow } from '../types.js'; type AdoptionReportInput = { bird: BirdRow; weights: WeightRow[]; vetVisits: VetVisitRow[]; notes: FlockNoteRow[]; - workspace: WorkspaceRow; transferCode: string; + birdPhotoBuffer?: Buffer | null; assets: { logoPath: string; wordmarkPath: string; @@ -256,8 +256,8 @@ export const renderAdoptionReportPdf = async ({ weights, vetVisits, notes, - workspace, transferCode, + birdPhotoBuffer = null, assets, printFriendly = false, }: AdoptionReportInput) => { @@ -275,7 +275,7 @@ export const renderAdoptionReportPdf = async ({ const logoPath = fs.existsSync(assets.logoPath) ? assets.logoPath : null; const wordmarkPath = fs.existsSync(assets.wordmarkPath) ? assets.wordmarkPath : logoPath; const defaultPhotoPath = fs.existsSync(assets.defaultBirdPhotoPath) ? assets.defaultBirdPhotoPath : null; - const photoBuffer = dataUrlToBuffer(bird.photo_data_url); + const photoBuffer = birdPhotoBuffer ?? dataUrlToBuffer(bird.photo_data_url); const contentWidth = page.width - page.margin * 2; const headerY = page.margin; const headerHeight = 136; @@ -346,21 +346,6 @@ export const renderAdoptionReportPdf = async ({ ); y += 72; - y = drawSectionTitle(doc, 'Weight Graph', y); - drawSimpleWeightChart(doc, weights, bird.chart_color, page.margin, y, contentWidth, 120); - y += 140; - - y = drawSectionTitle(doc, 'Weight History', y); - y = drawTable( - doc, - ['Date', 'Weight', 'Notes'], - weights.length ? weights.map((entry) => [formatDate(entry.recorded_on), formatWeight(entry.weight_grams), entry.notes || '']) : [['No weights recorded.', '', '']], - page.margin, - y, - [95, 70, contentWidth - 165], - 24, - ); - if (y > 610) { doc.addPage(); y = page.margin; @@ -388,6 +373,25 @@ export const renderAdoptionReportPdf = async ({ 28, ); + if (y > 575) { + doc.addPage(); + y = page.margin; + } + y = drawSectionTitle(doc, 'Weight Graph', y); + drawSimpleWeightChart(doc, weights, bird.chart_color, page.margin, y, contentWidth, 120); + y += 140; + + y = drawSectionTitle(doc, 'Weight History', y); + y = drawTable( + doc, + ['Date', 'Weight', 'Notes'], + weights.length ? weights.map((entry) => [formatDate(entry.recorded_on), formatWeight(entry.weight_grams), entry.notes || '']) : [['No weights recorded.', '', '']], + page.margin, + y, + [95, 70, contentWidth - 165], + 24, + ); + if (notes.length) { if (y > 635) { doc.addPage();