feat: cloud storage updates, claims storage, appointment and insurance routes

This commit is contained in:
Gitead
2026-04-27 00:29:11 -04:00
parent 3e899376c3
commit da7038e051
8 changed files with 138 additions and 95 deletions

View File

@@ -9,6 +9,7 @@ import { prisma as db } from "@repo/db/client";
export interface IStorage {
getClaim(id: number): Promise<Claim | undefined>;
getActiveClaimByAppointmentId(appointmentId: number): Promise<ClaimWithServiceLines | null>;
getRecentClaimsByPatientId(
patientId: number,
limit: number,
@@ -54,6 +55,17 @@ export const claimsStorage: IStorage = {
});
},
async getActiveClaimByAppointmentId(appointmentId: number): Promise<ClaimWithServiceLines | null> {
return db.claim.findFirst({
where: {
appointmentId,
status: { notIn: ["CANCELLED", "VOID"] },
},
orderBy: { createdAt: "desc" },
include: { serviceLines: true, claimFiles: true, staff: true },
}) as Promise<ClaimWithServiceLines | null>;
},
async getClaimsByAppointmentId(appointmentId: number): Promise<Claim[]> {
return await db.claim.findMany({ where: { appointmentId } });
},

View File

@@ -1,6 +1,23 @@
import { prisma as db } from "@repo/db/client";
import { CloudFolder, CloudFile } from "@repo/db/types";
import { serializeFile } from "../utils/prismaFileUtils";
import fs from "fs";
import path from "path";
const CLOUD_ROOT = path.join(process.cwd(), "uploads", "cloud-storage");
const CLOUD_TMP = path.join(CLOUD_ROOT, "tmp");
function cloudFolderDir(folderId: number | null): string {
const dir = path.join(CLOUD_ROOT, folderId != null ? `folder-${folderId}` : "root");
fs.mkdirSync(dir, { recursive: true });
return dir;
}
function tmpChunkDir(fileId: number): string {
const dir = path.join(CLOUD_TMP, String(fileId));
fs.mkdirSync(dir, { recursive: true });
return dir;
}
/**
* Cloud storage implementation
@@ -266,42 +283,47 @@ export const cloudStorageStorage: IStorage = {
},
async appendFileChunk(fileId: number, seq: number, data: Buffer) {
try {
await db.cloudFileChunk.create({ data: { fileId, seq, data } });
} catch (err: any) {
// idempotent: ignore duplicate chunk constraint
if (
err?.code === "P2002" ||
err?.message?.includes("Unique constraint failed")
) {
return;
}
throw err;
}
const chunkPath = path.join(tmpChunkDir(fileId), `chunk-${String(seq).padStart(8, "0")}`);
// idempotent: overwrite if already written
fs.writeFileSync(chunkPath, data);
},
async finalizeFileUpload(fileId: number) {
const chunks = await db.cloudFileChunk.findMany({ where: { fileId } });
if (!chunks.length) throw new Error("No chunks uploaded");
// compute total size
let total = 0;
for (const c of chunks) total += c.data.length;
// transactionally update file and read folderId
const updated = await db.$transaction(async (tx) => {
await tx.cloudFile.update({
where: { id: fileId },
data: { fileSize: BigInt(total), isComplete: true },
});
return tx.cloudFile.findUnique({
where: { id: fileId },
select: { folderId: true },
});
const file = await db.cloudFile.findUnique({
where: { id: fileId },
select: { name: true, folderId: true },
});
if (!file) throw new Error("File record not found");
const folderId = (updated as any)?.folderId ?? null;
await updateFolderTimestampsRecursively(folderId);
const tmpDir = path.join(CLOUD_TMP, String(fileId));
const chunkFiles = fs.existsSync(tmpDir)
? fs.readdirSync(tmpDir).filter((f) => f.startsWith("chunk-")).sort()
: [];
if (!chunkFiles.length) throw new Error("No chunks uploaded");
// Assemble chunks into final file
const destDir = cloudFolderDir(file.folderId);
const safeName = file.name.replace(/[/\\?%*:|"<>]/g, "-");
const destPath = path.join(destDir, `${Date.now()}_${safeName}`);
const out = fs.openSync(destPath, "w");
let total = 0;
for (const chunk of chunkFiles) {
const buf = fs.readFileSync(path.join(tmpDir, chunk));
fs.writeSync(out, buf);
total += buf.length;
}
fs.closeSync(out);
// Clean up temp chunks
fs.rmSync(tmpDir, { recursive: true, force: true });
const diskPath = path.relative(process.cwd(), destPath);
await db.cloudFile.update({
where: { id: fileId },
data: { fileSize: BigInt(total), isComplete: true, diskPath },
});
await updateFolderTimestampsRecursively(file.folderId);
return { ok: true, size: BigInt(total).toString() };
},
@@ -310,10 +332,17 @@ export const cloudStorageStorage: IStorage = {
try {
const file = await db.cloudFile.findUnique({
where: { id: fileId },
select: { folderId: true },
select: { folderId: true, diskPath: true },
});
if (!file) return false;
const folderId = file.folderId ?? null;
// Remove from disk
if (file.diskPath) {
const abs = path.join(process.cwd(), file.diskPath);
if (fs.existsSync(abs)) fs.unlinkSync(abs);
}
await db.cloudFile.delete({ where: { id: fileId } });
await updateFolderTimestampsRecursively(folderId);
return true;
@@ -473,20 +502,22 @@ export const cloudStorageStorage: IStorage = {
// --- STREAM ---
async streamFileTo(resStream: NodeJS.WritableStream, fileId: number) {
const batchSize = 100;
let offset = 0;
while (true) {
const chunks = await db.cloudFileChunk.findMany({
where: { fileId },
orderBy: { seq: "asc" },
take: batchSize,
skip: offset,
});
if (!chunks.length) break;
for (const c of chunks) resStream.write(Buffer.from(c.data));
offset += chunks.length;
if (chunks.length < batchSize) break;
}
const file = await db.cloudFile.findUnique({
where: { id: fileId },
select: { diskPath: true },
});
if (!file?.diskPath) throw new Error("File not found on disk");
const abs = path.join(process.cwd(), file.diskPath);
if (!fs.existsSync(abs)) throw new Error("File missing from disk");
await new Promise<void>((resolve, reject) => {
const readable = fs.createReadStream(abs);
readable.on("error", reject);
resStream.on("error", reject);
readable.on("end", resolve);
readable.pipe(resStream, { end: false });
});
},
};