-
Notifications
You must be signed in to change notification settings - Fork 2
Feature/quicklabs backend #107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
bce9b66
55900c0
3ed0106
d5f0ae0
c64c0d0
a7f55b5
d9c07bf
a681006
13de8be
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| // controllers/labController.js (for patient/doctor to search labs) | ||
| import Lab from '../../models/Lab/Lab.js'; | ||
|
|
||
| // Search labs | ||
| export const searchLabs = async (req, res) => { | ||
| try { | ||
| const { name, city, testName } = req.query; | ||
|
|
||
| const query = { isActive: true }; | ||
|
|
||
| if (name) { | ||
| query.name = { $regex: name, $options: 'i' }; | ||
| } | ||
|
|
||
| if (city) { | ||
| query['address.city'] = { $regex: city, $options: 'i' }; | ||
| } | ||
|
|
||
| if (testName) { | ||
| query['tests.testName'] = { $regex: testName, $options: 'i' }; | ||
| } | ||
|
|
||
| const labs = await Lab.find(query) | ||
| .select( | ||
| 'name description address contact logo ratings tests homeCollectionAvailable homeCollectionFee' | ||
| ) | ||
| .lean(); | ||
|
|
||
| res.json({ | ||
| count: labs.length, | ||
| labs, | ||
| }); | ||
| } catch (error) { | ||
| console.error('Error searching labs:', error); | ||
| res.status(500).json({ message: 'Failed to search labs' }); | ||
| } | ||
| }; | ||
|
|
||
| // Get lab details | ||
| export const getLabDetails = async (req, res) => { | ||
| try { | ||
| const { labId } = req.params; | ||
|
|
||
| const lab = await Lab.findById(labId).select('-staff -labAdminId').lean(); | ||
|
|
||
| if (!lab) { | ||
| return res.status(404).json({ message: 'Lab not found' }); | ||
| } | ||
|
|
||
| res.json({ lab }); | ||
| } catch (error) { | ||
| console.error('Error fetching lab details:', error); | ||
| res.status(500).json({ message: 'Failed to fetch lab details' }); | ||
| } | ||
| }; | ||
|
|
||
| // Get lab tests | ||
| export const getLabTests = async (req, res) => { | ||
| try { | ||
| const { labId } = req.params; | ||
|
|
||
| const lab = await Lab.findById(labId).select('tests name').lean(); | ||
|
|
||
| if (!lab) { | ||
| return res.status(404).json({ message: 'Lab not found' }); | ||
| } | ||
|
|
||
| const activeTests = lab.tests.filter((test) => test.isActive); | ||
|
|
||
| res.json({ | ||
| labName: lab.name, | ||
| count: activeTests.length, | ||
| tests: activeTests, | ||
| }); | ||
| } catch (error) { | ||
| console.error('Error fetching lab tests:', error); | ||
| res.status(500).json({ message: 'Failed to fetch tests' }); | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,174 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||
| // controllers/labReportController.js | ||||||||||||||||||||||||||||||||||||||||||||||||
| import mongoose from 'mongoose'; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import LabReport from '../../models/Lab/LabReport.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import LabAppointment from '../../models/Lab/LabAppointment.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { uploadToCloudinary } from '../../services/uploadService.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // Upload lab report (by lab admin/staff) | ||||||||||||||||||||||||||||||||||||||||||||||||
| export const uploadLabReport = async (req, res) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const { appointmentId } = req.params; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const { testResults } = req.body; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if (!req.file) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return res.status(400).json({ message: 'Report file is required' }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const appointment = await LabAppointment.findById(appointmentId); | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (!appointment) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return res.status(404).json({ message: 'Appointment not found' }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // Upload report file | ||||||||||||||||||||||||||||||||||||||||||||||||
| const uploadResult = await uploadToCloudinary(req.file.path); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const report = new LabReport({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| appointmentId, | ||||||||||||||||||||||||||||||||||||||||||||||||
| patientId: appointment.patientId, | ||||||||||||||||||||||||||||||||||||||||||||||||
| labId: appointment.labId, | ||||||||||||||||||||||||||||||||||||||||||||||||
| doctorId: appointment.suggestedByDoctorId || undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||
| testResults: testResults ? JSON.parse(testResults) : [], | ||||||||||||||||||||||||||||||||||||||||||||||||
| reportFile: { | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+29
to
+31
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrap If + let parsedTestResults = [];
+ if (testResults) {
+ try {
+ parsedTestResults = JSON.parse(testResults);
+ } catch (e) {
+ return res.status(400).json({ message: 'Invalid testResults JSON format' });
+ }
+ }
+
const report = new LabReport({
appointmentId,
patientId: appointment.patientId,
labId: appointment.labId,
doctorId: appointment.suggestedByDoctorId || undefined,
- testResults: testResults ? JSON.parse(testResults) : [],
+ testResults: parsedTestResults,
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
| url: uploadResult.url, | ||||||||||||||||||||||||||||||||||||||||||||||||
| fileName: req.file.originalname, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| isSharedWithDoctor: !!appointment.suggestedByDoctorId, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| await report.save(); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // Update appointment | ||||||||||||||||||||||||||||||||||||||||||||||||
| appointment.reportId = report._id; | ||||||||||||||||||||||||||||||||||||||||||||||||
| appointment.status = 'completed'; | ||||||||||||||||||||||||||||||||||||||||||||||||
| await appointment.save(); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| res.status(201).json({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| message: 'Lab report uploaded successfully', | ||||||||||||||||||||||||||||||||||||||||||||||||
| report: report.toObject({ getters: true }), | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| console.error('Error uploading lab report:', error); | ||||||||||||||||||||||||||||||||||||||||||||||||
| res.status(500).json({ message: 'Failed to upload report' }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // Get patient's lab reports | ||||||||||||||||||||||||||||||||||||||||||||||||
| export const getPatientLabReports = async (req, res) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const { profileId } = req.user; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const reports = await LabReport.find({ patientId: profileId }) | ||||||||||||||||||||||||||||||||||||||||||||||||
| .populate('labId', 'name address') | ||||||||||||||||||||||||||||||||||||||||||||||||
| .populate('doctorId', 'firstName lastName specialization') | ||||||||||||||||||||||||||||||||||||||||||||||||
| .populate('appointmentId', 'tests appointmentDate') | ||||||||||||||||||||||||||||||||||||||||||||||||
| .sort({ reportDate: -1 }) | ||||||||||||||||||||||||||||||||||||||||||||||||
| .lean(); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| res.json({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| count: reports.length, | ||||||||||||||||||||||||||||||||||||||||||||||||
| reports, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| console.error('Error fetching patient reports:', error); | ||||||||||||||||||||||||||||||||||||||||||||||||
| res.status(500).json({ message: 'Failed to fetch reports' }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // Get doctor's patient lab reports (reports shared with doctor) | ||||||||||||||||||||||||||||||||||||||||||||||||
| export const getDoctorPatientReports = async (req, res) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const { profileId } = req.user; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const reports = await LabReport.find({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| doctorId: profileId, | ||||||||||||||||||||||||||||||||||||||||||||||||
| isSharedWithDoctor: true, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||
| .populate('patientId', 'firstName lastName dateOfBirth gender') | ||||||||||||||||||||||||||||||||||||||||||||||||
| .populate('labId', 'name') | ||||||||||||||||||||||||||||||||||||||||||||||||
| .populate('appointmentId', 'tests appointmentDate') | ||||||||||||||||||||||||||||||||||||||||||||||||
| .sort({ reportDate: -1 }) | ||||||||||||||||||||||||||||||||||||||||||||||||
| .lean(); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| res.json({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| count: reports.length, | ||||||||||||||||||||||||||||||||||||||||||||||||
| reports, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| console.error('Error fetching doctor reports:', error); | ||||||||||||||||||||||||||||||||||||||||||||||||
| res.status(500).json({ message: 'Failed to fetch reports' }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // Add doctor remarks to report | ||||||||||||||||||||||||||||||||||||||||||||||||
| export const addDoctorRemarks = async (req, res) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const { reportId } = req.params; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const { profileId } = req.user; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const { remarks } = req.body; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if (!remarks) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return res.status(400).json({ message: 'Remarks are required' }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const report = await LabReport.findById(reportId); | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (!report) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return res.status(404).json({ message: 'Report not found' }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if (report.doctorId && report.doctorId.toString() !== profileId.toString()) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return res.status(403).json({ message: 'Unauthorized to add remarks to this report' }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+113
to
+120
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Authorization bypass: Any doctor can add remarks to reports without a linked doctor. The current logic allows any doctor to add remarks if - if (report.doctorId && report.doctorId.toString() !== profileId.toString()) {
+ if (!report.doctorId || report.doctorId.toString() !== profileId.toString()) {
return res.status(403).json({ message: 'Unauthorized to add remarks to this report' });
}🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| report.doctorRemarks = { | ||||||||||||||||||||||||||||||||||||||||||||||||
| remarks, | ||||||||||||||||||||||||||||||||||||||||||||||||
| addedAt: new Date(), | ||||||||||||||||||||||||||||||||||||||||||||||||
| addedBy: profileId, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| await report.save(); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| res.json({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| message: 'Remarks added successfully', | ||||||||||||||||||||||||||||||||||||||||||||||||
| report, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| console.error('Error adding remarks:', error); | ||||||||||||||||||||||||||||||||||||||||||||||||
| res.status(500).json({ message: 'Failed to add remarks' }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // Get specific report details | ||||||||||||||||||||||||||||||||||||||||||||||||
| export const getReportDetails = async (req, res) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const { reportId } = req.params; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const { profileId, role } = req.user; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const report = await LabReport.findById(reportId) | ||||||||||||||||||||||||||||||||||||||||||||||||
| .populate('patientId', 'firstName lastName dateOfBirth gender') | ||||||||||||||||||||||||||||||||||||||||||||||||
| .populate('labId', 'name address contact') | ||||||||||||||||||||||||||||||||||||||||||||||||
| .populate('doctorId', 'firstName lastName specialization') | ||||||||||||||||||||||||||||||||||||||||||||||||
| .populate('appointmentId') | ||||||||||||||||||||||||||||||||||||||||||||||||
| .lean(); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if (!report) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return res.status(404).json({ message: 'Report not found' }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // Authorization check | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (role === 'patient' && report.patientId._id.toString() !== profileId.toString()) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return res.status(403).json({ message: 'Unauthorized' }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||
| role === 'doctor' && | ||||||||||||||||||||||||||||||||||||||||||||||||
| (!report.doctorId || report.doctorId._id.toString() !== profileId.toString()) | ||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return res.status(403).json({ message: 'Unauthorized' }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+156
to
+167
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add null checks before accessing populated fields to prevent runtime errors. If population fails or returns null (e.g., deleted patient/doctor), accessing // Authorization check
- if (role === 'patient' && report.patientId._id.toString() !== profileId.toString()) {
+ if (role === 'patient' && report.patientId?._id?.toString() !== profileId.toString()) {
return res.status(403).json({ message: 'Unauthorized' });
}
if (
role === 'doctor' &&
- (!report.doctorId || report.doctorId._id.toString() !== profileId.toString())
+ (!report.doctorId || report.doctorId._id?.toString() !== profileId.toString())
) {
return res.status(403).json({ message: 'Unauthorized' });
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| res.json({ report }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| console.error('Error fetching report details:', error); | ||||||||||||||||||||||||||||||||||||||||||||||||
| res.status(500).json({ message: 'Failed to fetch report' }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Invalid field selection -
homeCollectionAvailableandhomeCollectionFeeare not top-level fields.These fields exist only within the nested
testsarray, not at the Lab document level. ThegeneralHomeCollectionFeefield exists at the top level instead.const labs = await Lab.find(query) .select( - 'name description address contact logo ratings tests homeCollectionAvailable homeCollectionFee' + 'name description address contact logo ratings tests generalHomeCollectionFee' ) .lean();📝 Committable suggestion
🤖 Prompt for AI Agents