feat: save insurance PDFs (eligibility/claims/preauth) to Cloud Storage instead of Postgres blobs

New saves go into the patient's Cloud Storage folder under category subfolders
(Eligibility/Claims/PreAuth/Claim Status/Attachments) via a new
storage.savePdfToCloudStorage helper, instead of the legacy PdfGroup/PdfFile
Postgres blob tables. Old records and their read paths are left untouched for
backward compatibility; PDF list/content/delete endpoints and viewers now
transparently handle both legacy numeric ids and new "cloud:<id>" ids.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-08 00:20:37 -04:00
co-authored by Claude Sonnet 5
parent 1190bc0494
commit 99cdfa702a
36 changed files with 496 additions and 296 deletions
+37 -26
View File
@@ -113,6 +113,11 @@ router.post(
try {
const folder = await storage.getOrCreatePatientFolder(req.user.id, patientId, patientName);
const attachmentsFolder = await storage.getOrCreateSubfolder(
req.user.id,
(folder as any).id,
"Attachments"
);
const result: { filename: string; mimeType: string; filePath: string }[] = [];
for (const file of files) {
@@ -126,14 +131,14 @@ router.post(
file.buffer
);
// Save to cloud storage patient folder (Cloud Storage page)
// Save to cloud storage patient's Attachments subfolder (Cloud Storage page)
const cloudFile = await storage.initializeFileUpload(
req.user.id,
file.originalname,
file.mimetype,
BigInt(file.size),
1,
(folder as any).id
(attachmentsFolder as any).id
);
await storage.appendFileChunk((cloudFile as any).id, 0, file.buffer);
const finalized = await storage.finalizeFileUpload((cloudFile as any).id);
@@ -337,36 +342,28 @@ router.post(
);
}
const GROUP_TITLES: Record<GroupKey, string> = {
const CLOUD_CATEGORIES: Record<GroupKey, "Claims" | "PreAuth"> = {
INSURANCE_CLAIM: "Claims",
INSURANCE_CLAIM_PREAUTH: "Preauth",
INSURANCE_CLAIM_PREAUTH: "PreAuth",
};
const groupTitle = GROUP_TITLES[groupTitleKey];
const cloudCategory = CLOUD_CATEGORIES[groupTitleKey];
// ✅ Find or create PDF group for this claim
let group = await storage.findPdfGroupByPatientTitleKey(
const patient = await storage.getPatient(parsedPatientId);
const patientName = patient
? `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim()
: "";
// ✅ Save PDF file into the patient's Cloud Storage category subfolder
const cloudFile = await storage.savePdfToCloudStorage(
req.user.id,
parsedPatientId,
groupTitleKey
patientName || `Patient ${parsedPatientId}`,
cloudCategory,
filename,
Buffer.from(pdfResponse.data)
);
if (!group) {
group = await storage.createPdfGroup(
parsedPatientId,
groupTitle,
groupTitleKey
);
}
// ✅ Save PDF file into that group
const created = await storage.createPdfFile(group.id!, filename, pdfResponse.data);
// Extract the PDF file ID for opening the viewer
let pdfFileId: number | null = null;
if (created && typeof created === "object" && "id" in created) {
pdfFileId = Number(created.id);
} else if (typeof created === "number") {
pdfFileId = created;
}
const pdfFileId: string = `cloud:${cloudFile.id}`;
return res.json({
success: true,
@@ -942,6 +939,20 @@ router.get(
const safeName = (f.filename || "claim.pdf").replace(/[/\\]/g, "_").trim() || "claim.pdf";
pdfEntries.push({ filename: safeName, data: buf });
}
// New claim PDFs are saved to the patient's Cloud Storage "Claims" subfolder
const cloudFiles = await storage.listPatientCategoryCloudFiles(patientId, "Claims");
for (const cf of cloudFiles) {
if (cf.id == null) continue;
const full = await storage.getFile(cf.id);
if (!full?.diskPath) continue;
const abs = path.join(process.cwd(), full.diskPath);
if (!fs.existsSync(abs)) continue;
const buf = fs.readFileSync(abs);
if (!buf.length) continue;
const safeName = (cf.name || "claim.pdf").replace(/[/\\]/g, "_").trim() || "claim.pdf";
pdfEntries.push({ filename: safeName, data: buf });
}
}
if (pdfEntries.length === 0) {
+75 -5
View File
@@ -150,6 +150,34 @@ router.post(
}
);
// Maps a legacy PdfGroup.titleKey to the matching Cloud Storage subfolder name
// so new (cloud-backed) PDFs show up alongside old (DB-blob) PDFs for the same category.
const TITLE_KEY_TO_CLOUD_CATEGORY: Record<string, string> = {
ELIGIBILITY_STATUS: "Eligibility",
INSURANCE_CLAIM: "Claims",
INSURANCE_CLAIM_PREAUTH: "PreAuth",
CLAIM_STATUS: "Claim Status",
OTHER: "Attachments",
};
async function listCloudPdfsForGroup(groupId: number) {
const group = await storage.getPdfGroupById(groupId);
if (!group) return [];
const category = TITLE_KEY_TO_CLOUD_CATEGORY[group.titleKey as string];
if (!category) return [];
const cloudFiles = await storage.listPatientCategoryCloudFiles(
group.patientId,
category
);
return cloudFiles.map((f) => ({
id: `cloud:${f.id}`,
filename: f.name,
uploadedAt: f.createdAt,
mimeType: f.mimeType,
}));
}
router.get(
"/pdf-files/group/:groupId",
async (req: Request, res: Response): Promise<any> => {
@@ -160,7 +188,8 @@ router.get(
}
const groupId = parseInt(idParam);
const files = await storage.getPdfFilesByGroupId(groupId);
res.json(files);
const cloudFiles = await listCloudPdfsForGroup(groupId);
res.json([...cloudFiles, ...(files as any[])]);
} catch (err) {
res.status(500).json({ error: "Internal server error" });
}
@@ -203,6 +232,8 @@ router.get(
// Decide whether client asked for paginated response
const wantsPagination = typeof limit === "number";
const cloudFiles = await listCloudPdfsForGroup(groupId);
if (wantsPagination) {
// storage.getPdfFilesByGroupId with pagination should return { total, data }
const result = await storage.getPdfFilesByGroupId(groupId, {
@@ -214,14 +245,17 @@ router.get(
// result should be { total, data }, but handle unexpected shapes defensively
if (Array.isArray(result)) {
// fallback: storage returned full array; compute total
return res.json({ total: result.length, data: result });
const merged = [...cloudFiles, ...result];
return res.json({ total: merged.length, data: merged });
}
return res.json(result);
const merged = [...cloudFiles, ...(result as any).data];
return res.json({ total: (result as any).total + cloudFiles.length, data: merged });
} else {
// no limit requested -> return all files for the group
const all = (await storage.getPdfFilesByGroupId(groupId)) as PdfFile[];
return res.json({ total: all.length, data: all });
const merged = [...cloudFiles, ...(all as any[])];
return res.json({ total: merged.length, data: merged });
}
} catch (err) {
console.error("GET /pdf-files/group/:groupId error:", err);
@@ -238,6 +272,31 @@ router.get(
if (!idParam) {
return res.status(400).json({ error: "Missing ID" });
}
// Cloud-Storage-backed PDF (new saves): id looks like "cloud:123"
const idStr = String(idParam);
if (idStr.startsWith("cloud:")) {
const cloudId = parseInt(idStr.slice("cloud:".length), 10);
if (Number.isNaN(cloudId)) {
return res.status(400).json({ error: "Invalid ID" });
}
const cloudFile = await storage.getFile(cloudId);
if (!cloudFile) return res.status(404).json({ error: "PDF not found" });
res.setHeader("Content-Type", cloudFile.mimeType || "application/pdf");
res.setHeader(
"Content-Disposition",
`attachment; filename="${cloudFile.name}"; filename*=UTF-8''${encodeURIComponent(cloudFile.name)}`
);
try {
await storage.streamFileTo(res, cloudId);
} catch (streamErr) {
console.error("Error streaming cloud PDF file:", streamErr);
if (!res.headersSent) res.status(500).json({ error: "Failed to stream PDF" });
}
return;
}
const id = parseInt(idParam, 10);
if (Number.isNaN(id)) {
return res.status(400).json({ error: "Invalid ID" });
@@ -409,7 +468,18 @@ router.delete(
if (!idParam) {
return res.status(400).json({ error: "Missing ID" });
}
const id = parseInt(idParam);
const idStr = String(idParam);
if (idStr.startsWith("cloud:")) {
const cloudId = parseInt(idStr.slice("cloud:".length), 10);
if (Number.isNaN(cloudId)) {
return res.status(400).json({ error: "Invalid ID" });
}
const success = await storage.deleteFile(cloudId);
return res.json({ success });
}
const id = parseInt(idStr);
const success = await storage.deletePdfFile(id);
res.json({ success });
+6 -24
View File
@@ -521,36 +521,18 @@ router.post(
) {
try {
const pdfBuf = await fs.readFile(seleniumResult.pdf_path);
const groupTitle = "Eligibility Status";
const groupTitleKey = "ELIGIBILITY_STATUS";
const patientName = `${updatedPatient.firstName ?? ""} ${updatedPatient.lastName ?? ""}`.trim();
let group = await storage.findPdfGroupByPatientTitleKey(
const cloudFile = await storage.savePdfToCloudStorage(
req.user.id,
updatedPatient.id,
groupTitleKey
);
if (!group) {
group = await storage.createPdfGroup(
updatedPatient.id,
groupTitle,
groupTitleKey
);
}
if (!group?.id)
throw new Error("Failed to create/find pdf group");
const created = await storage.createPdfFile(
group.id,
patientName || `Patient ${updatedPatient.id}`,
"Eligibility",
path.basename(seleniumResult.pdf_path),
pdfBuf
);
if (created && typeof created === "object" && "id" in created) {
resultItem.pdfFileId = Number(created.id);
} else if (typeof created === "number") {
resultItem.pdfFileId = created;
} else if (created && (created as any).id) {
resultItem.pdfFileId = (created as any).id;
}
resultItem.pdfFileId = `cloud:${cloudFile.id}`;
resultItem.processed = true;
} catch (pdfErr: any) {