Course System Codemap
Architecture Overview
┌─────────────────────────────────────────────────────────────────┐
│ SERVER (NestJS) — single source of truth │
│ Route prefix: /course │
│ 4 controllers → 10 services → 12 entities → 26 DTOs │
├─────────────────────────────────────────────────────────────────┤
│ FRONTENDS — each defines own types, consumes HTTP API │
│ instructor/ editor + dashboard │
│ admin/ management + analytics │
│ student/ player + progress │
│ public/ marketplace + search │
│ landing-page/ marketing │
└─────────────────────────────────────────────────────────────────┘
Cross-package rule: No type imports between frontend packages. Each defines local interfaces.
1. Server: Course Module
Module: server/src/course/course.module.ts
1.1 Hierarchy
Course
├── CourseSection (course_sections)
│ └ ── CourseSubSection (course_subsections)
│ └── CourseItem (course_items) — VIDEO, FILE, TEXT, etc.
├── Level (levels)
├── CourseManager (course_managers) — junction: instructor → course
├── Requirement (course requirements with icon+text)
├── Inclusion (course inclusions/benefits with icon+text)
└── Audience (target audience with icon+text)
1.2 Controllers
File: server/src/course/course.controller.ts — Route prefix /course
| Method | Endpoint | Service | Description |
|---|---|---|---|
POST | /course | CourseWriteService.create | Create course |
GET | /course | CourseReadService.findAll | Paginated list, view-based serialization |
GET | /course/:id | CourseReadService.findOne | By ID, ?view=admin|dashboard|public |
GET | /course/slug/:slug | CourseReadService.findOne | By slug, public access allowed |
PUT | /course/:id | CourseWriteService.update | Update course |
DELETE | /course/:id | CourseWriteService.remove | Hard delete |
PUT | /course/:id/visibility | CourseModerationService.updateCourseVisibility | Admin only — isFeatured, isHidden |
GET | /course/:id/player | CoursePlayerService.getCourseForPlayer | Student player with section locking |
GET | /course/:id/curriculum | CourseReadService.getNormalizedCurriculum | Sections → subsections → items |
GET | /course/:id/performance | CourseModerationService.getCoursePerformance | Analytics metrics |
GET | /course/summaries/:id | CourseModerationService.getCourseSummary | Instructor summaries |
GET | /course/:id/statistics | CourseModerationService.getInstructorStatistics | Instructor stats |
GET | /course/levels | CourseReadService.getAllLevels | All levels |
POST | /course/levels | CourseReadService.createLevel | Admin only |
Section endpoints (nested under /course):
| Method | Endpoint | Service |
|---|---|---|
GET | /course/section?courseId= | CourseSectionService.findAll |
GET | /course/section/:sectionId | CourseSectionService.findOne |
POST | /course/section | CourseSectionService.create |
PUT | /course/section/:sectionId | CourseSectionService.update |
DELETE | /course/section/:sectionId | CourseSectionService.remove |
Subsection endpoints:
| Method | Endpoint | Service |
|---|---|---|
GET | /course/subsection?sectionId= | CourseSubSectionService.findAll |
GET | /course/subsection/:subsectionId | CourseSubSectionService.findOne |
POST | /course/subsection | CourseSubSectionService.create |
PUT | /course/subsection/:subsectionId | CourseSubSectionService.update |
DELETE | /course/subsection/:subsectionId | CourseSubSectionService.remove |
POST | /course/subsection/batch-update-order | CourseSubSectionService.batchUpdateOrder |
Item endpoints:
| Method | Endpoint | Service |
|---|---|---|
GET | /course/item?subsectionId= | CourseItemService.findAll |
GET | /course/item/:courseItemId | CourseItemService.findOne |
POST | /course/item | CourseItemService.create |
PUT | /course/item/:courseItemId | CourseItemService.update |
DELETE | /course/item/:courseItemId | CourseItemService.remove |
GET | /course/download/:courseItemId | CourseItemService.downloadFile |
POST | /course/item/:courseItemId/video/upload | CourseItemService.initVideoUpload (Bunny TUS) |
POST | /course/item/:courseItemId/video/refresh | CourseItemService.refreshVideoUpload |
GET | /course/item/:courseItemId/video | CourseItemService.getVideoPlayback |
Property endpoints (Requirements, Inclusions, Audiences):
| Method | Endpoint |
|---|---|
GET | /course/:id/:type |
GET | /course/:courseId/:type/:propId |
POST | /course/:courseId/:type |
PUT | /course/:courseId/:type/replace |
PATCH | /course/:courseId/properties/:type/:propId |
DELETE | /course/:courseId/:type/:propId |
Batch controller (batch-update.controller.ts):
| Method | Endpoint |
|---|---|
PATCH | /course/batch/:courseId/sections |
PATCH | /course/batch/:sectionId/subsections |
PATCH | /course/batch/:subsectionId/items |
Course Manager controller (course-manager.controller.ts):
| Method | Endpoint |
|---|---|
POST | /course-managers |
GET | /course-managers/:id |
GET | /course-managers/course/:courseId |
GET | /course-managers/instructor/:instructorId |
PUT | /course-managers/:id |
DELETE | /course-managers/:id |
1.3 Services
| Service | File | Key Methods |
|---|---|---|
CourseReadService | services/course-read.service.ts | findAll (paginated, nestjs-paginate), findOne (by id/slug with view), getAllLevels, createLevel, getNormalizedCurriculum |
CourseWriteService | services/course-write.service.ts | create, update, remove — emits COURSE_UPDATED, COURSE_REMOVED events |
CourseModerationService | services/course-moderation.service.ts | getCourseSummary, getInstructorStatistics, getCoursePerformance, updateCourseVisibility |
CoursePlayerService | services/course-player.service.ts | getCourseForPlayer — assembles player data with section locking, progress, pre-signed video URLs, enrollment validation |
CourseResponseService | services/course-response.service.ts | toResponseDto — maps entities to response DTOs with serialization groups |
CourseSectionService | course-section.service.ts | CRUD for sections, batch order update |
CourseSubSectionService | course-subsection.service.ts | CRUD for subsections, batch order |
CourseItemService | course-item.service.ts | CRUD for items, Bunny Stream video upload init/refresh/playback, file downloads |
CourseManagerService | course-manager.service.ts | CRUD for course-instructor assignments |
ProgressService | progress.service.ts | trackProgress, getProgress, updateVideoProgress, markVideoCompleted, getCourseCompletionStatus |
TimeCalculationService | (in progress module) | Video time calculations |
1.4 Entities
Course (entities/course.entity.ts) — Table: courses
| Field | Type | Notes |
|---|---|---|
id | UUID PK | |
title | string | Unique |
slug | string | Unique, nullable |
subtitle | string | |
category | Relation→Category | |
subcategory | Relation→Category | |
level | Relation→Level | Eager |
language | string | |
subtitleLanguage | string | |
publishStatus | enum | DRAFT / PUBLISHED / ARCHIVED |
description | string | |
thumbnail | Relation→File | OneToOne |
introductoryVideo | Relation→File | OneToOne |
priceInCents | int | |
currency | enum | USD default |
isFree | boolean | |
saleActive | boolean | |
salePercentage | int | |
saleStart / saleEnd | Date | |
approved | boolean | |
isFeatured | boolean | |
isHidden | boolean | |
gigActive | boolean | OpenSearch indexed flag |
duration | int | |
durationMeasure | enum | MINUTES/HOURS/DAYS/WEEKS/MONTHS/YEARS |
instructor | Relation→Instructor | Eager |
managers | Relation→CourseManager[] | OneToMany |
sections | Relation→CourseSection[] | OneToMany, cascade |
requirements | Relation→Requirement[] | OneToMany, cascade |
inclusions | Relation→Inclusion[] | OneToMany, cascade |
audiences | Relation→Audience[] | OneToMany, cascade |
welcomeMessage | string | |
completionMessage | string | |
shareUrl | string | |
publishedAt | Date | |
approvedAt | Date | |
timing | string | |
lessonNote | string |
CourseSection (entities/sections.entity.ts) — Table: course_sections
| Field | Type |
|---|---|
id | UUID PK |
title | string |
description | string |
priceInCents | int (for à-la-carte) |
saleActive / salePercentage | |
isActive | boolean |
order | int |
course | Relation→Course |
subsections | Relation→CourseSubSection[] |
CourseSubSection (entities/subsections.entity.ts) — Table: course_subsections
| Field | Type |
|---|---|
id | UUID PK |
title | string |
description | string |
isActive | boolean |
order | int |
section | Relation→CourseSection |
items | Relation→CourseItem[] |
CourseItem (entities/course_item.entity.ts) — Table: course_items
| Field | Type |
|---|---|
id | UUID PK |
title | string |
type | enum: VIDEO / FILE / CAPTION / LECTURE_NOTE / TEXT / DESCRIPTION / ASSIGNMENT / QUIZ |
size | int (for files/video) |
duration | int (minutes for video) |
content | text (HTML for text-based types) |
fileUrl | string |
bunnyVideoId | string |
bunnyVideoStatus | string |
isActive | boolean |
order | int |
subSection | Relation→CourseSubSection |
Level (entities/level.entity.ts) — Table: levels — id, text
CourseManager (entities/manager.entity.ts) — Junction: course ↔ instructor
1.5 Enums
| Enum | File | Values |
|---|---|---|
PublishStatus | enums/publish-status.enum.ts | DRAFT, PUBLISHED, ARCHIVED |
CourseItemType | enums/course-item.enum.ts | VIDEO, FILE, CAPTION, LECTURE_NOTE, TEXT, DESCRIPTION, ASSIGNMENT, QUIZ |
ItemCompletionStatus | enums/item-status.enum.ts | NOT_STARTED, IN_PROGRESS, NOT_AVAILABLE, COMPLETED |
DurationMeasure | enums/duration-measure.enum.ts | MINUTES, HOURS, DAYS, WEEKS, MONTHS, YEARS |