180 lines
5.0 KiB
TypeScript
Executable File
180 lines
5.0 KiB
TypeScript
Executable File
import { PatientDocument, CreatePatientDocument } from "@repo/db/types";
|
|
import { prisma as db } from "@repo/db/client";
|
|
import path from "path";
|
|
import fs from "fs/promises";
|
|
import { randomBytes } from "crypto";
|
|
|
|
const UPLOAD_DIR = path.join(process.cwd(), "uploads", "patient-documents");
|
|
|
|
// Get local file path from URL
|
|
const getLocalFilePath = (fileUrl: string): string => {
|
|
const filename = fileUrl.split('/').pop() || '';
|
|
return path.join(UPLOAD_DIR, filename);
|
|
};
|
|
|
|
// Get the base URL for serving files
|
|
const getBaseUrl = (): string => {
|
|
// For development, use localhost instead of 0.0.0.0
|
|
const host = process.env.HOST === '0.0.0.0' ? 'localhost' : (process.env.HOST || 'localhost');
|
|
const port = process.env.PORT || '5000';
|
|
const protocol = process.env.NODE_ENV === 'production' ? 'https' : 'http';
|
|
return `${protocol}://${host}:${port}`;
|
|
};
|
|
|
|
// Ensure upload directory exists
|
|
const ensureUploadDir = async () => {
|
|
try {
|
|
await fs.access(UPLOAD_DIR);
|
|
} catch {
|
|
await fs.mkdir(UPLOAD_DIR, { recursive: true });
|
|
}
|
|
};
|
|
|
|
// Generate unique filename
|
|
const generateUniqueFilename = (originalName: string): string => {
|
|
const ext = path.extname(originalName);
|
|
const name = path.basename(originalName, ext);
|
|
const timestamp = Date.now();
|
|
const random = randomBytes(4).toString("hex");
|
|
return `${name}-${timestamp}-${random}${ext}`;
|
|
};
|
|
|
|
export const patientDocumentsStorage = {
|
|
// Create a new patient document
|
|
createPatientDocument: async (
|
|
patientId: number,
|
|
filename: string,
|
|
originalName: string,
|
|
mimeType: string,
|
|
fileSize: number,
|
|
buffer: Buffer
|
|
): Promise<PatientDocument> => {
|
|
await ensureUploadDir();
|
|
|
|
const uniqueFilename = generateUniqueFilename(filename);
|
|
const localFilePath = path.join(UPLOAD_DIR, uniqueFilename);
|
|
|
|
// Save file to disk
|
|
await fs.writeFile(localFilePath, buffer);
|
|
|
|
// Create the full URL for accessing the file using the uploads directory structure
|
|
const fileUrl = `${getBaseUrl()}/uploads/patient-documents/${uniqueFilename}`;
|
|
|
|
// Create database record with full URL
|
|
const document = await db.patientDocument.create({
|
|
data: {
|
|
patientId,
|
|
filename: uniqueFilename,
|
|
originalName,
|
|
mimeType,
|
|
fileSize: BigInt(fileSize),
|
|
filePath: fileUrl, // Store the full URL instead of local path
|
|
},
|
|
});
|
|
|
|
return document;
|
|
},
|
|
|
|
// Get all documents for a patient
|
|
getDocumentsByPatientId: async (patientId: number): Promise<PatientDocument[]> => {
|
|
return await db.patientDocument.findMany({
|
|
where: { patientId },
|
|
orderBy: { uploadedAt: "desc" },
|
|
});
|
|
},
|
|
|
|
// Get a specific document by ID
|
|
getDocumentById: async (id: number): Promise<PatientDocument | null> => {
|
|
return await db.patientDocument.findUnique({
|
|
where: { id },
|
|
include: {
|
|
patient: {
|
|
select: {
|
|
firstName: true,
|
|
lastName: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
},
|
|
|
|
// Get document file content
|
|
getDocumentFile: async (id: number): Promise<{ buffer: Buffer; document: PatientDocument } | null> => {
|
|
const document = await db.patientDocument.findUnique({
|
|
where: { id },
|
|
});
|
|
|
|
if (!document) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
// Get local file path from the stored URL
|
|
const localFilePath = getLocalFilePath(document.filePath);
|
|
const buffer = await fs.readFile(localFilePath);
|
|
return { buffer, document };
|
|
} catch (error) {
|
|
console.error("Error reading file:", error);
|
|
return null;
|
|
}
|
|
},
|
|
|
|
// Delete a document
|
|
deleteDocument: async (id: number): Promise<boolean> => {
|
|
const document = await db.patientDocument.findUnique({
|
|
where: { id },
|
|
});
|
|
|
|
if (!document) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
// Get local file path from the stored URL
|
|
const localFilePath = getLocalFilePath(document.filePath);
|
|
|
|
// Delete file from disk
|
|
await fs.unlink(localFilePath);
|
|
|
|
// Delete database record
|
|
await db.patientDocument.delete({
|
|
where: { id },
|
|
});
|
|
|
|
return true;
|
|
} catch (error) {
|
|
console.error("Error deleting document:", error);
|
|
return false;
|
|
}
|
|
},
|
|
|
|
// Update document metadata
|
|
updateDocument: async (id: number, data: Partial<CreatePatientDocument>): Promise<PatientDocument | null> => {
|
|
return await db.patientDocument.update({
|
|
where: { id },
|
|
data,
|
|
});
|
|
},
|
|
|
|
// Get documents with pagination
|
|
getDocumentsByPatientIdPaginated: async (
|
|
patientId: number,
|
|
limit: number,
|
|
offset: number
|
|
): Promise<{ documents: PatientDocument[]; total: number }> => {
|
|
const [documents, total] = await Promise.all([
|
|
db.patientDocument.findMany({
|
|
where: { patientId },
|
|
orderBy: { uploadedAt: "desc" },
|
|
skip: offset,
|
|
take: limit,
|
|
}),
|
|
db.patientDocument.count({
|
|
where: { patientId },
|
|
}),
|
|
]);
|
|
|
|
return { documents, total };
|
|
},
|
|
};
|