Skip to content

fix: validate and deduplicate course progress - #41

Merged
udaycodespace merged 1 commit into
udaycodespace:mainfrom
Jidnyasa-P:fix/39-progress-validation
Aug 5, 2026
Merged

fix: validate and deduplicate course progress#41
udaycodespace merged 1 commit into
udaycodespace:mainfrom
Jidnyasa-P:fix/39-progress-validation

Conversation

@Jidnyasa-P

Copy link
Copy Markdown
Contributor

Description

Implements the backend changes requested in Issue #39 to validate course-section completion and prevent duplicate progress entries.

Fixes #39

ECSoC26

This contribution is submitted under ECSoC26.

Problem

The previous course-completion flow manually appended section progress to an array.

That allowed:

  • Duplicate progress entries
  • Lost updates during concurrent requests
  • Invalid section IDs
  • Progress for sections outside the selected course
  • Inconsistent handling of missing courses and unenrolled users

Implementation

Course validation

The controller now:

  • Validates that courseId and sectionId are provided
  • Rejects malformed MongoDB course IDs
  • Returns 404 when the course does not exist
  • Verifies that the requested section belongs to the course

Current section-ID compatibility

The current frontend submits a zero-based section index.

The backend continues to support that format:

0 → first section
1 → second section
2 → third section

The implementation also supports embedded section _id or id values for future compatibility.

Enrollment validation

The authenticated user must have an enrollment record for the requested course.

An unenrolled user receives a controlled 403 response.

Atomic duplicate prevention

Progress is now updated with a conditional MongoDB query and $addToSet.

The filter excludes records where the section is already complete:

{
  _id: enrollment._id,
  "progress.sectionId": {
    $ne: normalizedSectionId,
  },
}

The update uses:

{
  $addToSet: {
    progress: {
      sectionId: normalizedSectionId,
    },
  },
}

This prevents repeated and concurrent requests from creating duplicate progress entries.

Idempotent response

A newly completed section returns:

{
  "success": true,
  "alreadyCompleted": false,
  "message": "Section completed successfully"
}

A repeated request returns:

{
  "success": true,
  "alreadyCompleted": true,
  "message": "Section was already completed"
}

Tests added

The backend tests cover:

  • Successful section completion
  • Duplicate completion
  • Atomic $addToSet usage
  • Invalid MongoDB course IDs
  • Missing courses
  • Sections outside the course
  • Unenrolled students
  • Existing numeric section-index compatibility

Acceptance criteria verification

  • courseId and sectionId are validated
  • Course existence is checked
  • Section membership is checked
  • Student enrollment is checked
  • Duplicate progress entries are prevented
  • The update is atomic
  • Repeated requests return an idempotent success response
  • Existing frontend section-index behaviour remains compatible
  • Backend tests were added
  • No frontend changes are included

Verification commands

Syntax checks

node --check controllers/progressController.js
node --check routers/userRoutes.js
node --check schemas/enrolledCourseModel.js

Test suite

cd backend
npm install
npm test

Relevant code checks

grep -nE "isValidObjectId|courseId|sectionId|Section not found|Course not found" controllers/progressController.js
grep -nE "\\$addToSet|progress\\.sectionId|alreadyCompleted" controllers/progressController.js
grep -nE "completeCourseSection|completemodule|progressController" routers/userRoutes.js
grep -nE "duplicate|invalid|unenrolled|not found|\\$addToSet" tests/progress.test.js

Test evidence

Screenshot 2026-08-05 204412 image

Files changed

backend/controllers/progressController.js
backend/schemas/enrolledCourseModel.js
backend/routers/userRoutes.js
backend/package.json
backend/package-lock.json
backend/tests/progress.test.js
docs/issue-39-progress-validation.md

@udaycodespace
udaycodespace self-requested a review August 5, 2026 17:05
@udaycodespace udaycodespace added ECSoC26 Required label for a PR to be eligible for Sentinel scoring ECSoC26-L3 Difficult, auto-assigned by Sentinel — 15 points good-pr PA-awarded bonus for an exceptionally executed PR — +15 XP and removed documentation backend configuration fullstack database tests labels Aug 5, 2026
@udaycodespace

