Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
419 changes: 419 additions & 0 deletions server/Controllers/QuickLab/labAdminController.js

Large diffs are not rendered by default.

415 changes: 415 additions & 0 deletions server/Controllers/QuickLab/labAppointmentController.js

Large diffs are not rendered by default.

79 changes: 79 additions & 0 deletions server/Controllers/QuickLab/labController.js
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();
Comment on lines +23 to +27
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Invalid field selection - homeCollectionAvailable and homeCollectionFee are not top-level fields.

These fields exist only within the nested tests array, not at the Lab document level. The generalHomeCollectionFee field 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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const labs = await Lab.find(query)
.select(
'name description address contact logo ratings tests homeCollectionAvailable homeCollectionFee'
)
.lean();
const labs = await Lab.find(query)
.select(
'name description address contact logo ratings tests generalHomeCollectionFee'
)
.lean();
🤖 Prompt for AI Agents
In server/Controllers/QuickLab/labController.js around lines 23 to 27, the
.select includes homeCollectionAvailable and homeCollectionFee which are not
top-level Lab fields; replace those two with the top-level
generalHomeCollectionFee field in the projection and, if you need the per-test
homeCollectionAvailable/homeCollectionFee values, explicitly include the tests
subdocument fields (e.g., 'tests.homeCollectionAvailable
tests.homeCollectionFee') or return the full tests array; update the .select
call accordingly so only valid top-level fields are requested.


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' });
}
};
174 changes: 174 additions & 0 deletions server/Controllers/QuickLab/labReportController.js
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Wrap JSON.parse in try-catch to handle malformed input.

If testResults contains invalid JSON, this will throw an unhandled exception and return a 500 error instead of a proper 400 validation error.

+    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,

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In server/Controllers/QuickLab/labReportController.js around lines 29 to 31, the
call to JSON.parse(testResults) can throw on malformed input; wrap the parse in
a try-catch, validate that testResults is a string before parsing, and on parse
failure return a 400 validation response (or attach a validation error to the
response flow) instead of letting an exception bubble; if testResults is missing
or empty fall back to an empty array after successful validation.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Authorization bypass: Any doctor can add remarks to reports without a linked doctor.

The current logic allows any doctor to add remarks if report.doctorId is falsy. This should deny access when the report has no associated doctor or when the IDs don't match.

-    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
In server/Controllers/QuickLab/labReportController.js around lines 113 to 120,
the current check allows any doctor to add remarks when report.doctorId is
falsy; change the condition to deny access when the report has no associated
doctor OR when the doctorId does not match the requesting profileId.
Specifically, replace the existing if to return 403 when (!report.doctorId ||
report.doctorId.toString() !== profileId.toString()), ensuring proper type-safe
comparison and a clear unauthorized message.


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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add null checks before accessing populated fields to prevent runtime errors.

If population fails or returns null (e.g., deleted patient/doctor), accessing ._id will throw. The authorization checks should handle these edge cases.

     // 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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 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' });
}
// 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' });
}
🤖 Prompt for AI Agents
In server/Controllers/QuickLab/labReportController.js around lines 156 to 167,
the authorization checks assume populated patientId and doctorId exist and
access ._id directly, which can throw if population returned null; update the
checks to first verify that report.patientId and report.doctorId are non-null
(and have an _id) before comparing to profileId, and treat missing or null
populated references as unauthorized (return 403) to avoid runtime errors.


res.json({ report });
} catch (error) {
console.error('Error fetching report details:', error);
res.status(500).json({ message: 'Failed to fetch report' });
}
};
Loading