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 => { 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 => { 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 => { 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 => { 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 => { 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 => { 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;