Copy link
Copy Markdown
Owner

Description

Implements the backend changes requested in Issue #39 to validate course-section completion and prevent duplicate progress entries.

Fixes #39

ECSoC26

This contribution is submitted under ECSoC26.

Problem

The previous course-completion flow manually appended section progress to an array.

That allowed:

  • Duplicate progress entries
  • Lost updates during concurrent requests
  • Invalid section IDs
  • Progress for sections outside the selected course
  • Inconsistent handling of missing courses and unenrolled users

Implementation

Course validation

The controller now:

  • Validates that courseId and sectionId are provided
  • Rejects malformed MongoDB course IDs
  • Returns 404 when the course does not exist
  • Verifies that the requested section belongs to the course

Current section-ID compatibility

The current frontend submits a zero-based section index.

The backend continues to support that format:

0 → first section
1 → second section
2 → third section

The implementation also supports embedded section _id or id values for future compatibility.

Enrollment validation

The authenticated user must have an enrollment record for the requested course.

An unenrolled user receives a controlled 403 response.

Atomic duplicate prevention

Progress is now updated with a conditional MongoDB query and $addToSet.

The filter excludes records where the section is already complete:

{
  _id: enrollment._id,
  "progress.sectionId": {
    $ne: normalizedSectionId,
  },
}

The update uses:

{
  $addToSet: {
    progress: {
      sectionId: normalizedSectionId,
    },
  },
}

This prevents repeated and concurrent requests from creating duplicate progress entries.

Idempotent response

A newly completed section returns:

{
  "success": true,
  "alreadyCompleted": false,
  "message": "Section completed successfully"
}

A repeated request returns:

{
  "success": true,
  "alreadyCompleted": true,
  "message": "Section was already completed"
}

Tests added

The backend tests cover:

  • Successful section completion
  • Duplicate completion
  • Atomic $addToSet usage
  • Invalid MongoDB course IDs
  • Missing courses
  • Sections outside the course
  • Unenrolled students
  • Existing numeric section-index compatibility

Acceptance criteria verification

  • courseId and sectionId are validated
  • Course existence is checked
  • Section membership is checked
  • Student enrollment is checked
  • Duplicate progress entries are prevented
  • The update is atomic
  • Repeated requests return an idempotent success response
  • Existing frontend section-index behaviour remains compatible
  • Backend tests were added
  • No frontend changes are included

Verification commands

Syntax checks

node --check controllers/progressController.js
node --check routers/userRoutes.js
node --check schemas/enrolledCourseModel.js

Test suite

cd backend
npm install
npm test

Relevant code checks

grep -nE "isValidObjectId|courseId|sectionId|Section not found|Course not found" controllers/progressController.js
grep -nE "\\$addToSet|progress\\.sectionId|alreadyCompleted" controllers/progressController.js
grep -nE "completeCourseSection|completemodule|progressController" routers/userRoutes.js
grep -nE "duplicate|invalid|unenrolled|not found|\\$addToSet" tests/progress.test.js

Test evidence

Screenshot 2026-08-05 204412 image

Files changed

backend/controllers/progressController.js
backend/schemas/enrolledCourseModel.js
backend/routers/userRoutes.js
backend/package.json
backend/package-lock.json
backend/tests/progress.test.js
docs/issue-39-progress-validation.md

LGTM!

@udaycodespace
udaycodespace merged commit d2f7c90 into udaycodespace:main Aug 5, 2026
2 of 14 checks passed
@ecsoc-sentinel ecsoc-sentinel Bot added ECSoC26-L3 Difficult, auto-assigned by Sentinel — 15 points and removed ECSoC26-L3 Difficult, auto-assigned by Sentinel — 15 points labels Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ECSoC26-L3 Difficult, auto-assigned by Sentinel — 15 points ECSoC26 Required label for a PR to be eligible for Sentinel scoring good-pr PA-awarded bonus for an exceptionally executed PR — +15 XP

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Backend]: Prevent duplicate progress entries and validate completed sections

2 participants