Skip to content

feat(courses): add learner progress snapshot API (closes #169)#269

Merged
Nabeelahh merged 2 commits into
BlockDash-Studios:mainfrom
otobongdev:feat/169-course-progress-snapshot
Jun 30, 2026
Merged

feat(courses): add learner progress snapshot API (closes #169)#269
Nabeelahh merged 2 commits into
BlockDash-Studios:mainfrom
otobongdev:feat/169-course-progress-snapshot

Conversation

@otobongdev

Copy link
Copy Markdown
Contributor

Summary

Adds a learner-facing course progress snapshot API under the courses feature (BackendAcademy/src/courses/). Consumers can fetch an aggregated snapshot of a learner's progress across every course they have touched, or drill into a single course. Closes #169.

What's new

A new ProgressModule mounted at BackendAcademy/src/courses/progress/:

  • progress.service.ts – in-memory learner → course progress store. Hydrates course metadata via the existing CourseService, derives per-course completionPercent using a documented weighted 70/30 lesson/task split, and returns a sorted IProgressSnapshot (touched courses first, untouched courses pushed to the end).
  • progress.controller.ts – REST endpoints mounted under /courses/progress, all with ParseUUIDPipe for ids.
  • progress.module.ts – registered in src/app.module.ts and re-exported from src/courses/index.ts.
  • interfaces/progress-snapshot.interface.tsIProgressSnapshot, ICourseSnapshot, IOverallSnapshot, and the CourseProgressStatus enum.
  • dto/register-course-progress.dto.ts – seed enrollment with totalLessons / totalTasks.
  • dto/record-completion.dto.tsRecordLessonCompletionDto and RecordTaskCompletionDto.
  • progress.service.spec.ts21 unit tests, all green.

Endpoints

Method Path Purpose
GET /courses/progress/snapshot/:userId Full snapshot (overall + per-course)
GET /courses/progress/snapshot/:userId/course/:courseId Single-course snapshot (404 if no progress)
POST /courses/progress/:userId/courses Register enrollment + optional totals
PUT /courses/progress/:userId/courses/:courseId/lessons Record lesson completion (idempotent)
PUT /courses/progress/:userId/courses/:courseId/tasks Record task completion (idempotent)
DELETE /courses/progress/:userId Reset a learner (admin / tests)

Snapshot response shape

{
  "userId": "...",
  "generatedAt": "2026-…",
  "overall": {
    "totalXp": 60,
    "coursesCompleted": 1,
    "coursesInProgress": 1,
    "lessonsCompleted": 2,
    "tasksCompleted": 1,
    "lastActiveAt": "2026-…"
  },
  "courses": [
    {
      "courseId": "...",
      "title": "Rust Basics",
      "level": "beginner",
      "learningPathId": "path-rust-basics",
      "status": "completed",
      "completionPercent": 100,
      "lessonsCompleted": 1, "totalLessons": 1,
      "tasksCompleted": 1, "totalTasks": 1,
      "xpEarned": 50, "xpAvailable": 200,
      "startedAt": "...",
      "lastActivityAt": "...",
      "completedAt": "...",
      "firstCompletedLessonId": "lesson-1"
    }
  ]
}

Design notes

  • Self-registration: the completion endpoints auto-register the (user, course) pair so callers don't need two round-trips.
  • Idempotency: re-recording the same lesson/task is a no-op for counters/XP but always refreshes lastActivityAt.
  • Completion percent: 70% lessons / 30% tasks weighted. When only one of the two totals is known the math collapses to a pure ratio for that axis. Reports 0 (not NaN) when neither total is registered.
  • Sort: untouched courses are pushed to the end so consumers see active work at the top.
  • Course deletion safety: getSnapshot skips progress rows whose underlying course record has been removed.

Tests (progress.service.spec.ts, 21 cases, all green)

  • empty snapshot for unknown learner
  • registerCourse NotFoundException for missing course
  • register storage + totals placeholder
  • re-register preserves completions; explicit values overwrite
  • omitting a total on re-register keeps the prior value
  • lesson completion idempotency
  • task completion XP accumulation
  • auto-registration on first completion
  • NotFoundException on completion for unknown course
  • status transitions NOT_STARTED → IN_PROGRESS → COMPLETED
  • partial completion (20 %)
  • NOT_STARTED with only totals registered
  • 0 % completion when no totals registered
  • overall stats aggregation across multiple courses
  • deleted-course progress is skipped
  • getCourseSnapshot returns null when the learner never touched the course
  • learner isolation
  • resetLearner clears the state
  • xpAvailable reflects course.xpReward
  • sort order pushes untouched courses to the end
  • firstCompletedLessonId hint from prior completions + null when none

What's NOT in this PR

  • No persistent storage (intentionally in-memory to stay aligned with the rest of the courses feature today). A future PR can swap the in-memory maps for a repository without changing the public surface.
  • The pre-existing src/social/social.service.ts syntax error exists on main and is unchanged by this PR.

Closes #169

Adds a ProgressModule under the courses feature that exposes aggregated
snapshot data for a learner across every course they have touched,
satisfying issue BlockDash-Studios#169.

Endpoints (all under /courses/progress):
- GET    snapshot/:userId                       full snapshot per learner
- GET    snapshot/:userId/course/:courseId       single-course snapshot
- POST   :userId/courses                        register enrollment + totals
- PUT    :userId/courses/:courseId/lessons       record a lesson completion
- PUT    :userId/courses/:courseId/tasks         record a task completion
- DELETE :userId                                reset learner progress

The ProgressService keeps an in-memory learner -> course[Set/Map] store
and hydrates course metadata via CourseService. Completion percentage
uses a weighted 70/30 lesson/task split and the snapshot sorts touched
courses first with active work at the top.

21 unit tests cover empty snapshots, idempotent completions,
auto-registration, status transitions, learner isolation, sort order,
deleted-course handling, the firstCompletedLessonId hint, and
NotFoundException paths.

Closes BlockDash-Studios#169
@drips-wave

drips-wave Bot commented Jun 29, 2026

Copy link
Copy Markdown

@otobongdev Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Nabeelahh Nabeelahh merged commit 4bb6267 into BlockDash-Studios:main Jun 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add course progress snapshot API

2 participants