diff --git a/apps/Backend/src/storage/appointements-storage.ts b/apps/Backend/src/storage/appointements-storage.ts new file mode 100644 index 0000000..23f1747 --- /dev/null +++ b/apps/Backend/src/storage/appointements-storage.ts @@ -0,0 +1,198 @@ +import { + Appointment, + InsertAppointment, + Patient, + UpdateAppointment, +} from "@repo/db/types"; +import { prisma as db } from "@repo/db/client"; + +export interface IStorage { + getAppointment(id: number): Promise; + getAllAppointments(): Promise; + getAppointmentsByUserId(userId: number): Promise; + getAppointmentsByPatientId(patientId: number): Promise; + getPatientFromAppointmentId( + appointmentId: number + ): Promise; + getRecentAppointments(limit: number, offset: number): Promise; + getAppointmentsOnRange(start: Date, end: Date): Promise; + createAppointment(appointment: InsertAppointment): Promise; + updateAppointment( + id: number, + appointment: UpdateAppointment + ): Promise; + deleteAppointment(id: number): Promise; + getPatientAppointmentByDateTime( + patientId: number, + date: Date, + startTime: string + ): Promise; + getStaffAppointmentByDateTime( + staffId: number, + date: Date, + startTime: string, + excludeId?: number + ): Promise; + getPatientConflictAppointment( + patientId: number, + date: Date, + startTime: string, + excludeId: number + ): Promise; + getStaffConflictAppointment( + staffId: number, + date: Date, + startTime: string, + excludeId: number + ): Promise; +} + +export const appointmentsStorage: IStorage = { + async getAppointment(id: number): Promise { + const appointment = await db.appointment.findUnique({ where: { id } }); + return appointment ?? undefined; + }, + + async getAllAppointments(): Promise { + return await db.appointment.findMany(); + }, + + async getAppointmentsByUserId(userId: number): Promise { + return await db.appointment.findMany({ where: { userId } }); + }, + + async getAppointmentsByPatientId(patientId: number): Promise { + return await db.appointment.findMany({ where: { patientId } }); + }, + + async getPatientFromAppointmentId( + appointmentId: number + ): Promise { + const appointment = await db.appointment.findUnique({ + where: { id: appointmentId }, + include: { patient: true }, + }); + return appointment?.patient ?? undefined; + }, + + async getAppointmentsOnRange(start: Date, end: Date): Promise { + return db.appointment.findMany({ + where: { + date: { + gte: start, + lte: end, + }, + }, + orderBy: { date: "asc" }, + }); + }, + + async getRecentAppointments( + limit: number, + offset: number + ): Promise { + return db.appointment.findMany({ + skip: offset, + take: limit, + orderBy: { date: "desc" }, + }); + }, + + async createAppointment( + appointment: InsertAppointment + ): Promise { + return await db.appointment.create({ data: appointment as Appointment }); + }, + + async updateAppointment( + id: number, + updateData: UpdateAppointment + ): Promise { + try { + return await db.appointment.update({ + where: { id }, + data: updateData, + }); + } catch (err) { + throw new Error(`Appointment with ID ${id} not found`); + } + }, + + async deleteAppointment(id: number): Promise { + try { + await db.appointment.delete({ where: { id } }); + } catch (err) { + throw new Error(`Appointment with ID ${id} not found`); + } + }, + + async getPatientAppointmentByDateTime( + patientId: number, + date: Date, + startTime: string + ): Promise { + return ( + (await db.appointment.findFirst({ + where: { + patientId, + date, + startTime, + }, + })) ?? undefined + ); + }, + + async getStaffAppointmentByDateTime( + staffId: number, + date: Date, + startTime: string, + excludeId?: number + ): Promise { + return ( + (await db.appointment.findFirst({ + where: { + staffId, + date, + startTime, + NOT: excludeId ? { id: excludeId } : undefined, + }, + })) ?? undefined + ); + }, + + async getPatientConflictAppointment( + patientId: number, + date: Date, + startTime: string, + excludeId: number + ): Promise { + return ( + (await db.appointment.findFirst({ + where: { + patientId, + date, + startTime, + NOT: { id: excludeId }, + }, + })) ?? undefined + ); + }, + + async getStaffConflictAppointment( + staffId: number, + date: Date, + startTime: string, + excludeId: number + ): Promise { + return ( + (await db.appointment.findFirst({ + where: { + staffId, + date, + startTime, + NOT: { id: excludeId }, + }, + })) ?? undefined + ); + }, +}; diff --git a/apps/Backend/src/storage/claims-storage.ts b/apps/Backend/src/storage/claims-storage.ts new file mode 100644 index 0000000..f5a9229 --- /dev/null +++ b/apps/Backend/src/storage/claims-storage.ts @@ -0,0 +1,98 @@ +import { + Claim, + ClaimWithServiceLines, + InsertClaim, + UpdateClaim, +} from "@repo/db/types"; +import { prisma as db } from "@repo/db/client"; + +export interface IStorage { + getClaim(id: number): Promise; + getRecentClaimsByPatientId( + patientId: number, + limit: number, + offset: number + ): Promise; + + getTotalClaimCountByPatient(patientId: number): Promise; + getClaimsByAppointmentId(appointmentId: number): Promise; + getRecentClaims(limit: number, offset: number): Promise; + getTotalClaimCount(): Promise; + createClaim(claim: InsertClaim): Promise; + updateClaim(id: number, updates: UpdateClaim): Promise; + deleteClaim(id: number): Promise; +} + +export const claimsStorage: IStorage = { + async getClaim(id: number): Promise { + const claim = await db.claim.findUnique({ where: { id } }); + return claim ?? undefined; + }, + + async getRecentClaimsByPatientId( + patientId: number, + limit: number, + offset: number + ): Promise { + return db.claim.findMany({ + where: { patientId }, + orderBy: { createdAt: "desc" }, + skip: offset, + take: limit, + include: { + serviceLines: true, + staff: true, + claimFiles: true, + }, + }); + }, + + async getTotalClaimCountByPatient(patientId: number): Promise { + return db.claim.count({ + where: { patientId }, + }); + }, + + async getClaimsByAppointmentId(appointmentId: number): Promise { + return await db.claim.findMany({ where: { appointmentId } }); + }, + + async getRecentClaims( + limit: number, + offset: number + ): Promise { + return db.claim.findMany({ + orderBy: { createdAt: "desc" }, + skip: offset, + take: limit, + include: { serviceLines: true, staff: true, claimFiles: true }, + }); + }, + + async getTotalClaimCount(): Promise { + return db.claim.count(); + }, + + async createClaim(claim: InsertClaim): Promise { + return await db.claim.create({ data: claim as Claim }); + }, + + async updateClaim(id: number, updates: UpdateClaim): Promise { + try { + return await db.claim.update({ + where: { id }, + data: updates, + }); + } catch (err) { + throw new Error(`Claim with ID ${id} not found`); + } + }, + + async deleteClaim(id: number): Promise { + try { + await db.claim.delete({ where: { id } }); + } catch (err) { + throw new Error(`Claim with ID ${id} not found`); + } + }, +}; diff --git a/apps/Backend/src/storage/cloudStorage-storage.ts b/apps/Backend/src/storage/cloudStorage-storage.ts new file mode 100644 index 0000000..a5ef907 --- /dev/null +++ b/apps/Backend/src/storage/cloudStorage-storage.ts @@ -0,0 +1,349 @@ +import { prisma as db } from "@repo/db/client"; +import { CloudFile, CloudFolder } from "@repo/db/types"; +import { serializeFile } from "../utils/prismaFileUtils"; + +export interface IStorage { + // CloudFolder methods + getFolder(id: number): Promise; + getFoldersByUser( + userId: number, + parentId: number | null, + limit: number, + offset: number + ): Promise; + getRecentFolders(limit: number, offset: number): Promise; + createFolder( + userId: number, + name: string, + parentId?: number | null + ): Promise; + updateFolder( + id: number, + updates: Partial<{ name?: string; parentId?: number | null }> + ): Promise; + deleteFolder(id: number): Promise; + + // CloudFile methods + getFile(id: number): Promise; + listFilesByFolderByUser( + userId: number, + folderId: number | null, + limit: number, + offset: number + ): Promise; + listFilesByFolder( + folderId: number | null, + limit: number, + offset: number + ): Promise; + + // chunked upload methods + createFileInit( + userId: number, + name: string, + mimeType?: string | null, + expectedSize?: bigint | null, + totalChunks?: number | null, + folderId?: number | null + ): Promise; + addChunk(fileId: number, seq: number, data: Buffer): Promise; + completeFile(fileId: number): Promise<{ ok: true; size: string }>; + deleteFile(fileId: number): Promise; + + // search + searchByName( + userId: number, + q: string, + limit: number, + offset: number + ): Promise<{ + folders: CloudFolder[]; + files: CloudFile[]; + foldersTotal: number; + filesTotal: number; + }>; + + // helper: stream file chunks via Node.js stream + streamFileTo(resStream: NodeJS.WritableStream, fileId: number): Promise; +} + +export const cloudStorageStorage: IStorage = { + // --- Folders --- + async getFolder(id: number) { + const folder = await db.cloudFolder.findUnique({ + where: { id }, + include: { files: false }, + }); + return folder ?? null; + }, + + async getFoldersByUser( + userId: number, + parentId: number | null = null, + limit = 50, + offset = 0 + ) { + const folders = await db.cloudFolder.findMany({ + where: { userId, parentId }, + orderBy: { name: "asc" }, + skip: offset, + take: limit, + }); + return folders; + }, + + async getRecentFolders(limit = 50, offset = 0) { + const folders = await db.cloudFolder.findMany({ + orderBy: { name: "asc" }, + skip: offset, + take: limit, + }); + return folders; + }, + + async createFolder( + userId: number, + name: string, + parentId: number | null = null + ) { + const created = await db.cloudFolder.create({ + data: { userId, name, parentId }, + }); + return created; + }, + + async updateFolder( + id: number, + updates: Partial<{ name?: string; parentId?: number | null }> + ) { + try { + const updated = await db.cloudFolder.update({ + where: { id }, + data: updates, + }); + return updated; + } catch (err) { + return null; + } + }, + + async deleteFolder(id: number) { + try { + await db.cloudFolder.delete({ where: { id } }); + return true; + } catch (err) { + console.error("deleteFolder error", err); + return false; + } + }, + + // --- Files --- + async getFile(id: number): Promise { + const file = await db.cloudFile.findUnique({ + where: { id }, + include: { chunks: { orderBy: { seq: "asc" } } }, + }); + return (file as unknown as CloudFile) ?? null; + }, + + async listFilesByFolderByUser( + userId: number, + folderId: number | null = null, + limit = 50, + offset = 0 + ) { + const files = await db.cloudFile.findMany({ + where: { userId, folderId }, + orderBy: { createdAt: "desc" }, + skip: offset, + take: limit, + select: { + id: true, + name: true, + mimeType: true, + fileSize: true, + folderId: true, + isComplete: true, + createdAt: true, + updatedAt: true, + }, + }); + return files.map(serializeFile); + }, + + async listFilesByFolder( + folderId: number | null = null, + limit = 50, + offset = 0 + ) { + const files = await db.cloudFile.findMany({ + where: { folderId }, + orderBy: { createdAt: "desc" }, + skip: offset, + take: limit, + select: { + id: true, + name: true, + mimeType: true, + fileSize: true, + folderId: true, + isComplete: true, + createdAt: true, + updatedAt: true, + }, + }); + return files.map(serializeFile); + }, + + // --- Chunked upload methods --- + async createFileInit( + userId, + name, + mimeType = null, + expectedSize = null, + totalChunks = null, + folderId = null + ) { + const created = await db.cloudFile.create({ + data: { + userId, + name, + mimeType, + fileSize: expectedSize ?? BigInt(0), + folderId, + totalChunks, + isComplete: false, + }, + }); + return serializeFile(created); + }, + + async addChunk(fileId: number, seq: number, data: Buffer) { + // Ensure file exists & belongs to owner will be done by caller (route) + // Attempt insert; if unique violation => ignore (idempotent) + try { + await db.cloudFileChunk.create({ + data: { + fileId, + seq, + data, + }, + }); + } catch (err: any) { + // If unique constraint violation (duplicate chunk), ignore + if ( + err?.code === "P2002" || + err?.message?.includes("Unique constraint failed") + ) { + // duplicate chunk, ignore + return; + } + throw err; + } + }, + + async completeFile(fileId: number) { + // Compute total size from chunks and mark complete inside a transaction + const chunks = await db.cloudFileChunk.findMany({ where: { fileId } }); + if (!chunks.length) { + throw new Error("No chunks uploaded"); + } + let total = 0; + for (const c of chunks) total += c.data.length; + + // Update file + await db.cloudFile.update({ + where: { id: fileId }, + data: { + fileSize: BigInt(total), + isComplete: true, + }, + }); + return { ok: true, size: BigInt(total).toString() }; + }, + + async deleteFile(fileId: number) { + try { + await db.cloudFile.delete({ where: { id: fileId } }); + // chunks cascade-delete via Prisma relation onDelete: Cascade + return true; + } catch (err) { + console.error("deleteFile error", err); + return false; + } + }, + + // --- Search --- + async searchByName(userId: number, q: string, limit = 20, offset = 0) { + const [folders, files, foldersTotal, filesTotal] = await Promise.all([ + db.cloudFolder.findMany({ + where: { + userId, + name: { contains: q, mode: "insensitive" }, + }, + orderBy: { name: "asc" }, + skip: offset, + take: limit, + }), + db.cloudFile.findMany({ + where: { + userId, + name: { contains: q, mode: "insensitive" }, + }, + orderBy: { createdAt: "desc" }, + skip: offset, + take: limit, + select: { + id: true, + name: true, + mimeType: true, + fileSize: true, + folderId: true, + isComplete: true, + createdAt: true, + updatedAt: true, + }, + }), + db.cloudFolder.count({ + where: { + userId, + name: { contains: q, mode: "insensitive" }, + }, + }), + db.cloudFile.count({ + where: { + userId, + name: { contains: q, mode: "insensitive" }, + }, + }), + ]); + return { + folders, + files: files.map(serializeFile), + foldersTotal, + filesTotal, + }; + }, + + // --- Streaming helper --- + async streamFileTo(resStream: NodeJS.WritableStream, fileId: number) { + // Stream chunks in batches to avoid loading everything at once. + 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; + } + // caller will end the response stream + }, +}; diff --git a/apps/Backend/src/storage/database-backup-storage.ts b/apps/Backend/src/storage/database-backup-storage.ts new file mode 100644 index 0000000..0a878c2 --- /dev/null +++ b/apps/Backend/src/storage/database-backup-storage.ts @@ -0,0 +1,39 @@ +import { DatabaseBackup } from "@repo/db/types"; +import { prisma as db } from "@repo/db/client"; + +export interface IStorage { + // Database Backup methods + createBackup(userId: number): Promise; + getLastBackup(userId: number): Promise; + getBackups(userId: number, limit?: number): Promise; + deleteBackups(userId: number): Promise; // clears all for user +} + +export const databaseBackupStorage: IStorage = { + // ============================== + // Database Backup methods + // ============================== + async createBackup(userId) { + return await db.databaseBackup.create({ data: { userId } }); + }, + + async getLastBackup(userId) { + return await db.databaseBackup.findFirst({ + where: { userId }, + orderBy: { createdAt: "desc" }, + }); + }, + + async getBackups(userId, limit = 10) { + return await db.databaseBackup.findMany({ + where: { userId }, + orderBy: { createdAt: "desc" }, + take: limit, + }); + }, + + async deleteBackups(userId) { + const result = await db.databaseBackup.deleteMany({ where: { userId } }); + return result.count; + }, +}; diff --git a/apps/Backend/src/storage/general-pdf-storage.ts b/apps/Backend/src/storage/general-pdf-storage.ts new file mode 100644 index 0000000..1a9a304 --- /dev/null +++ b/apps/Backend/src/storage/general-pdf-storage.ts @@ -0,0 +1,218 @@ +import { PdfFile, PdfGroup } from "@repo/db/types"; +import { prisma as db } from "@repo/db/client"; +import { PdfTitleKey } from "@repo/db/generated/prisma"; + +export interface IStorage { + // General PDF Methods + createPdfFile( + groupId: number, + filename: string, + pdfData: Buffer + ): Promise; + getPdfFileById(id: number): Promise; + getPdfFilesByGroupId( + groupId: number, + opts?: { limit?: number; offset?: number; withGroup?: boolean } + ): Promise; + getRecentPdfFiles(limit: number, offset: number): Promise; + deletePdfFile(id: number): Promise; + updatePdfFile( + id: number, + updates: Partial> + ): Promise; + + // PDF Group management + createPdfGroup( + patientId: number, + title: string, + titleKey: PdfTitleKey + ): Promise; + findPdfGroupByPatientTitleKey( + patientId: number, + titleKey: PdfTitleKey + ): Promise; + getAllPdfGroups(): Promise; + getPdfGroupById(id: number): Promise; + getPdfGroupsByPatientId(patientId: number): Promise; + updatePdfGroup( + id: number, + updates: Partial> + ): Promise; + deletePdfGroup(id: number): Promise; +} + +export const generalPdfStorage: IStorage = { + // PDF Files + async createPdfFile(groupId, filename, pdfData) { + return db.pdfFile.create({ + data: { + groupId, + filename, + pdfData, + }, + }); + }, + + async getAllPdfGroups(): Promise { + return db.pdfGroup.findMany({ + orderBy: { + createdAt: "desc", + }, + }); + }, + + async getPdfFileById(id) { + return (await db.pdfFile.findUnique({ where: { id } })) ?? undefined; + }, + + /** + * getPdfFilesByGroupId: supports + * - getPdfFilesByGroupId(groupId) => Promise + * - getPdfFilesByGroupId(groupId, { limit, offset }) => Promise<{ total, data }> + * - getPdfFilesByGroupId(groupId, { limit, offset, withGroup: true }) => Promise<{ total, data: PdfFileWithGroup[] }> + */ + async getPdfFilesByGroupId(groupId, opts) { + // if pagination is requested (limit provided) return total + page + const wantsPagination = + !!opts && + (typeof opts.limit === "number" || typeof opts.offset === "number"); + + if (wantsPagination) { + const limit = Math.min(Number(opts?.limit ?? 5), 1000); + const offset = Number(opts?.offset ?? 0); + + if (opts?.withGroup) { + // return total + data with group included + const [total, data] = await Promise.all([ + db.pdfFile.count({ where: { groupId } }), + db.pdfFile.findMany({ + where: { groupId }, + orderBy: { uploadedAt: "desc" }, + take: limit, + skip: offset, + include: { group: true }, // only include + }), + ]); + + return { total, data }; + } else { + // return total + data with limited fields via select + const [total, data] = await Promise.all([ + db.pdfFile.count({ where: { groupId } }), + db.pdfFile.findMany({ + where: { groupId }, + orderBy: { uploadedAt: "desc" }, + take: limit, + skip: offset, + select: { id: true, filename: true, uploadedAt: true }, // only select + }), + ]); + + // Note: selected shape won't have all PdfFile fields; cast if needed + return { total, data: data as unknown as PdfFile[] }; + } + } + + // non-paginated: return all files (keep descending order) + if (opts?.withGroup) { + const all = await db.pdfFile.findMany({ + where: { groupId }, + orderBy: { uploadedAt: "desc" }, + include: { group: true }, + }); + return all as PdfFile[]; + } else { + const all = await db.pdfFile.findMany({ + where: { groupId }, + orderBy: { uploadedAt: "desc" }, + // no select or include -> returns full PdfFile + }); + return all as PdfFile[]; + } + }, + + async getRecentPdfFiles(limit: number, offset: number): Promise { + return db.pdfFile.findMany({ + skip: offset, + take: limit, + orderBy: { uploadedAt: "desc" }, + include: { group: true }, + }); + }, + + async updatePdfFile(id, updates) { + try { + return await db.pdfFile.update({ + where: { id }, + data: updates, + }); + } catch { + return undefined; + } + }, + + async deletePdfFile(id) { + try { + await db.pdfFile.delete({ where: { id } }); + return true; + } catch { + return false; + } + }, + + // ---------------------- + // PdfGroup CRUD + // ---------------------- + + async createPdfGroup(patientId, title, titleKey) { + return db.pdfGroup.create({ + data: { + patientId, + title, + titleKey, + }, + }); + }, + + async findPdfGroupByPatientTitleKey(patientId, titleKey) { + return ( + (await db.pdfGroup.findFirst({ + where: { + patientId, + titleKey, + }, + })) ?? undefined + ); + }, + + async getPdfGroupById(id) { + return (await db.pdfGroup.findUnique({ where: { id } })) ?? undefined; + }, + + async getPdfGroupsByPatientId(patientId) { + return db.pdfGroup.findMany({ + where: { patientId }, + orderBy: { createdAt: "desc" }, + }); + }, + + async updatePdfGroup(id, updates) { + try { + return await db.pdfGroup.update({ + where: { id }, + data: updates, + }); + } catch { + return undefined; + } + }, + + async deletePdfGroup(id) { + try { + await db.pdfGroup.delete({ where: { id } }); + return true; + } catch { + return false; + } + }, +}; diff --git a/apps/Backend/src/storage/index.ts b/apps/Backend/src/storage/index.ts index 4150b61..fbf03e3 100644 --- a/apps/Backend/src/storage/index.ts +++ b/apps/Backend/src/storage/index.ts @@ -1,1153 +1,32 @@ -import { prisma as db } from "@repo/db/client"; -import { PdfTitleKey } from "@repo/db/generated/prisma"; -import { - Appointment, - Claim, - ClaimWithServiceLines, - DatabaseBackup, - InsertAppointment, - InsertClaim, - InsertInsuranceCredential, - InsertPatient, - InsertPayment, - InsertUser, - InsuranceCredential, - Notification, - NotificationTypes, - Patient, - Payment, - PaymentWithExtras, - PdfFile, - PdfGroup, - Staff, - UpdateAppointment, - UpdateClaim, - UpdatePatient, - UpdatePayment, - User, -} from "@repo/db/types"; -export interface IStorage { - // User methods - getUser(id: number): Promise; - getUsers(limit: number, offset: number): Promise; - getUserByUsername(username: string): Promise; - createUser(user: InsertUser): Promise; - updateUser(id: number, updates: Partial): Promise; - deleteUser(id: number): Promise; - // Patient methods - getPatient(id: number): Promise; - getPatientByInsuranceId(insuranceId: string): Promise; - getPatientsByUserId(userId: number): Promise; - getRecentPatients(limit: number, offset: number): Promise; - getPatientsByIds(ids: number[]): Promise; - createPatient(patient: InsertPatient): Promise; - updatePatient(id: number, patient: UpdatePatient): Promise; - deletePatient(id: number): Promise; - searchPatients(args: { - filters: any; - limit: number; - offset: number; - }): Promise< - { - id: number; - firstName: string | null; - lastName: string | null; - phone: string | null; - gender: string | null; - dateOfBirth: Date; - insuranceId: string | null; - insuranceProvider: string | null; - status: string; - }[] - >; - getTotalPatientCount(): Promise; - countPatients(filters: any): Promise; // optional but useful +import { usersStorage } from './users-storage'; +import { patientsStorage } from './patients-storage'; +import { appointmentsStorage } from './appointements-storage'; +import { staffStorage } from './staff-storage'; +import { claimsStorage } from './claims-storage'; +import { insuranceCredsStorage } from './insurance-creds-storage'; +import { generalPdfStorage } from './general-pdf-storage'; +import { paymentsStorage } from './payments-storage'; +import { databaseBackupStorage } from './database-backup-storage'; +import { notificationsStorage } from './notifications-storage'; +import { cloudStorageStorage } from './cloudStorage-storage'; + + + +export const storage = { + ...usersStorage, + ...patientsStorage, + ...appointmentsStorage, + ...staffStorage, + ...claimsStorage, + ...insuranceCredsStorage, + ...generalPdfStorage, + ...paymentsStorage, + ...databaseBackupStorage, + ...notificationsStorage, + ...cloudStorageStorage, - // Appointment methods - getAppointment(id: number): Promise; - getAllAppointments(): Promise; - getAppointmentsByUserId(userId: number): Promise; - getAppointmentsByPatientId(patientId: number): Promise; - getPatientFromAppointmentId( - appointmentId: number - ): Promise; - getRecentAppointments(limit: number, offset: number): Promise; - getAppointmentsOnRange(start: Date, end: Date): Promise; - createAppointment(appointment: InsertAppointment): Promise; - updateAppointment( - id: number, - appointment: UpdateAppointment - ): Promise; - deleteAppointment(id: number): Promise; - getPatientAppointmentByDateTime( - patientId: number, - date: Date, - startTime: string - ): Promise; - getStaffAppointmentByDateTime( - staffId: number, - date: Date, - startTime: string, - excludeId?: number - ): Promise; - getPatientConflictAppointment( - patientId: number, - date: Date, - startTime: string, - excludeId: number - ): Promise; - getStaffConflictAppointment( - staffId: number, - date: Date, - startTime: string, - excludeId: number - ): Promise; - - // Staff methods - getStaff(id: number): Promise; - getAllStaff(): Promise; - createStaff(staff: Staff): Promise; - updateStaff(id: number, updates: Partial): Promise; - deleteStaff(id: number): Promise; - countAppointmentsByStaffId(staffId: number): Promise; - countClaimsByStaffId(staffId: number): Promise; - - // Claim methods - getClaim(id: number): Promise; - getRecentClaimsByPatientId( - patientId: number, - limit: number, - offset: number - ): Promise; - - getTotalClaimCountByPatient(patientId: number): Promise; - getClaimsByAppointmentId(appointmentId: number): Promise; - getRecentClaims(limit: number, offset: number): Promise; - getTotalClaimCount(): Promise; - createClaim(claim: InsertClaim): Promise; - updateClaim(id: number, updates: UpdateClaim): Promise; - deleteClaim(id: number): Promise; - - // InsuranceCredential methods - getInsuranceCredential(id: number): Promise; - getInsuranceCredentialsByUser(userId: number): Promise; - createInsuranceCredential( - data: InsertInsuranceCredential - ): Promise; - updateInsuranceCredential( - id: number, - updates: Partial - ): Promise; - deleteInsuranceCredential(userId: number, id: number): Promise; - getInsuranceCredentialByUserAndSiteKey( - userId: number, - siteKey: string - ): Promise; - - // General PDF Methods - createPdfFile( - groupId: number, - filename: string, - pdfData: Buffer - ): Promise; - getPdfFileById(id: number): Promise; - getPdfFilesByGroupId( - groupId: number, - opts?: { limit?: number; offset?: number; withGroup?: boolean } - ): Promise; - getRecentPdfFiles(limit: number, offset: number): Promise; - deletePdfFile(id: number): Promise; - updatePdfFile( - id: number, - updates: Partial> - ): Promise; - - // PDF Group management - createPdfGroup( - patientId: number, - title: string, - titleKey: PdfTitleKey - ): Promise; - findPdfGroupByPatientTitleKey( - patientId: number, - titleKey: PdfTitleKey - ): Promise; - getAllPdfGroups(): Promise; - getPdfGroupById(id: number): Promise; - getPdfGroupsByPatientId(patientId: number): Promise; - updatePdfGroup( - id: number, - updates: Partial> - ): Promise; - deletePdfGroup(id: number): Promise; - - // Payment methods: - getPayment(id: number): Promise; - createPayment(data: InsertPayment): Promise; - updatePayment(id: number, updates: UpdatePayment): Promise; - deletePayment(id: number, userId: number): Promise; - getPaymentById(id: number): Promise; - getRecentPaymentsByPatientId( - patientId: number, - limit: number, - offset: number - ): Promise; - getTotalPaymentCountByPatient(patientId: number): Promise; - getPaymentsByClaimId(claimId: number): Promise; - getRecentPayments( - limit: number, - offset: number - ): Promise; - getPaymentsByDateRange(from: Date, to: Date): Promise; - getTotalPaymentCount(): Promise; - - // Database Backup methods - createBackup(userId: number): Promise; - getLastBackup(userId: number): Promise; - getBackups(userId: number, limit?: number): Promise; - deleteBackups(userId: number): Promise; // clears all for user - - // Notification methods - createNotification( - userId: number, - type: NotificationTypes, - message: string - ): Promise; - getNotifications( - userId: number, - limit?: number, - offset?: number - ): Promise; - markNotificationRead( - userId: number, - notificationId: number - ): Promise; - markAllNotificationsRead(userId: number): Promise; - deleteNotificationsByType( - userId: number, - type: NotificationTypes - ): Promise; - deleteAllNotifications(userId: number): Promise; -} - -export const storage: IStorage = { - // User methods - async getUser(id: number): Promise { - const user = await db.user.findUnique({ where: { id } }); - return user ?? undefined; - }, - - async getUsers(limit: number, offset: number): Promise { - return await db.user.findMany({ skip: offset, take: limit }); - }, - - async getUserByUsername(username: string): Promise { - const user = await db.user.findUnique({ where: { username } }); - return user ?? undefined; - }, - - async createUser(user: InsertUser): Promise { - return await db.user.create({ data: user as User }); - }, - - async updateUser( - id: number, - updates: Partial - ): Promise { - try { - return await db.user.update({ where: { id }, data: updates }); - } catch { - return undefined; - } - }, - - async deleteUser(id: number): Promise { - try { - await db.user.delete({ where: { id } }); - return true; - } catch { - return false; - } - }, - - // Patient methods - async getPatient(id: number): Promise { - const patient = await db.patient.findUnique({ where: { id } }); - return patient ?? undefined; - }, - - async getPatientsByUserId(userId: number): Promise { - return await db.patient.findMany({ where: { userId } }); - }, - - async getPatientByInsuranceId(insuranceId: string): Promise { - return db.patient.findFirst({ - where: { insuranceId }, - }); - }, - - async getRecentPatients(limit: number, offset: number): Promise { - return db.patient.findMany({ - skip: offset, - take: limit, - orderBy: { createdAt: "desc" }, - }); - }, - - async getPatientsByIds(ids: number[]): Promise { - if (!ids || ids.length === 0) return []; - const uniqueIds = Array.from(new Set(ids)); - return db.patient.findMany({ - where: { id: { in: uniqueIds } }, - select: { - id: true, - firstName: true, - lastName: true, - phone: true, - email: true, - dateOfBirth: true, - gender: true, - insuranceId: true, - insuranceProvider: true, - status: true, - userId: true, - createdAt: true, - }, - }); - }, - - async createPatient(patient: InsertPatient): Promise { - return await db.patient.create({ data: patient as Patient }); - }, - - async updatePatient(id: number, updateData: UpdatePatient): Promise { - try { - return await db.patient.update({ - where: { id }, - data: updateData, - }); - } catch (err) { - throw new Error(`Patient with ID ${id} not found`); - } - }, - - async deletePatient(id: number): Promise { - try { - await db.patient.delete({ where: { id } }); - } catch (err) { - console.error("Error deleting patient:", err); - throw new Error(`Failed to delete patient: ${err}`); - } - }, - - async searchPatients({ - filters, - limit, - offset, - }: { - filters: any; - limit: number; - offset: number; - }) { - return db.patient.findMany({ - where: filters, - orderBy: { createdAt: "desc" }, - take: limit, - skip: offset, - select: { - id: true, - firstName: true, - lastName: true, - phone: true, - gender: true, - dateOfBirth: true, - insuranceId: true, - insuranceProvider: true, - status: true, - }, - }); - }, - - async getTotalPatientCount(): Promise { - return db.patient.count(); - }, - - async countPatients(filters: any) { - return db.patient.count({ where: filters }); - }, - - // Appointment methods - async getAppointment(id: number): Promise { - const appointment = await db.appointment.findUnique({ where: { id } }); - return appointment ?? undefined; - }, - - async getAllAppointments(): Promise { - return await db.appointment.findMany(); - }, - - async getAppointmentsByUserId(userId: number): Promise { - return await db.appointment.findMany({ where: { userId } }); - }, - - async getAppointmentsByPatientId(patientId: number): Promise { - return await db.appointment.findMany({ where: { patientId } }); - }, - - async getPatientFromAppointmentId( - appointmentId: number - ): Promise { - const appointment = await db.appointment.findUnique({ - where: { id: appointmentId }, - include: { patient: true }, - }); - return appointment?.patient ?? undefined; - }, - - async getAppointmentsOnRange(start: Date, end: Date): Promise { - return db.appointment.findMany({ - where: { - date: { - gte: start, - lte: end, - }, - }, - orderBy: { date: "asc" }, - }); - }, - - async getRecentAppointments( - limit: number, - offset: number - ): Promise { - return db.appointment.findMany({ - skip: offset, - take: limit, - orderBy: { date: "desc" }, - }); - }, - - async createAppointment( - appointment: InsertAppointment - ): Promise { - return await db.appointment.create({ data: appointment as Appointment }); - }, - - async updateAppointment( - id: number, - updateData: UpdateAppointment - ): Promise { - try { - return await db.appointment.update({ - where: { id }, - data: updateData, - }); - } catch (err) { - throw new Error(`Appointment with ID ${id} not found`); - } - }, - - async deleteAppointment(id: number): Promise { - try { - await db.appointment.delete({ where: { id } }); - } catch (err) { - throw new Error(`Appointment with ID ${id} not found`); - } - }, - - async getPatientAppointmentByDateTime( - patientId: number, - date: Date, - startTime: string - ): Promise { - return ( - (await db.appointment.findFirst({ - where: { - patientId, - date, - startTime, - }, - })) ?? undefined - ); - }, - - async getStaffAppointmentByDateTime( - staffId: number, - date: Date, - startTime: string, - excludeId?: number - ): Promise { - return ( - (await db.appointment.findFirst({ - where: { - staffId, - date, - startTime, - NOT: excludeId ? { id: excludeId } : undefined, - }, - })) ?? undefined - ); - }, - - async getPatientConflictAppointment( - patientId: number, - date: Date, - startTime: string, - excludeId: number - ): Promise { - return ( - (await db.appointment.findFirst({ - where: { - patientId, - date, - startTime, - NOT: { id: excludeId }, - }, - })) ?? undefined - ); - }, - - async getStaffConflictAppointment( - staffId: number, - date: Date, - startTime: string, - excludeId: number - ): Promise { - return ( - (await db.appointment.findFirst({ - where: { - staffId, - date, - startTime, - NOT: { id: excludeId }, - }, - })) ?? undefined - ); - }, - - // Staff methods - async getStaff(id: number): Promise { - const staff = await db.staff.findUnique({ where: { id } }); - return staff ?? undefined; - }, - - async getAllStaff(): Promise { - const staff = await db.staff.findMany(); - return staff; - }, - - async createStaff(staff: Staff): Promise { - const createdStaff = await db.staff.create({ - data: staff, - }); - return createdStaff; - }, - - async updateStaff( - id: number, - updates: Partial - ): Promise { - const updatedStaff = await db.staff.update({ - where: { id }, - data: updates, - }); - return updatedStaff ?? undefined; - }, - - async deleteStaff(id: number): Promise { - try { - await db.staff.delete({ where: { id } }); - return true; - } catch (error) { - console.error("Error deleting staff:", error); - return false; - } - }, - - async countAppointmentsByStaffId(staffId: number): Promise { - return await db.appointment.count({ where: { staffId } }); - }, - - async countClaimsByStaffId(staffId: number): Promise { - return await db.claim.count({ where: { staffId } }); - }, - - // Claim methods implementation - async getClaim(id: number): Promise { - const claim = await db.claim.findUnique({ where: { id } }); - return claim ?? undefined; - }, - - async getRecentClaimsByPatientId( - patientId: number, - limit: number, - offset: number - ): Promise { - return db.claim.findMany({ - where: { patientId }, - orderBy: { createdAt: "desc" }, - skip: offset, - take: limit, - include: { - serviceLines: true, - staff: true, - claimFiles: true, - }, - }); - }, - - async getTotalClaimCountByPatient(patientId: number): Promise { - return db.claim.count({ - where: { patientId }, - }); - }, - - async getClaimsByAppointmentId(appointmentId: number): Promise { - return await db.claim.findMany({ where: { appointmentId } }); - }, - - async getRecentClaims( - limit: number, - offset: number - ): Promise { - return db.claim.findMany({ - orderBy: { createdAt: "desc" }, - skip: offset, - take: limit, - include: { serviceLines: true, staff: true, claimFiles: true }, - }); - }, - - async getTotalClaimCount(): Promise { - return db.claim.count(); - }, - - async createClaim(claim: InsertClaim): Promise { - return await db.claim.create({ data: claim as Claim }); - }, - - async updateClaim(id: number, updates: UpdateClaim): Promise { - try { - return await db.claim.update({ - where: { id }, - data: updates, - }); - } catch (err) { - throw new Error(`Claim with ID ${id} not found`); - } - }, - - async deleteClaim(id: number): Promise { - try { - await db.claim.delete({ where: { id } }); - } catch (err) { - throw new Error(`Claim with ID ${id} not found`); - } - }, - - // Insurance Creds - async getInsuranceCredential(id: number) { - return await db.insuranceCredential.findUnique({ where: { id } }); - }, - - async getInsuranceCredentialsByUser(userId: number) { - return await db.insuranceCredential.findMany({ where: { userId } }); - }, - - async createInsuranceCredential(data: InsertInsuranceCredential) { - return await db.insuranceCredential.create({ - data: data as InsuranceCredential, - }); - }, - - async updateInsuranceCredential( - id: number, - updates: Partial - ) { - return await db.insuranceCredential.update({ - where: { id }, - data: updates, - }); - }, - - async deleteInsuranceCredential(userId: number, id: number) { - try { - await db.insuranceCredential.delete({ where: { userId, id } }); - return true; - } catch { - return false; - } - }, - - async getInsuranceCredentialByUserAndSiteKey( - userId: number, - siteKey: string - ): Promise { - return await db.insuranceCredential.findFirst({ - where: { userId, siteKey }, - }); - }, - - // PDF Files - async createPdfFile(groupId, filename, pdfData) { - return db.pdfFile.create({ - data: { - groupId, - filename, - pdfData, - }, - }); - }, - - async getAllPdfGroups(): Promise { - return db.pdfGroup.findMany({ - orderBy: { - createdAt: "desc", - }, - }); - }, - - async getPdfFileById(id) { - return (await db.pdfFile.findUnique({ where: { id } })) ?? undefined; - }, - - /** - * getPdfFilesByGroupId: supports - * - getPdfFilesByGroupId(groupId) => Promise - * - getPdfFilesByGroupId(groupId, { limit, offset }) => Promise<{ total, data }> - * - getPdfFilesByGroupId(groupId, { limit, offset, withGroup: true }) => Promise<{ total, data: PdfFileWithGroup[] }> - */ - async getPdfFilesByGroupId(groupId, opts) { - // if pagination is requested (limit provided) return total + page - const wantsPagination = - !!opts && - (typeof opts.limit === "number" || typeof opts.offset === "number"); - - if (wantsPagination) { - const limit = Math.min(Number(opts?.limit ?? 5), 1000); - const offset = Number(opts?.offset ?? 0); - - if (opts?.withGroup) { - // return total + data with group included - const [total, data] = await Promise.all([ - db.pdfFile.count({ where: { groupId } }), - db.pdfFile.findMany({ - where: { groupId }, - orderBy: { uploadedAt: "desc" }, - take: limit, - skip: offset, - include: { group: true }, // only include - }), - ]); - - return { total, data }; - } else { - // return total + data with limited fields via select - const [total, data] = await Promise.all([ - db.pdfFile.count({ where: { groupId } }), - db.pdfFile.findMany({ - where: { groupId }, - orderBy: { uploadedAt: "desc" }, - take: limit, - skip: offset, - select: { id: true, filename: true, uploadedAt: true }, // only select - }), - ]); - - // Note: selected shape won't have all PdfFile fields; cast if needed - return { total, data: data as unknown as PdfFile[] }; - } - } - - // non-paginated: return all files (keep descending order) - if (opts?.withGroup) { - const all = await db.pdfFile.findMany({ - where: { groupId }, - orderBy: { uploadedAt: "desc" }, - include: { group: true }, - }); - return all as PdfFile[]; - } else { - const all = await db.pdfFile.findMany({ - where: { groupId }, - orderBy: { uploadedAt: "desc" }, - // no select or include -> returns full PdfFile - }); - return all as PdfFile[]; - } - }, - - async getRecentPdfFiles(limit: number, offset: number): Promise { - return db.pdfFile.findMany({ - skip: offset, - take: limit, - orderBy: { uploadedAt: "desc" }, - include: { group: true }, - }); - }, - - async updatePdfFile(id, updates) { - try { - return await db.pdfFile.update({ - where: { id }, - data: updates, - }); - } catch { - return undefined; - } - }, - - async deletePdfFile(id) { - try { - await db.pdfFile.delete({ where: { id } }); - return true; - } catch { - return false; - } - }, - - // ---------------------- - // PdfGroup CRUD - // ---------------------- - - async createPdfGroup(patientId, title, titleKey) { - return db.pdfGroup.create({ - data: { - patientId, - title, - titleKey, - }, - }); - }, - - async findPdfGroupByPatientTitleKey(patientId, titleKey) { - return ( - (await db.pdfGroup.findFirst({ - where: { - patientId, - titleKey, - }, - })) ?? undefined - ); - }, - - async getPdfGroupById(id) { - return (await db.pdfGroup.findUnique({ where: { id } })) ?? undefined; - }, - - async getPdfGroupsByPatientId(patientId) { - return db.pdfGroup.findMany({ - where: { patientId }, - orderBy: { createdAt: "desc" }, - }); - }, - - async updatePdfGroup(id, updates) { - try { - return await db.pdfGroup.update({ - where: { id }, - data: updates, - }); - } catch { - return undefined; - } - }, - - async deletePdfGroup(id) { - try { - await db.pdfGroup.delete({ where: { id } }); - return true; - } catch { - return false; - } - }, - - // Payment Methods - async getPayment(id: number): Promise { - const payment = await db.payment.findUnique({ where: { id } }); - return payment ?? undefined; - }, - - async createPayment(payment: InsertPayment): Promise { - return db.payment.create({ data: payment as Payment }); - }, - - async updatePayment(id: number, updates: UpdatePayment): Promise { - const existing = await db.payment.findFirst({ where: { id } }); - if (!existing) { - throw new Error("Payment not found"); - } - - return db.payment.update({ - where: { id }, - data: updates, - }); - }, - - async deletePayment(id: number, userId: number): Promise { - const existing = await db.payment.findFirst({ where: { id, userId } }); - if (!existing) { - throw new Error("Not authorized or payment not found"); - } - - await db.payment.delete({ where: { id } }); - }, - - async getRecentPaymentsByPatientId( - patientId: number, - limit: number, - offset: number - ): Promise { - const payments = await db.payment.findMany({ - where: { patientId }, - orderBy: { createdAt: "desc" }, - skip: offset, - take: limit, - include: { - claim: { - include: { - serviceLines: true, - }, - }, - serviceLines: true, - serviceLineTransactions: { - include: { - serviceLine: true, - }, - }, - updatedBy: true, - patient: true, - }, - }); - - return payments.map((payment) => ({ - ...payment, - patientName: payment.claim?.patientName ?? "", - paymentDate: payment.createdAt, - paymentMethod: payment.serviceLineTransactions[0]?.method ?? "OTHER", - })); - }, - - async getTotalPaymentCountByPatient(patientId: number): Promise { - return db.payment.count({ - where: { patientId }, - }); - }, - - async getPaymentById(id: number): Promise { - const payment = await db.payment.findFirst({ - where: { id }, - include: { - claim: { - include: { - serviceLines: true, - }, - }, - serviceLines: true, - serviceLineTransactions: { - include: { - serviceLine: true, - }, - }, - updatedBy: true, - patient: true, - }, - }); - - if (!payment) return null; - - return { - ...payment, - patientName: payment.claim?.patientName ?? "", - paymentDate: payment.createdAt, - paymentMethod: payment.serviceLineTransactions[0]?.method ?? "OTHER", - }; - }, - - async getPaymentsByClaimId( - claimId: number - ): Promise { - const payment = await db.payment.findFirst({ - where: { claimId }, - include: { - claim: { - include: { - serviceLines: true, - }, - }, - serviceLines: true, - serviceLineTransactions: { - include: { - serviceLine: true, - }, - }, - updatedBy: true, - patient: true, - }, - }); - - if (!payment) return null; - - return { - ...payment, - patientName: payment.claim?.patientName ?? "", - paymentDate: payment.createdAt, - paymentMethod: payment.serviceLineTransactions[0]?.method ?? "OTHER", - }; - }, - - async getRecentPayments( - limit: number, - offset: number - ): Promise { - const payments = await db.payment.findMany({ - orderBy: { createdAt: "desc" }, - skip: offset, - take: limit, - include: { - claim: { - include: { - serviceLines: true, - }, - }, - serviceLines: true, - serviceLineTransactions: { - include: { - serviceLine: true, - }, - }, - updatedBy: true, - patient: true, - }, - }); - - return payments.map((payment) => ({ - ...payment, - patientName: payment.claim?.patientName ?? "", - paymentDate: payment.createdAt, - paymentMethod: payment.serviceLineTransactions[0]?.method ?? "OTHER", - })); - }, - - async getPaymentsByDateRange( - from: Date, - to: Date - ): Promise { - const payments = await db.payment.findMany({ - where: { - createdAt: { - gte: from, - lte: to, - }, - }, - orderBy: { createdAt: "desc" }, - include: { - claim: { - include: { - serviceLines: true, - }, - }, - serviceLines: true, - serviceLineTransactions: { - include: { - serviceLine: true, - }, - }, - updatedBy: true, - patient: true, - }, - }); - - return payments.map((payment) => ({ - ...payment, - patientName: payment.claim?.patientName ?? "", - paymentDate: payment.createdAt, - paymentMethod: payment.serviceLineTransactions[0]?.method ?? "OTHER", - })); - }, - - async getTotalPaymentCount(): Promise { - return db.payment.count(); - }, - - // ============================== - // Database Backup methods - // ============================== - async createBackup(userId) { - return await db.databaseBackup.create({ data: { userId } }); - }, - - async getLastBackup(userId) { - return await db.databaseBackup.findFirst({ - where: { userId }, - orderBy: { createdAt: "desc" }, - }); - }, - - async getBackups(userId, limit = 10) { - return await db.databaseBackup.findMany({ - where: { userId }, - orderBy: { createdAt: "desc" }, - take: limit, - }); - }, - - async deleteBackups(userId) { - const result = await db.databaseBackup.deleteMany({ where: { userId } }); - return result.count; - }, - - // ============================== - // Notification methods - // ============================== - async createNotification(userId, type, message) { - return await db.notification.create({ - data: { userId, type, message }, - }); - }, - - async getNotifications( - userId: number, - limit = 50, - offset = 0 - ): Promise { - return await db.notification.findMany({ - where: { userId }, - orderBy: { createdAt: "desc" }, - take: limit, - skip: offset, - }); - }, - - async markNotificationRead(userId, notificationId) { - const result = await db.notification.updateMany({ - where: { id: notificationId, userId }, - data: { read: true }, - }); - return result.count > 0; - }, - - async markAllNotificationsRead(userId) { - const result = await db.notification.updateMany({ - where: { userId }, - data: { read: true }, - }); - return result.count; - }, - - async deleteNotificationsByType(userId, type) { - const result = await db.notification.deleteMany({ - where: { userId, type }, - }); - return result.count; - }, - - async deleteAllNotifications(userId: number): Promise { - const result = await db.notification.deleteMany({ - where: { userId }, - }); - return result.count; - }, }; + +export default storage; diff --git a/apps/Backend/src/storage/insurance-creds-storage.ts b/apps/Backend/src/storage/insurance-creds-storage.ts new file mode 100644 index 0000000..9a9faf1 --- /dev/null +++ b/apps/Backend/src/storage/insurance-creds-storage.ts @@ -0,0 +1,63 @@ +import { InsertInsuranceCredential, InsuranceCredential } from "@repo/db/types"; +import { prisma as db } from "@repo/db/client"; + +export interface IStorage { + getInsuranceCredential(id: number): Promise; + getInsuranceCredentialsByUser(userId: number): Promise; + createInsuranceCredential( + data: InsertInsuranceCredential + ): Promise; + updateInsuranceCredential( + id: number, + updates: Partial + ): Promise; + deleteInsuranceCredential(userId: number, id: number): Promise; + getInsuranceCredentialByUserAndSiteKey( + userId: number, + siteKey: string + ): Promise; +} + +export const insuranceCredsStorage: IStorage = { + async getInsuranceCredential(id: number) { + return await db.insuranceCredential.findUnique({ where: { id } }); + }, + + async getInsuranceCredentialsByUser(userId: number) { + return await db.insuranceCredential.findMany({ where: { userId } }); + }, + + async createInsuranceCredential(data: InsertInsuranceCredential) { + return await db.insuranceCredential.create({ + data: data as InsuranceCredential, + }); + }, + + async updateInsuranceCredential( + id: number, + updates: Partial + ) { + return await db.insuranceCredential.update({ + where: { id }, + data: updates, + }); + }, + + async deleteInsuranceCredential(userId: number, id: number) { + try { + await db.insuranceCredential.delete({ where: { userId, id } }); + return true; + } catch { + return false; + } + }, + + async getInsuranceCredentialByUserAndSiteKey( + userId: number, + siteKey: string + ): Promise { + return await db.insuranceCredential.findFirst({ + where: { userId, siteKey }, + }); + }, +}; diff --git a/apps/Backend/src/storage/notifications-storage.ts b/apps/Backend/src/storage/notifications-storage.ts new file mode 100644 index 0000000..e7ca078 --- /dev/null +++ b/apps/Backend/src/storage/notifications-storage.ts @@ -0,0 +1,80 @@ +import { Notification, NotificationTypes } from "@repo/db/types"; +import { prisma as db } from "@repo/db/client"; + +export interface IStorage { + // Notification methods + createNotification( + userId: number, + type: NotificationTypes, + message: string + ): Promise; + getNotifications( + userId: number, + limit?: number, + offset?: number + ): Promise; + markNotificationRead( + userId: number, + notificationId: number + ): Promise; + markAllNotificationsRead(userId: number): Promise; + deleteNotificationsByType( + userId: number, + type: NotificationTypes + ): Promise; + deleteAllNotifications(userId: number): Promise; +} + +export const notificationsStorage: IStorage = { + // ============================== + // Notification methods + // ============================== + async createNotification(userId, type, message) { + return await db.notification.create({ + data: { userId, type, message }, + }); + }, + + async getNotifications( + userId: number, + limit = 50, + offset = 0 + ): Promise { + return await db.notification.findMany({ + where: { userId }, + orderBy: { createdAt: "desc" }, + take: limit, + skip: offset, + }); + }, + + async markNotificationRead(userId, notificationId) { + const result = await db.notification.updateMany({ + where: { id: notificationId, userId }, + data: { read: true }, + }); + return result.count > 0; + }, + + async markAllNotificationsRead(userId) { + const result = await db.notification.updateMany({ + where: { userId }, + data: { read: true }, + }); + return result.count; + }, + + async deleteNotificationsByType(userId, type) { + const result = await db.notification.deleteMany({ + where: { userId, type }, + }); + return result.count; + }, + + async deleteAllNotifications(userId: number): Promise { + const result = await db.notification.deleteMany({ + where: { userId }, + }); + return result.count; + }, +}; diff --git a/apps/Backend/src/storage/patients-storage.ts b/apps/Backend/src/storage/patients-storage.ts new file mode 100644 index 0000000..47a3279 --- /dev/null +++ b/apps/Backend/src/storage/patients-storage.ts @@ -0,0 +1,141 @@ +import { InsertPatient, Patient, UpdatePatient } from "@repo/db/types"; +import { prisma as db } from "@repo/db/client"; + +export interface IStorage { + // Patient methods + getPatient(id: number): Promise; + getPatientByInsuranceId(insuranceId: string): Promise; + getPatientsByUserId(userId: number): Promise; + getRecentPatients(limit: number, offset: number): Promise; + getPatientsByIds(ids: number[]): Promise; + createPatient(patient: InsertPatient): Promise; + updatePatient(id: number, patient: UpdatePatient): Promise; + deletePatient(id: number): Promise; + searchPatients(args: { + filters: any; + limit: number; + offset: number; + }): Promise< + { + id: number; + firstName: string | null; + lastName: string | null; + phone: string | null; + gender: string | null; + dateOfBirth: Date; + insuranceId: string | null; + insuranceProvider: string | null; + status: string; + }[] + >; + getTotalPatientCount(): Promise; + countPatients(filters: any): Promise; // optional but useful +} + +export const patientsStorage: IStorage = { + // Patient methods + async getPatient(id: number): Promise { + const patient = await db.patient.findUnique({ where: { id } }); + return patient ?? undefined; + }, + + async getPatientsByUserId(userId: number): Promise { + return await db.patient.findMany({ where: { userId } }); + }, + + async getPatientByInsuranceId(insuranceId: string): Promise { + return db.patient.findFirst({ + where: { insuranceId }, + }); + }, + + async getRecentPatients(limit: number, offset: number): Promise { + return db.patient.findMany({ + skip: offset, + take: limit, + orderBy: { createdAt: "desc" }, + }); + }, + + async getPatientsByIds(ids: number[]): Promise { + if (!ids || ids.length === 0) return []; + const uniqueIds = Array.from(new Set(ids)); + return db.patient.findMany({ + where: { id: { in: uniqueIds } }, + select: { + id: true, + firstName: true, + lastName: true, + phone: true, + email: true, + dateOfBirth: true, + gender: true, + insuranceId: true, + insuranceProvider: true, + status: true, + userId: true, + createdAt: true, + }, + }); + }, + + async createPatient(patient: InsertPatient): Promise { + return await db.patient.create({ data: patient as Patient }); + }, + + async updatePatient(id: number, updateData: UpdatePatient): Promise { + try { + return await db.patient.update({ + where: { id }, + data: updateData, + }); + } catch (err) { + throw new Error(`Patient with ID ${id} not found`); + } + }, + + async deletePatient(id: number): Promise { + try { + await db.patient.delete({ where: { id } }); + } catch (err) { + console.error("Error deleting patient:", err); + throw new Error(`Failed to delete patient: ${err}`); + } + }, + + async searchPatients({ + filters, + limit, + offset, + }: { + filters: any; + limit: number; + offset: number; + }) { + return db.patient.findMany({ + where: filters, + orderBy: { createdAt: "desc" }, + take: limit, + skip: offset, + select: { + id: true, + firstName: true, + lastName: true, + phone: true, + gender: true, + dateOfBirth: true, + insuranceId: true, + insuranceProvider: true, + status: true, + }, + }); + }, + + async getTotalPatientCount(): Promise { + return db.patient.count(); + }, + + async countPatients(filters: any) { + return db.patient.count({ where: filters }); + }, +}; diff --git a/apps/Backend/src/storage/payments-storage.ts b/apps/Backend/src/storage/payments-storage.ts new file mode 100644 index 0000000..d6e61ff --- /dev/null +++ b/apps/Backend/src/storage/payments-storage.ts @@ -0,0 +1,239 @@ +import { + InsertPayment, + Payment, + PaymentWithExtras, + UpdatePayment, +} from "@repo/db/types"; +import { prisma as db } from "@repo/db/client"; + +export interface IStorage { + // Payment methods: + getPayment(id: number): Promise; + createPayment(data: InsertPayment): Promise; + updatePayment(id: number, updates: UpdatePayment): Promise; + deletePayment(id: number, userId: number): Promise; + getPaymentById(id: number): Promise; + getRecentPaymentsByPatientId( + patientId: number, + limit: number, + offset: number + ): Promise; + getTotalPaymentCountByPatient(patientId: number): Promise; + getPaymentsByClaimId(claimId: number): Promise; + getRecentPayments( + limit: number, + offset: number + ): Promise; + getPaymentsByDateRange(from: Date, to: Date): Promise; + getTotalPaymentCount(): Promise; +} + +export const paymentsStorage: IStorage = { + // Payment Methods + async getPayment(id: number): Promise { + const payment = await db.payment.findUnique({ where: { id } }); + return payment ?? undefined; + }, + + async createPayment(payment: InsertPayment): Promise { + return db.payment.create({ data: payment as Payment }); + }, + + async updatePayment(id: number, updates: UpdatePayment): Promise { + const existing = await db.payment.findFirst({ where: { id } }); + if (!existing) { + throw new Error("Payment not found"); + } + + return db.payment.update({ + where: { id }, + data: updates, + }); + }, + + async deletePayment(id: number, userId: number): Promise { + const existing = await db.payment.findFirst({ where: { id, userId } }); + if (!existing) { + throw new Error("Not authorized or payment not found"); + } + + await db.payment.delete({ where: { id } }); + }, + + async getRecentPaymentsByPatientId( + patientId: number, + limit: number, + offset: number + ): Promise { + const payments = await db.payment.findMany({ + where: { patientId }, + orderBy: { createdAt: "desc" }, + skip: offset, + take: limit, + include: { + claim: { + include: { + serviceLines: true, + }, + }, + serviceLines: true, + serviceLineTransactions: { + include: { + serviceLine: true, + }, + }, + updatedBy: true, + patient: true, + }, + }); + + return payments.map((payment) => ({ + ...payment, + patientName: payment.claim?.patientName ?? "", + paymentDate: payment.createdAt, + paymentMethod: payment.serviceLineTransactions[0]?.method ?? "OTHER", + })); + }, + + async getTotalPaymentCountByPatient(patientId: number): Promise { + return db.payment.count({ + where: { patientId }, + }); + }, + + async getPaymentById(id: number): Promise { + const payment = await db.payment.findFirst({ + where: { id }, + include: { + claim: { + include: { + serviceLines: true, + }, + }, + serviceLines: true, + serviceLineTransactions: { + include: { + serviceLine: true, + }, + }, + updatedBy: true, + patient: true, + }, + }); + + if (!payment) return null; + + return { + ...payment, + patientName: payment.claim?.patientName ?? "", + paymentDate: payment.createdAt, + paymentMethod: payment.serviceLineTransactions[0]?.method ?? "OTHER", + }; + }, + + async getPaymentsByClaimId( + claimId: number + ): Promise { + const payment = await db.payment.findFirst({ + where: { claimId }, + include: { + claim: { + include: { + serviceLines: true, + }, + }, + serviceLines: true, + serviceLineTransactions: { + include: { + serviceLine: true, + }, + }, + updatedBy: true, + patient: true, + }, + }); + + if (!payment) return null; + + return { + ...payment, + patientName: payment.claim?.patientName ?? "", + paymentDate: payment.createdAt, + paymentMethod: payment.serviceLineTransactions[0]?.method ?? "OTHER", + }; + }, + + async getRecentPayments( + limit: number, + offset: number + ): Promise { + const payments = await db.payment.findMany({ + orderBy: { createdAt: "desc" }, + skip: offset, + take: limit, + include: { + claim: { + include: { + serviceLines: true, + }, + }, + serviceLines: true, + serviceLineTransactions: { + include: { + serviceLine: true, + }, + }, + updatedBy: true, + patient: true, + }, + }); + + return payments.map((payment) => ({ + ...payment, + patientName: payment.claim?.patientName ?? "", + paymentDate: payment.createdAt, + paymentMethod: payment.serviceLineTransactions[0]?.method ?? "OTHER", + })); + }, + + async getPaymentsByDateRange( + from: Date, + to: Date + ): Promise { + const payments = await db.payment.findMany({ + where: { + createdAt: { + gte: from, + lte: to, + }, + }, + orderBy: { createdAt: "desc" }, + include: { + claim: { + include: { + serviceLines: true, + }, + }, + serviceLines: true, + serviceLineTransactions: { + include: { + serviceLine: true, + }, + }, + updatedBy: true, + patient: true, + }, + }); + + return payments.map((payment) => ({ + ...payment, + patientName: payment.claim?.patientName ?? "", + paymentDate: payment.createdAt, + paymentMethod: payment.serviceLineTransactions[0]?.method ?? "OTHER", + })); + }, + + async getTotalPaymentCount(): Promise { + return db.payment.count(); + }, +}; diff --git a/apps/Backend/src/storage/staff-storage.ts b/apps/Backend/src/storage/staff-storage.ts new file mode 100644 index 0000000..57ec3a3 --- /dev/null +++ b/apps/Backend/src/storage/staff-storage.ts @@ -0,0 +1,61 @@ +import { Staff } from "@repo/db/types"; +import { prisma as db } from "@repo/db/client"; + +export interface IStorage { + getStaff(id: number): Promise; + getAllStaff(): Promise; + createStaff(staff: Staff): Promise; + updateStaff(id: number, updates: Partial): Promise; + deleteStaff(id: number): Promise; + countAppointmentsByStaffId(staffId: number): Promise; + countClaimsByStaffId(staffId: number): Promise; +} + +export const staffStorage: IStorage = { + // Staff methods + async getStaff(id: number): Promise { + const staff = await db.staff.findUnique({ where: { id } }); + return staff ?? undefined; + }, + + async getAllStaff(): Promise { + const staff = await db.staff.findMany(); + return staff; + }, + + async createStaff(staff: Staff): Promise { + const createdStaff = await db.staff.create({ + data: staff, + }); + return createdStaff; + }, + + async updateStaff( + id: number, + updates: Partial + ): Promise { + const updatedStaff = await db.staff.update({ + where: { id }, + data: updates, + }); + return updatedStaff ?? undefined; + }, + + async deleteStaff(id: number): Promise { + try { + await db.staff.delete({ where: { id } }); + return true; + } catch (error) { + console.error("Error deleting staff:", error); + return false; + } + }, + + async countAppointmentsByStaffId(staffId: number): Promise { + return await db.appointment.count({ where: { staffId } }); + }, + + async countClaimsByStaffId(staffId: number): Promise { + return await db.claim.count({ where: { staffId } }); + }, +}; diff --git a/apps/Backend/src/storage/users-storage.ts b/apps/Backend/src/storage/users-storage.ts new file mode 100644 index 0000000..6db2d22 --- /dev/null +++ b/apps/Backend/src/storage/users-storage.ts @@ -0,0 +1,53 @@ +import { InsertUser, User } from "@repo/db/types"; +import { prisma as db } from "@repo/db/client"; + +export interface IUsersStorage { + // User methods + getUser(id: number): Promise; + getUsers(limit: number, offset: number): Promise; + getUserByUsername(username: string): Promise; + createUser(user: InsertUser): Promise; + updateUser(id: number, updates: Partial): Promise; + deleteUser(id: number): Promise; +} + +export const usersStorage: IUsersStorage = { + // User methods + async getUser(id: number): Promise { + const user = await db.user.findUnique({ where: { id } }); + return user ?? undefined; + }, + + async getUsers(limit: number, offset: number): Promise { + return await db.user.findMany({ skip: offset, take: limit }); + }, + + async getUserByUsername(username: string): Promise { + const user = await db.user.findUnique({ where: { username } }); + return user ?? undefined; + }, + + async createUser(user: InsertUser): Promise { + return await db.user.create({ data: user as User }); + }, + + async updateUser( + id: number, + updates: Partial + ): Promise { + try { + return await db.user.update({ where: { id }, data: updates }); + } catch { + return undefined; + } + }, + + async deleteUser(id: number): Promise { + try { + await db.user.delete({ where: { id } }); + return true; + } catch { + return false; + } + }, +};