You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
deleteUserController and deleteCourseController in backend/controllers/adminController.js are both a single findByIdAndDelete with nothing after it. Everything that references the deleted row stays in the database, and every uploaded video that belonged to a deleted course stays on disk forever.
constuser=awaituserSchema.findByIdAndDelete(userid);// enrolments, payments, reviews, bookmarks, logs all surviveconstcourse=awaitcourseSchema.findByIdAndDelete(courseid);// plus every section .mp4 in backend/uploads
The teacher-facing path already does this properly. courseDeletionController.js calls removeCourseVideoFiles(deletedCourse) and is already written to accept an admin (["teacher", "admin"].includes(role)), but adminRoutes.js wires DELETE /api/admin/deletecourse/:courseid to the weaker duplicate in adminController.js instead. So the same action cleans up or does not depending on which screen it was triggered from.
Expected result
Deleting a user removes, in one operation:
their enrolments (enrolledCourses)
their payment records (coursePayments)
their reviews (courseReviews) and the affected courses' rating summaries
their bookmarks (courseBookmarks)
courses they authored, if they were a teacher, including those courses' videos
Deleting a course removes its enrolments, payments, reviews and bookmarks, and deletes its section videos from backend/uploads, exactly like the teacher route does.
The response should report what was removed so the admin dashboard can show it.
Actual result
GET /api/admin/enrolled-courses populates userId/courseId on rows whose referenced document is gone, so the dashboard renders rows with a blank student and a blank course.
GET /api/admin/payments does the same, and the payment history of deleted users is still fully readable.
courseReviews keeps rows for deleted courses, and getSummary() keeps counting them, so a course's average rating is computed from reviews by accounts that no longer exist.
backend/uploads grows without limit. There are already orphaned .mp4 files checked into the repo.
Sign in as admin, create a teacher, have them publish a course with a video section, and enrol a student in it.
DELETE /api/admin/deletecourse/:courseid.
ls backend/uploads — the section video is still there.
db.enrolledcourses.find({ courseId: <id> }) — the enrolment is still there.
db.coursereviews.find({ courseId: <id> }) — the reviews are still there.
Open the admin "Enrolled courses" tab — a row renders with an empty course title.
Repeat with DELETE /api/admin/deleteuser/:userid; the payments and enrolments for that user survive.
Notes
The two admin controllers should not stay as a second implementation of deletion. Routing /api/admin/deletecourse at courseDeletionController gets the file cleanup for free, and the cascade itself belongs in a shared utility that both entry points call.
courseModel.userId is a String while enrolledCourseModel.userId is an ObjectId, so a cascade has to query authored courses by string and enrolments by ObjectId. Worth handling explicitly rather than assuming one shape.
A deleted enrolment should also decrement course.enrolled, otherwise the learner count on the catalogue card drifts upward permanently.
Summary
deleteUserControlleranddeleteCourseControllerinbackend/controllers/adminController.jsare both a singlefindByIdAndDeletewith nothing after it. Everything that references the deleted row stays in the database, and every uploaded video that belonged to a deleted course stays on disk forever.The teacher-facing path already does this properly.
courseDeletionController.jscallsremoveCourseVideoFiles(deletedCourse)and is already written to accept an admin (["teacher", "admin"].includes(role)), butadminRoutes.jswiresDELETE /api/admin/deletecourse/:courseidto the weaker duplicate inadminController.jsinstead. So the same action cleans up or does not depending on which screen it was triggered from.Expected result
Deleting a user removes, in one operation:
enrolledCourses)coursePayments)courseReviews) and the affected courses' rating summariescourseBookmarks)Deleting a course removes its enrolments, payments, reviews and bookmarks, and deletes its section videos from
backend/uploads, exactly like the teacher route does.The response should report what was removed so the admin dashboard can show it.
Actual result
GET /api/admin/enrolled-coursespopulatesuserId/courseIdon rows whose referenced document is gone, so the dashboard renders rows with a blank student and a blank course.GET /api/admin/paymentsdoes the same, and the payment history of deleted users is still fully readable.courseReviewskeeps rows for deleted courses, andgetSummary()keeps counting them, so a course's average rating is computed from reviews by accounts that no longer exist.backend/uploadsgrows without limit. There are already orphaned.mp4files checked into the repo.getEnrolledCoursesControllerhas to defensively skip enrolments whose course is missing (added in [Bug]: getallcoursesuser runs N+1 queries and returns null rows that crash the enrolled-courses page #65) purely because deletion does not clean up.Steps to reproduce
DELETE /api/admin/deletecourse/:courseid.ls backend/uploads— the section video is still there.db.enrolledcourses.find({ courseId: <id> })— the enrolment is still there.db.coursereviews.find({ courseId: <id> })— the reviews are still there.DELETE /api/admin/deleteuser/:userid; the payments and enrolments for that user survive.Notes
/api/admin/deletecourseatcourseDeletionControllergets the file cleanup for free, and the cascade itself belongs in a shared utility that both entry points call.courseModel.userIdis aStringwhileenrolledCourseModel.userIdis anObjectId, so a cascade has to query authored courses by string and enrolments by ObjectId. Worth handling explicitly rather than assuming one shape.course.enrolled, otherwise the learner count on the catalogue card drifts upward permanently.