Generate adoption reports as PDFs
This commit is contained in:
@@ -64,6 +64,7 @@ import {
|
||||
updateVetVisitForBird,
|
||||
} from './repositories/birdRepository.js';
|
||||
import { createIntegrationTokenRecord, listIntegrationTokens, revokeIntegrationToken } from './repositories/integrationTokenRepository.js';
|
||||
import { renderAdoptionReportPdf } from './reports/adoptionReport.js';
|
||||
import {
|
||||
createAuditLogEntry,
|
||||
createFlockNote,
|
||||
@@ -149,6 +150,7 @@ const trustProxy = process.env.TRUST_PROXY?.trim() ?? '';
|
||||
const milestoneRemindersEnabled = (process.env.MILESTONE_REMINDERS_ENABLED ?? 'true').toLowerCase() !== 'false';
|
||||
const milestoneReminderTimeZone = process.env.MILESTONE_REMINDER_TIME_ZONE?.trim() || 'America/New_York';
|
||||
const milestoneReminderCheckIntervalMs = 60 * 60 * 1000;
|
||||
const adoptionReportWeightHistoryDays = 425;
|
||||
const photoDeliveryMode = process.env.PHOTO_DELIVERY_MODE === 'redirect' ? 'redirect' : 'proxy';
|
||||
|
||||
if (trustProxy) {
|
||||
@@ -781,6 +783,7 @@ app.disable('x-powered-by');
|
||||
app.use(helmet({ crossOriginResourcePolicy: false }));
|
||||
app.use(
|
||||
cors({
|
||||
exposedHeaders: ['X-FlockPal-Transfer-Code'],
|
||||
origin(origin, callback) {
|
||||
if (!origin || allowedOrigins.includes(origin)) {
|
||||
callback(null, true);
|
||||
@@ -3362,6 +3365,92 @@ app.post(
|
||||
},
|
||||
);
|
||||
|
||||
app.post(
|
||||
'/api/birds/:birdId/reports/adoption',
|
||||
requireAuth,
|
||||
requireWriteAccess,
|
||||
requireSessionAuth,
|
||||
requireWorkspaceRole(['owner', 'assistant']),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const sourceBird = await getBirdById(req.params.birdId, req.auth!.workspace.id);
|
||||
|
||||
if (!sourceBird) {
|
||||
res.status(404).json({ error: 'Bird not found.' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ensureBirdWritable(sourceBird, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let transferCode = null;
|
||||
|
||||
for (let attempt = 0; attempt < 3; attempt += 1) {
|
||||
try {
|
||||
transferCode = await createBirdTransferCode({
|
||||
code: createBirdTransferCodeValue(),
|
||||
birdId: sourceBird.id,
|
||||
sourceWorkspaceId: req.auth!.workspace.id,
|
||||
requestedByUserId: req.auth!.user.id,
|
||||
});
|
||||
break;
|
||||
} catch (error) {
|
||||
if (typeof error === 'object' && error && 'code' in error && error.code === '23505' && attempt < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (!transferCode) {
|
||||
throw new Error('Unable to create bird transfer code.');
|
||||
}
|
||||
|
||||
const [weights, vetVisits, notes] = await Promise.all([
|
||||
listWeightsForBird(sourceBird.id, req.auth!.workspace.id, adoptionReportWeightHistoryDays),
|
||||
listVetVisitsForBird(sourceBird.id, req.auth!.workspace.id),
|
||||
listFlockNotes(req.auth!.workspace.id),
|
||||
]);
|
||||
const birdNotes = notes.filter((note) => note.bird_id === sourceBird.id);
|
||||
const pdf = await renderAdoptionReportPdf({
|
||||
bird: sourceBird,
|
||||
weights,
|
||||
vetVisits,
|
||||
notes: birdNotes,
|
||||
workspace: req.auth!.workspace,
|
||||
transferCode: transferCode.code,
|
||||
printFriendly: req.query.printFriendly === 'true',
|
||||
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'),
|
||||
},
|
||||
});
|
||||
|
||||
await writeAuditLog(req.auth!, 'bird.adoption_report_created', 'bird', sourceBird.id, sourceBird.name, {
|
||||
transferCodeId: transferCode.id,
|
||||
printFriendly: req.query.printFriendly === 'true',
|
||||
});
|
||||
|
||||
const safeName = sourceBird.name
|
||||
.trim()
|
||||
.replace(/[^a-z0-9]+/gi, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
.toLowerCase() || 'bird';
|
||||
|
||||
res.setHeader('Content-Type', 'application/pdf');
|
||||
res.setHeader('Content-Disposition', `inline; filename="flockpal-adoption-report-${safeName}.pdf"`);
|
||||
res.setHeader('Content-Length', pdf.length.toString());
|
||||
res.setHeader('X-FlockPal-Transfer-Code', transferCode.code);
|
||||
res.send(pdf);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
app.put('/api/birds/:birdId', requireAuth, requireWriteAccess, requireWorkspaceRole(['owner', 'assistant', 'caregiver']), async (req: Request, res: Response, next: NextFunction) => {
|
||||
const parsed = birdSchema.safeParse(req.body);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user