initial commit
This commit is contained in:
258
apps/Backend/src/routes/patient-documents.ts
Executable file
258
apps/Backend/src/routes/patient-documents.ts
Executable file
@@ -0,0 +1,258 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user