Background
The frontend course-progress hook (hooks/useCourseProgress.ts) syncs a learner's video/course progress to:
GET /api/progress/courses — fetch all of the current user's course progress
PUT /api/progress/course/:courseId — upsert progress for one course
Neither exists. /api/progress is not mounted (app.js has no such app.use), so both calls 404:
statusCode":404,"message":"Can't find /api/progress/courses on this server!"
The frontend degrades gracefully (on 404 it falls back to localStorage), so progress works on a single device but is never persisted server-side or synced across devices, and the 404s spam server logs. The per-course endpoints GET/POST /api/courses/:id/progress exist but do not match this contract, and there is no aggregate "all my progress" endpoint.
Scope
Add a progressRoutes module mounted at /api/progress (authenticated with protect), reusing the existing src/models/CourseProgress.js model:
GET /api/progress/courses — return every CourseProgress document for the authenticated user. Response shape must match the frontend exactly:
{ "success": true, "progress": [ { "courseId": "<id>", "positionSeconds": 0, "durationSeconds": 0, "completed": false } ] }
PUT /api/progress/course/:courseId — upsert the caller's progress for that course from body { positionSeconds, durationSeconds, completed, lessonId }. Return the updated entry ({ success: true, progress: {...} }).
- Mount the router in
app.js next to the other course routes.
- Reuse existing per-course progress logic where sensible; do not duplicate the model.
Out of scope
- Frontend changes (the contract above is fixed by the client).
- Reading/book progress (
/api/books/.../progress) — separate feature.
Acceptance criteria
Background
The frontend course-progress hook (
hooks/useCourseProgress.ts) syncs a learner's video/course progress to:GET /api/progress/courses— fetch all of the current user's course progressPUT /api/progress/course/:courseId— upsert progress for one courseNeither exists.
/api/progressis not mounted (app.jshas no suchapp.use), so both calls 404:The frontend degrades gracefully (on 404 it falls back to
localStorage), so progress works on a single device but is never persisted server-side or synced across devices, and the 404s spam server logs. The per-course endpointsGET/POST /api/courses/:id/progressexist but do not match this contract, and there is no aggregate "all my progress" endpoint.Scope
Add a
progressRoutesmodule mounted at/api/progress(authenticated withprotect), reusing the existingsrc/models/CourseProgress.jsmodel:GET /api/progress/courses— return everyCourseProgressdocument for the authenticated user. Response shape must match the frontend exactly:{ "success": true, "progress": [ { "courseId": "<id>", "positionSeconds": 0, "durationSeconds": 0, "completed": false } ] }PUT /api/progress/course/:courseId— upsert the caller's progress for that course from body{ positionSeconds, durationSeconds, completed, lessonId }. Return the updated entry ({ success: true, progress: {...} }).app.jsnext to the other course routes.Out of scope
/api/books/.../progress) — separate feature.Acceptance criteria
GET /api/progress/coursesreturns the authenticated user's progress in the exact{ success, progress: [...] }shapePUT /api/progress/course/:courseIdupserts and returns the entry; only affects the caller's own progress/api/progress; both routes behindprotectCourseProgressmodel (no schema duplication)dev