104 lines
2.7 KiB
TypeScript
104 lines
2.7 KiB
TypeScript
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'),
|
|
},
|
|
});
|
|
};
|