Files
DentalManagementMH04/apps/Backend/src/routes/patient-documents.ts
2026-04-04 22:13:55 -04:00

259 lines
7.8 KiB
TypeScript
Executable File

import { Router } from "express";
import { Request, Response } from "express";
import { storage } from "../storage";
import multer from "multer";
import { z } from "zod";
const router = Router();
// Configure multer for file uploads
const upload = multer({
storage: multer.memoryStorage(),
limits: {
fileSize: 10 * 1024 * 1024, // 10MB limit
},
fileFilter: (req, file, cb) => {
// Accept common document and image formats
const allowedTypes = [
'application/pdf',
'image/jpeg',
'image/jpg',
'image/png',
'image/gif',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'text/plain',
];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Invalid file type. Only PDF, images, and documents are allowed.'));
}
}
});
// Validation schemas
const uploadDocumentSchema = z.object({
patientId: z.string().transform((val) => parseInt(val, 10)),
});
const getDocumentsSchema = z.object({
patientId: z.string().transform((val) => parseInt(val, 10)),
limit: z.string().optional().transform((val) => val ? parseInt(val, 10) : undefined),
offset: z.string().optional().transform((val) => val ? parseInt(val, 10) : undefined),
});
const deleteDocumentSchema = z.object({
id: z.string().transform((val) => parseInt(val, 10)),
});
// POST /api/patient-documents/upload
// Upload a document for a specific patient
router.post("/upload", upload.single("file"), async (req: Request, res: Response): Promise<any> => {
try {
const { patientId } = uploadDocumentSchema.parse(req.body);
const file = req.file;
if (!file) {
return res.status(400).json({ error: "No file uploaded" });
}
const document = await storage.createPatientDocument(
patientId,
file.originalname,
file.originalname,
file.mimetype,
file.size,
file.buffer
);
res.status(201).json({
success: true,
document: {
...document,
fileSize: Number(document.fileSize), // Convert BigInt to Number for JSON serialization
}
});
} catch (error) {
console.error("Error uploading document:", error);
if (error instanceof z.ZodError) {
return res.status(400).json({ error: "Invalid request data", details: error.errors });
}
if (error instanceof Error && error.message.includes('Invalid file type')) {
return res.status(400).json({ error: error.message });
}
res.status(500).json({ error: "Internal server error" });
}
});
// GET /api/patient-documents/patient/:patientId
// Get all documents for a specific patient
router.get("/patient/:patientId", async (req: Request, res: Response): Promise<any> => {
try {
const { patientId, limit, offset } = getDocumentsSchema.parse({
patientId: req.params.patientId,
limit: req.query.limit,
offset: req.query.offset,
});
if (limit !== undefined && offset !== undefined) {
// Paginated response
const result = await storage.getDocumentsByPatientIdPaginated(patientId, limit, offset);
res.json({
success: true,
documents: result.documents.map(doc => ({
...doc,
fileSize: Number(doc.fileSize), // Convert BigInt to Number
})),
total: result.total,
});
} else {
// Non-paginated response
const documents = await storage.getDocumentsByPatientId(patientId);
res.json({
success: true,
documents: documents.map(doc => ({
...doc,
fileSize: Number(doc.fileSize), // Convert BigInt to Number
})),
});
}
} catch (error) {
console.error("Error fetching documents:", error);
if (error instanceof z.ZodError) {
return res.status(400).json({ error: "Invalid patient ID", details: error.errors });
}
res.status(500).json({ error: "Internal server error" });
}
});
// GET /api/patient-documents/:id/download
// Download a specific document
router.get("/:id/download", async (req: Request, res: Response): Promise<any> => {
try {
const { id } = deleteDocumentSchema.parse({ id: req.params.id });
const result = await storage.getDocumentFile(id);
if (!result) {
return res.status(404).json({ error: "Document not found" });
}
const { buffer, document } = result;
// Set appropriate headers
res.setHeader("Content-Type", document.mimeType);
res.setHeader("Content-Length", document.fileSize.toString());
res.setHeader(
"Content-Disposition",
`attachment; filename="${encodeURIComponent(document.originalName)}"`
);
res.send(buffer);
} catch (error) {
console.error("Error downloading document:", error);
if (error instanceof z.ZodError) {
return res.status(400).json({ error: "Invalid document ID", details: error.errors });
}
res.status(500).json({ error: "Internal server error" });
}
});
// GET /api/patient-documents/:id/view
// View a specific document (inline display)
router.get("/:id/view", async (req: Request, res: Response): Promise<any> => {
try {
const { id } = deleteDocumentSchema.parse({ id: req.params.id });
const result = await storage.getDocumentFile(id);
if (!result) {
return res.status(404).json({ error: "Document not found" });
}
const { buffer, document } = result;
// Set appropriate headers for inline viewing
res.setHeader("Content-Type", document.mimeType);
res.setHeader("Content-Length", document.fileSize.toString());
res.setHeader(
"Content-Disposition",
`inline; filename="${encodeURIComponent(document.originalName)}"`
);
res.send(buffer);
} catch (error) {
console.error("Error viewing document:", error);
if (error instanceof z.ZodError) {
return res.status(400).json({ error: "Invalid document ID", details: error.errors });
}
res.status(500).json({ error: "Internal server error" });
}
});
// DELETE /api/patient-documents/:id
// Delete a specific document
router.delete("/:id", async (req: Request, res: Response): Promise<any> => {
try {
const { id } = deleteDocumentSchema.parse({ id: req.params.id });
const success = await storage.deleteDocument(id);
if (!success) {
return res.status(404).json({ error: "Document not found" });
}
res.json({ success: true, message: "Document deleted successfully" });
} catch (error) {
console.error("Error deleting document:", error);
if (error instanceof z.ZodError) {
return res.status(400).json({ error: "Invalid document ID", details: error.errors });
}
res.status(500).json({ error: "Internal server error" });
}
});
// POST /api/patient-documents/scan
// Simulate document scanning (placeholder for actual scanner integration)
router.post("/scan", async (req: Request, res: Response): Promise<any> => {
try {
const { patientId } = uploadDocumentSchema.parse(req.body);
// This is a placeholder for actual scanner integration
// In a real implementation, you would:
// 1. Interface with scanner hardware/software
// 2. Capture the scanned image
// 3. Process and save the image
// 4. Return the document info
res.json({
success: true,
message: "Scanner interface ready. Please integrate with your scanner hardware/software.",
patientId,
note: "This endpoint requires integration with scanner hardware/software SDK."
});
} catch (error) {
console.error("Error scanning document:", error);
if (error instanceof z.ZodError) {
return res.status(400).json({ error: "Invalid request data", details: error.errors });
}
res.status(500).json({ error: "Internal server error" });
}
});
export default router;