feat(categorised files)

This commit is contained in:
2025-09-25 03:25:56 +05:30
parent 2b9d135105
commit e86aefd62f
12 changed files with 1567 additions and 1149 deletions

View File

@@ -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<Appointment | undefined>;
getAllAppointments(): Promise<Appointment[]>;
getAppointmentsByUserId(userId: number): Promise<Appointment[]>;
getAppointmentsByPatientId(patientId: number): Promise<Appointment[]>;
getPatientFromAppointmentId(
appointmentId: number
): Promise<Patient | undefined>;
getRecentAppointments(limit: number, offset: number): Promise<Appointment[]>;
getAppointmentsOnRange(start: Date, end: Date): Promise<Appointment[]>;
createAppointment(appointment: InsertAppointment): Promise<Appointment>;
updateAppointment(
id: number,
appointment: UpdateAppointment
): Promise<Appointment>;
deleteAppointment(id: number): Promise<void>;
getPatientAppointmentByDateTime(
patientId: number,
date: Date,
startTime: string
): Promise<Appointment | undefined>;
getStaffAppointmentByDateTime(
staffId: number,
date: Date,
startTime: string,
excludeId?: number
): Promise<Appointment | undefined>;
getPatientConflictAppointment(
patientId: number,
date: Date,
startTime: string,
excludeId: number
): Promise<Appointment | undefined>;
getStaffConflictAppointment(
staffId: number,
date: Date,
startTime: string,
excludeId: number
): Promise<Appointment | undefined>;
}
export const appointmentsStorage: IStorage = {
async getAppointment(id: number): Promise<Appointment | undefined> {
const appointment = await db.appointment.findUnique({ where: { id } });
return appointment ?? undefined;
},
async getAllAppointments(): Promise<Appointment[]> {
return await db.appointment.findMany();
},
async getAppointmentsByUserId(userId: number): Promise<Appointment[]> {
return await db.appointment.findMany({ where: { userId } });
},
async getAppointmentsByPatientId(patientId: number): Promise<Appointment[]> {
return await db.appointment.findMany({ where: { patientId } });
},
async getPatientFromAppointmentId(
appointmentId: number
): Promise<Patient | undefined> {
const appointment = await db.appointment.findUnique({
where: { id: appointmentId },
include: { patient: true },
});
return appointment?.patient ?? undefined;
},
async getAppointmentsOnRange(start: Date, end: Date): Promise<Appointment[]> {
return db.appointment.findMany({
where: {
date: {
gte: start,
lte: end,
},
},
orderBy: { date: "asc" },
});
},
async getRecentAppointments(
limit: number,
offset: number
): Promise<Appointment[]> {
return db.appointment.findMany({
skip: offset,
take: limit,
orderBy: { date: "desc" },
});
},
async createAppointment(
appointment: InsertAppointment
): Promise<Appointment> {
return await db.appointment.create({ data: appointment as Appointment });
},
async updateAppointment(
id: number,
updateData: UpdateAppointment
): Promise<Appointment> {
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<void> {
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<Appointment | undefined> {
return (
(await db.appointment.findFirst({
where: {
patientId,
date,
startTime,
},
})) ?? undefined
);
},
async getStaffAppointmentByDateTime(
staffId: number,
date: Date,
startTime: string,
excludeId?: number
): Promise<Appointment | undefined> {
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<Appointment | undefined> {
return (
(await db.appointment.findFirst({
where: {
patientId,
date,
startTime,
NOT: { id: excludeId },
},
})) ?? undefined
);
},
async getStaffConflictAppointment(
staffId: number,
date: Date,
startTime: string,
excludeId: number
): Promise<Appointment | undefined> {
return (
(await db.appointment.findFirst({
where: {
staffId,
date,
startTime,
NOT: { id: excludeId },
},
})) ?? undefined
);
},
};

View File

@@ -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<Claim | undefined>;
getRecentClaimsByPatientId(
patientId: number,
limit: number,
offset: number
): Promise<ClaimWithServiceLines[]>;
getTotalClaimCountByPatient(patientId: number): Promise<number>;
getClaimsByAppointmentId(appointmentId: number): Promise<Claim[]>;
getRecentClaims(limit: number, offset: number): Promise<Claim[]>;
getTotalClaimCount(): Promise<number>;
createClaim(claim: InsertClaim): Promise<Claim>;
updateClaim(id: number, updates: UpdateClaim): Promise<Claim>;
deleteClaim(id: number): Promise<void>;
}
export const claimsStorage: IStorage = {
async getClaim(id: number): Promise<Claim | undefined> {
const claim = await db.claim.findUnique({ where: { id } });
return claim ?? undefined;
},
async getRecentClaimsByPatientId(
patientId: number,
limit: number,
offset: number
): Promise<ClaimWithServiceLines[]> {
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<number> {
return db.claim.count({
where: { patientId },
});
},
async getClaimsByAppointmentId(appointmentId: number): Promise<Claim[]> {
return await db.claim.findMany({ where: { appointmentId } });
},
async getRecentClaims(
limit: number,
offset: number
): Promise<ClaimWithServiceLines[]> {
return db.claim.findMany({
orderBy: { createdAt: "desc" },
skip: offset,
take: limit,
include: { serviceLines: true, staff: true, claimFiles: true },
});
},
async getTotalClaimCount(): Promise<number> {
return db.claim.count();
},
async createClaim(claim: InsertClaim): Promise<Claim> {
return await db.claim.create({ data: claim as Claim });
},
async updateClaim(id: number, updates: UpdateClaim): Promise<Claim> {
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<void> {
try {
await db.claim.delete({ where: { id } });
} catch (err) {
throw new Error(`Claim with ID ${id} not found`);
}
},
};

View File

@@ -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<CloudFolder | null>;
getFoldersByUser(
userId: number,
parentId: number | null,
limit: number,
offset: number
): Promise<CloudFolder[]>;
getRecentFolders(limit: number, offset: number): Promise<CloudFolder[]>;
createFolder(
userId: number,
name: string,
parentId?: number | null
): Promise<CloudFolder>;
updateFolder(
id: number,
updates: Partial<{ name?: string; parentId?: number | null }>
): Promise<CloudFolder | null>;
deleteFolder(id: number): Promise<boolean>;
// CloudFile methods
getFile(id: number): Promise<CloudFile | null>;
listFilesByFolderByUser(
userId: number,
folderId: number | null,
limit: number,
offset: number
): Promise<CloudFile[]>;
listFilesByFolder(
folderId: number | null,
limit: number,
offset: number
): Promise<CloudFile[]>;
// chunked upload methods
createFileInit(
userId: number,
name: string,
mimeType?: string | null,
expectedSize?: bigint | null,
totalChunks?: number | null,
folderId?: number | null
): Promise<CloudFile>;
addChunk(fileId: number, seq: number, data: Buffer): Promise<void>;
completeFile(fileId: number): Promise<{ ok: true; size: string }>;
deleteFile(fileId: number): Promise<boolean>;
// 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<void>;
}
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<CloudFile | null> {
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
},
};

View File

@@ -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<DatabaseBackup>;
getLastBackup(userId: number): Promise<DatabaseBackup | null>;
getBackups(userId: number, limit?: number): Promise<DatabaseBackup[]>;
deleteBackups(userId: number): Promise<number>; // 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;
},
};

