diff --git a/apps/Backend/src/routes/patients.ts b/apps/Backend/src/routes/patients.ts index 528d382..027b19b 100644 --- a/apps/Backend/src/routes/patients.ts +++ b/apps/Backend/src/routes/patients.ts @@ -3,6 +3,7 @@ import type { Request, Response } from "express"; import { storage } from "../storage"; import { z } from "zod"; import { insertPatientSchema, updatePatientSchema } from "@repo/db/types"; +import { normalizeInsuranceId } from "../utils/helpers"; const router = Router(); @@ -160,6 +161,18 @@ router.get( // Create a new patient router.post("/", async (req: Request, res: Response): Promise => { try { + const body: any = { ...req.body, userId: req.user!.id }; + + // Normalize insuranceId early and return clear error if invalid + try { + const normalized = normalizeInsuranceId(body.insuranceId); + body.insuranceId = normalized; + } catch (err: any) { + return res.status(400).json({ + message: "Invalid insuranceId", + details: err?.message ?? "Invalid insuranceId format", + }); + } // Validate request body const patientData = insertPatientSchema.parse({ ...req.body, @@ -169,7 +182,7 @@ router.post("/", async (req: Request, res: Response): Promise => { // Check for duplicate insuranceId if it's provided if (patientData.insuranceId) { const existingPatient = await storage.getPatientByInsuranceId( - patientData.insuranceId + patientData.insuranceId as string ); if (existingPatient) { @@ -201,6 +214,18 @@ router.put( try { const patientIdParam = req.params.id; + // Normalize incoming insuranceId (if present) + try { + if (req.body.insuranceId !== undefined) { + req.body.insuranceId = normalizeInsuranceId(req.body.insuranceId); + } + } catch (err: any) { + return res.status(400).json({ + message: "Invalid insuranceId", + details: err?.message ?? "Invalid insuranceId format", + }); + } + // Ensure that patientIdParam exists and is a valid number if (!patientIdParam) { return res.status(400).json({ message: "Patient ID is required" }); @@ -223,7 +248,7 @@ router.put( patientData.insuranceId !== existingPatient.insuranceId ) { const duplicatePatient = await storage.getPatientByInsuranceId( - patientData.insuranceId + patientData.insuranceId as string ); if (duplicatePatient && duplicatePatient.id !== patientId) { return res.status(409).json({ diff --git a/apps/Backend/src/storage/patients-storage.ts b/apps/Backend/src/storage/patients-storage.ts index 47a3279..685a586 100644 --- a/apps/Backend/src/storage/patients-storage.ts +++ b/apps/Backend/src/storage/patients-storage.ts @@ -87,7 +87,7 @@ export const patientsStorage: IStorage = { try { return await db.patient.update({ where: { id }, - data: updateData, + data: updateData as Patient, }); } catch (err) { throw new Error(`Patient with ID ${id} not found`); diff --git a/apps/Backend/src/utils/helpers.ts b/apps/Backend/src/utils/helpers.ts new file mode 100644 index 0000000..5fa450f --- /dev/null +++ b/apps/Backend/src/utils/helpers.ts @@ -0,0 +1,27 @@ +export function normalizeInsuranceId(raw: unknown): string | undefined { + if (raw === undefined || raw === null) return undefined; + + // Accept numbers too (e.g. 12345), but prefer strings + let s: string; + if (typeof raw === "number") { + s = String(raw); + } else if (typeof raw === "string") { + s = raw; + } else { + // Not acceptable type + throw new Error("Insurance ID must be a numeric string."); + } + + // Remove all whitespace + const cleaned = s.replace(/\s+/g, ""); + + // If empty after cleaning, treat as undefined + if (cleaned === "") return undefined; + + // Only digits allowed (since you said it's numeric) + if (!/^\d+$/.test(cleaned)) { + throw new Error("Insurance ID must contain only digits."); + } + + return cleaned; +} diff --git a/packages/db/types/patient-types.ts b/packages/db/types/patient-types.ts index 9612bc4..5f764a2 100644 --- a/packages/db/types/patient-types.ts +++ b/packages/db/types/patient-types.ts @@ -1,14 +1,44 @@ import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas"; -import {z} from "zod"; +import { z } from "zod"; export type Patient = z.infer; +export const insuranceIdSchema = z.preprocess( + (val) => { + if (val === undefined || val === null) return undefined; + + // Accept numbers and strings + if (typeof val === "number") { + return String(val).replace(/\s+/g, ""); + } + if (typeof val === "string") { + const cleaned = val.replace(/\s+/g, ""); + if (cleaned === "") return undefined; + return cleaned; + } + return val; + }, + // After preprocess, require digits-only string (or optional nullable) + z + .string() + .regex(/^\d+$/, { message: "Insurance ID must contain only digits" }) + .min(1) + .max(32) + .optional() + .nullable() +); + export const insertPatientSchema = ( PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject -).omit({ - id: true, - createdAt: true, -}); +) + .omit({ + id: true, + createdAt: true, + }) + .extend({ + insuranceId: insuranceIdSchema, // enforce numeric insuranceId + }); + export type InsertPatient = z.infer; export const updatePatientSchema = ( @@ -19,6 +49,9 @@ export const updatePatientSchema = ( createdAt: true, userId: true, }) - .partial(); + .partial() + .extend({ + insuranceId: insuranceIdSchema, // enforce numeric insuranceId + }); -export type UpdatePatient = z.infer; \ No newline at end of file +export type UpdatePatient = z.infer;