import path from 'path'; import sharp from 'sharp'; import { getBirdById, listVetVisitsForBird, listWeightsForBird, } from '../repositories/birdRepository.js'; import { listFlockNotes } from '../repositories/auditRepository.js'; import { getS3ImageStorageConfig } from '../storage/imageStorageConfig.js'; import { getSignedS3ObjectUrl } from '../storage/s3Client.js'; import type { BirdRow } from '../types.js'; import { renderAdoptionReportPdf } from './adoptionReport.js'; const adoptionReportWeightHistoryDays = 14; const parseDataImage = (value: string | null) => { if (!value) { return null; } const match = value.match(/^data:image\/(?:png|jpeg|jpg|webp|gif);base64,(.+)$/); return match ? Buffer.from(match[1], 'base64') : null; }; const normalizeReportPhotoBuffer = async (imageBuffer: Buffer | null) => { if (!imageBuffer) { return null; } try { return await sharp(imageBuffer).rotate().png().toBuffer(); } catch (error) { console.warn('Unable to normalize bird photo for adoption report:', error); return null; } }; const loadBirdReportPhotoBuffer = async (bird: BirdRow) => { if (!bird.photo_object_key) { return normalizeReportPhotoBuffer(parseDataImage(bird.photo_data_url)); } 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; } return normalizeReportPhotoBuffer(Buffer.from(await imageResponse.arrayBuffer())); }; export const renderAdoptionReportForBird = async ({ birdId, workspaceId, transferCode, printFriendly, }: { birdId: string; workspaceId: number; transferCode: string; printFriendly: boolean; }) => { const bird = await getBirdById(birdId, workspaceId); if (!bird) { throw new Error('Bird not found.'); } const [weights, vetVisits, notes, birdPhotoBuffer] = await Promise.all([ listWeightsForBird(bird.id, workspaceId, adoptionReportWeightHistoryDays), listVetVisitsForBird(bird.id, workspaceId), listFlockNotes(workspaceId), loadBirdReportPhotoBuffer(bird), ]); const birdNotes = notes.filter((note) => note.bird_id === bird.id); return renderAdoptionReportPdf({ bird, weights, vetVisits, notes: birdNotes, transferCode, birdPhotoBuffer, printFriendly, assets: { logoPath: path.join(process.cwd(), 'assets', 'flockpal-logo.png'), wordmarkPath: path.join(process.cwd(), 'assets', 'flockpal-text.png'), defaultBirdPhotoPath: path.join(process.cwd(), 'assets', 'yoda-default.png'), }, }); };