View File

@@ -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<PdfFile>;
getPdfFileById(id: number): Promise<PdfFile | undefined>;
getPdfFilesByGroupId(
groupId: number,
opts?: { limit?: number; offset?: number; withGroup?: boolean }
): Promise<PdfFile[] | { total: number; data: PdfFile[] }>;
getRecentPdfFiles(limit: number, offset: number): Promise<PdfFile[]>;
deletePdfFile(id: number): Promise<boolean>;
updatePdfFile(
id: number,
updates: Partial<Pick<PdfFile, "filename" | "pdfData">>
): Promise<PdfFile | undefined>;
// PDF Group management
createPdfGroup(
patientId: number,
title: string,
titleKey: PdfTitleKey
): Promise<PdfGroup>;
findPdfGroupByPatientTitleKey(
patientId: number,
titleKey: PdfTitleKey
): Promise<PdfGroup | undefined>;
getAllPdfGroups(): Promise<PdfGroup[]>;
getPdfGroupById(id: number): Promise<PdfGroup | undefined>;
getPdfGroupsByPatientId(patientId: number): Promise<PdfGroup[]>;
updatePdfGroup(
id: number,
updates: Partial<Pick<PdfGroup, "title">>
): Promise<PdfGroup | undefined>;
deletePdfGroup(id: number): Promise<boolean>;
}
export const generalPdfStorage: IStorage = {
// PDF Files
async createPdfFile(groupId, filename, pdfData) {
return db.pdfFile.create({
data: {
groupId,
filename,
pdfData,
},
});
},
async getAllPdfGroups(): Promise<PdfGroup[]> {
return db.pdfGroup.findMany({
orderBy: {
createdAt: "desc",
},
});
},
async getPdfFileById(id) {
return (await db.pdfFile.findUnique({ where: { id } })) ?? undefined;
},
/**
* getPdfFilesByGroupId: supports
* - getPdfFilesByGroupId(groupId) => Promise<PdfFile[]>
* - 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<PdfFile[]> {
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;
}
},
};

File diff suppressed because it is too large Load Diff

View File

@@ -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<InsuranceCredential | null>;
getInsuranceCredentialsByUser(userId: number): Promise<InsuranceCredential[]>;
createInsuranceCredential(
data: InsertInsuranceCredential
): Promise<InsuranceCredential>;
updateInsuranceCredential(
id: number,
updates: Partial<InsuranceCredential>
): Promise<InsuranceCredential | null>;
deleteInsuranceCredential(userId: number, id: number): Promise<boolean>;
getInsuranceCredentialByUserAndSiteKey(
userId: number,
siteKey: string
): Promise<InsuranceCredential | null>;
}
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<InsuranceCredential>
) {
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<InsuranceCredential | null> {
return await db.insuranceCredential.findFirst({
where: { userId, siteKey },
});
},
};

View File

@@ -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<Notification>;
getNotifications(
userId: number,
limit?: number,
offset?: number
): Promise<Notification[]>;
markNotificationRead(
userId: number,
notificationId: number
): Promise<boolean>;
markAllNotificationsRead(userId: number): Promise<number>;
deleteNotificationsByType(
userId: number,
type: NotificationTypes
): Promise<number>;
deleteAllNotifications(userId: number): Promise<number>;
}
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<Notification[]> {
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<number> {
const result = await db.notification.deleteMany({
where: { userId },
});
return result.count;
},
};

View File

@@ -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<Patient | undefined>;
getPatientByInsuranceId(insuranceId: string): Promise<Patient | null>;
getPatientsByUserId(userId: number): Promise<Patient[]>;
getRecentPatients(limit: number, offset: number): Promise<Patient[]>;
getPatientsByIds(ids: number[]): Promise<Patient[]>;
createPatient(patient: InsertPatient): Promise<Patient>;
updatePatient(id: number, patient: UpdatePatient): Promise<Patient>;
deletePatient(id: number): Promise<void>;
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<number>;
countPatients(filters: any): Promise<number>; // optional but useful
}
export const patientsStorage: IStorage = {
// Patient methods
async getPatient(id: number): Promise<Patient | undefined> {
const patient = await db.patient.findUnique({ where: { id } });
return patient ?? undefined;
},
async getPatientsByUserId(userId: number): Promise<Patient[]> {
return await db.patient.findMany({ where: { userId } });
},
async getPatientByInsuranceId(insuranceId: string): Promise<Patient | null> {
return db.patient.findFirst({
where: { insuranceId },
});
},
async getRecentPatients(limit: number, offset: number): Promise<Patient[]> {
return db.patient.findMany({
skip: offset,
take: limit,
orderBy: { createdAt: "desc" },
});
},
async getPatientsByIds(ids: number[]): Promise<Patient[]> {
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<Patient> {
return await db.patient.create({ data: patient as Patient });
},
async updatePatient(id: number, updateData: UpdatePatient): Promise<Patient> {
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<void> {
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<number> {
return db.patient.count();
},
async countPatients(filters: any) {
return db.patient.count({ where: filters });
},
};

View File

@@ -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<Payment | undefined>;
createPayment(data: InsertPayment): Promise<Payment>;
updatePayment(id: number, updates: UpdatePayment): Promise<Payment>;
deletePayment(id: number, userId: number): Promise<void>;
getPaymentById(id: number): Promise<PaymentWithExtras | null>;
getRecentPaymentsByPatientId(
patientId: number,
limit: number,
offset: number
): Promise<PaymentWithExtras[] | null>;
getTotalPaymentCountByPatient(patientId: number): Promise<number>;
getPaymentsByClaimId(claimId: number): Promise<PaymentWithExtras | null>;
getRecentPayments(
limit: number,
offset: number
): Promise<PaymentWithExtras[]>;
getPaymentsByDateRange(from: Date, to: Date): Promise<PaymentWithExtras[]>;
getTotalPaymentCount(): Promise<number>;
}
export const paymentsStorage: IStorage = {
// Payment Methods
async getPayment(id: number): Promise<Payment | undefined> {
const payment = await db.payment.findUnique({ where: { id } });
return payment ?? undefined;
},
async createPayment(payment: InsertPayment): Promise<Payment> {
return db.payment.create({ data: payment as Payment });
},
async updatePayment(id: number, updates: UpdatePayment): Promise<Payment> {
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<void> {
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<PaymentWithExtras[]> {
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<number> {
return db.payment.count({
where: { patientId },
});
},
async getPaymentById(id: number): Promise<PaymentWithExtras | null> {
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<PaymentWithExtras | null> {
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<PaymentWithExtras[]> {
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<PaymentWithExtras[]> {
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<number> {
return db.payment.count();
},
};

View File

@@ -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<Staff | undefined>;
getAllStaff(): Promise<Staff[]>;
createStaff(staff: Staff): Promise<Staff>;
updateStaff(id: number, updates: Partial<Staff>): Promise<Staff | undefined>;
deleteStaff(id: number): Promise<boolean>;
countAppointmentsByStaffId(staffId: number): Promise<number>;
countClaimsByStaffId(staffId: number): Promise<number>;
}
export const staffStorage: IStorage = {
// Staff methods
async getStaff(id: number): Promise<Staff | undefined> {
const staff = await db.staff.findUnique({ where: { id } });
return staff ?? undefined;
},
async getAllStaff(): Promise<Staff[]> {
const staff = await db.staff.findMany();
return staff;
},
async createStaff(staff: Staff): Promise<Staff> {
const createdStaff = await db.staff.create({
data: staff,
});
return createdStaff;
},
async updateStaff(
id: number,
updates: Partial<Staff>
): Promise<Staff | undefined> {
const updatedStaff = await db.staff.update({
where: { id },
data: updates,
});
return updatedStaff ?? undefined;
},
async deleteStaff(id: number): Promise<boolean> {
try {
await db.staff.delete({ where: { id } });
return true;
} catch (error) {
console.error("Error deleting staff:", error);
return false;
}
},
async countAppointmentsByStaffId(staffId: number): Promise<number> {
return await db.appointment.count({ where: { staffId } });
},
async countClaimsByStaffId(staffId: number): Promise<number> {
return await db.claim.count({ where: { staffId } });
},
};

View File

@@ -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<User | undefined>;
getUsers(limit: number, offset: number): Promise<User[]>;
getUserByUsername(username: string): Promise<User | undefined>;
createUser(user: InsertUser): Promise<User>;
updateUser(id: number, updates: Partial<User>): Promise<User | undefined>;
deleteUser(id: number): Promise<boolean>;
}
export const usersStorage: IUsersStorage = {
// User methods
async getUser(id: number): Promise<User | undefined> {
const user = await db.user.findUnique({ where: { id } });
return user ?? undefined;
},
async getUsers(limit: number, offset: number): Promise<User[]> {
return await db.user.findMany({ skip: offset, take: limit });
},
async getUserByUsername(username: string): Promise<User | undefined> {
const user = await db.user.findUnique({ where: { username } });
return user ?? undefined;
},
async createUser(user: InsertUser): Promise<User> {
return await db.user.create({ data: user as User });
},
async updateUser(
id: number,
updates: Partial<User>
): Promise<User | undefined> {
try {
return await db.user.update({ where: { id }, data: updates });
} catch {
return undefined;
}
},
async deleteUser(id: number): Promise<boolean> {
try {
await db.user.delete({ where: { id } });
return true;
} catch {
return false;
}
},
};