MAS LMS — Courses, Batches, Modules & Enrollment¶
This document describes the education / learning domain of the Mr. Mentor backend: the public course catalog, the marketing-driven "MAS course" cards (cohorts, accelerators, capsules, deep-tech), the structured curriculum model (Course → Module → Class), AI-authored Course Plans, Batches (with attached Mr Learn courses and Mr Test exams), student Enrollment, the paid enrollment / payment path, mentee provisioning, announcements, attendance, and the college directory. It is the connective tissue between the public website, the admin LMS builder, and the enrolled-student dashboard.
Status: documented from source on this branch.
Overview¶
The LMS domain spans three loosely-coupled layers that all live in mr-mentor-backend:
- Public catalog / marketing layer — two parallel course models:
Course(coursestable) — the structured course used inside the LMS: title, description, instructor, level, price, a roadmap, and childModule/Classrows.MasCourse(mas_coursestable, also called "NewCourse") — marketing cards rendered on the public website, grouped into four families: placement cohorts, accelerators, capsules, and deep-tech. These carry a free-formmetadataJSON blob (price labels, badges, icons) and are Redis-cached for the website.- Authoring layer (admin) — admins build structured
Coursecurricula (modules, classes, quizzes, assignments), generate AI Course Plans (a blueprint → per-lecture AI classroom generation pipeline), and assemble Batches that pin a course, a roadmap, attached external Mr Learn courses, and Mr Test exams. - Enrollment / delivery layer — students reach a course either by a paid enrollment (Razorpay, via the
Applicationentity) or by an access grant / provisioning (admin tooling). Enrolled students see their roadmap, attached courses, exams, announcements, and attendance.
Personas / roles¶
| Persona | Role enum | What they do here |
|---|---|---|
| Public visitor | (none) | Browses /api/courses/* catalog and MasCourse marketing cards |
| Student / Mentee | USER |
Enrolls (paid or granted), views own courses, roadmap, saved list, course plans |
| Admin | ADMIN |
CRUD courses/modules/batches, builds course plans, provisions mentees, attaches Mr Learn/Mr Test |
| Superadmin | SUPERADMIN |
Superset of admin |
| Community Manager | COMMUNITY_MANAGER |
Marks attendance, schedules batch meetings, bypasses isEnrolled |
| Super Mentor | (assignment-based) | Views assigned batches, student attendance |
| Batch Lead | batch_lead |
Owns a batch, adds roadmap notes |
The enrollment "gate" is enforced by
Applicationstatus, not by theEnrollmentrow. See Edge cases.
Key concepts & entities¶
| Concept | Entity (file) | Table | Notes |
|---|---|---|---|
| Structured course | Course (src/entities/Course.ts) |
courses |
Has roadmapItems JSON, level, price, status, child modules |
| Marketing course card | MasCourse (src/entities/NewCourse.ts) |
mas_courses |
Typed (MasCourseType), metadata JSON, slug, displayOrder |
| Curriculum module | Module (src/entities/Module.ts) |
course_modules |
Belongs to a Course; has classes/quizzes/assignments; moduleOrder |
| Class / session | Class (src/entities/Class.ts) |
classes |
Belongs to a Module; ClassType live/recorded/hybrid; scheduled date |
| Student enrollment | Enrollment (src/entities/Enrollment.ts) |
enrollments |
userId+courseId; progress, EnrollmentStatus |
| AI course plan | CoursePlan (src/entities/CoursePlan.ts) |
course_plans |
Blueprint JSON; CoursePlanStatus; links to a Course |
| AI course plan lecture | CoursePlanLecture (src/entities/CoursePlanLecture.ts) |
course_plan_lectures |
One per lecture; LectureStatus; links to an AiClassroom |
| Saved / "My List" course | SavedCourse (src/entities/SavedCourse.ts) |
saved_courses |
courseType = course_plan or ai_classroom |
| External free course | ExternalCourse (src/entities/ExternalCourse.ts) |
(none — static const array) | Hard-coded MrLearn courses; not a DB table |
| Per-student course audit | CourseAssignmentHistory (src/entities/CourseAssignmentHistory.ts) |
course_assignment_history |
Append-only log of courseId changes per application |
| Batch | Batch (src/entities/Batch.ts) |
batches |
Pins a courseId, pricing, document rules, roadmap notes, BL/CM/SuperMentor |
| Batch meeting | BatchMeeting (src/entities/BatchMeeting.ts) |
batch_meetings |
Live sessions scheduled by BL/CM |
| Batch → Mr Learn course | BatchMrLearnCourse (src/entities/BatchMrLearnCourse.ts) |
batch_mrlearn_courses |
Attaches a Graphy course to a roadmap item |
| Batch → Mr Test exam | BatchMrTestExam (src/entities/BatchMrTestExam.ts) |
batch_mrtest_exams |
Attaches an EzExam exam, with optional prerequisite-course gate |
| Announcement | Announcement (src/entities/Announcement.ts) |
announcements |
Global or per-course; AnnouncementType |
| Attendance | Attendance (src/entities/Attendance.ts) |
attendance |
Per studentId+date; AttendanceStatus |
| College | College (src/entities/College.ts) |
colleges |
Directory used by signup/forms |
Status enums¶
CourseStatus(Course):DRAFT·PUBLISHED·ARCHIVEDCourseLevel:BEGINNER·INTERMEDIATE·ADVANCED·ALL_LEVELSMasCourseType(MasCourse):PLACEMENT_COHORT·CAPSULE·ACCELERATOR·DEEP_TECHMasCourseStatus:DRAFT·PUBLISHED·ARCHIVED·SOLD_OUT·COMING_SOONEnrollmentStatus(Enrollment):NOT_STARTED·IN_PROGRESS·COMPLETEDCoursePlanStatus(CoursePlan):DRAFT·APPROVED·GENERATING·PAUSED·COMPLETED·FAILEDLectureStatus(CoursePlanLecture):PENDING·QUEUED·GENERATING·COMPLETED·FAILEDBatchStatus(Batch):UPCOMING·ACTIVE·COMPLETED·CANCELLEDBatchMeetingStatus:SCHEDULED·COMPLETED·CANCELLEDAttendanceStatus:PRESENT·ABSENT·LATE·EXCUSEDAnnouncementType:INFO·SUCCESS·WARNING·ERROR
Note on scope drift: there is no
CourseAccessGrantentity orCourseAccessService/courseAccesscontroller in this branch. "Access grant" in practice is implemented through theApplicationlifecycle (paid or admin-provisioned ENROLLED state) and the idempotentEnrollmentrow. This is called out explicitly so future readers don't search for a service that doesn't exist.
Architecture¶
flowchart TD
subgraph Clients["Clients"]
WEB["mas-website-live (public site)"]
ADM["mr-mentor-frontend (admin LMS)"]
STU["Student dashboard"]
end
subgraph Routes["Express routes"]
PUB["/api/courses (public + slug + cohorts)"]
CR["/api (course CRUD + student courses)"]
CE["/api/course-enrollment (payments)"]
NC["/api/admin/new-courses (MasCourse)"]
AM["/api/admin/mas/* (batches, modules, course-plans)"]
SR["/api/student/* (plans, saved, roadmap)"]
CM["/api/cm (attendance, batch meetings)"]
COL["/api/colleges"]
end
subgraph Services["Services"]
CS["CourseService"]
CES["CourseEnrollmentService"]
NCS["MasCourseService"]
MS["ModuleService"]
BS["BatchService"]
COLS["CollegeService"]
DSS["DemoSeedService"]
end
subgraph Data["PostgreSQL (TypeORM)"]
DB[("courses, mas_courses, course_modules, classes, enrollments, course_plans, course_plan_lectures, batches, batch_mrlearn_courses, batch_mrtest_exams, attendance, announcements, colleges, applications")]
end
subgraph External["External systems"]
RZP["Razorpay (orders + invoices)"]
REDIS["Redis cache + BullMQ"]
QUEUE["coursePlanQueue + emailQueue"]
GRAPHY["Mr Learn / Graphy"]
EZ["Mr Test / EzExam"]
end
WEB --> PUB
WEB --> NC
ADM --> AM
ADM --> CR
STU --> CR
STU --> CE
STU --> SR
CM --> CM
PUB --> NCS
PUB --> REDIS
CR --> CS
CR --> MS
CE --> CES
NC --> NCS
AM --> BS
AM --> CS
AM --> MS
SR --> CS
COL --> COLS
CS --> DB
CES --> DB
NCS --> DB
MS --> DB
BS --> DB
COLS --> DB
DSS --> DB
CES --> RZP
CES --> QUEUE
AM --> QUEUE
BS --> GRAPHY
BS --> EZ
NCS --> REDIS
Data model¶
erDiagram
COURSE ||--o{ MODULE : "has"
MODULE ||--o{ CLASS_SESSION : "has"
COURSE ||--o{ ENROLLMENT : "enrolled via"
USER ||--o{ ENROLLMENT : "enrolls"
COURSE ||--o{ COURSE_PLAN : "linked to"
COURSE_PLAN ||--o{ COURSE_PLAN_LECTURE : "contains"
COURSE_PLAN_LECTURE }o--|| AI_CLASSROOM : "generated as"
BATCH }o--|| COURSE : "pins default course"
BATCH ||--o{ BATCH_MEETING : "schedules"
BATCH ||--o{ BATCH_MRLEARN_COURSE : "attaches"
BATCH ||--o{ BATCH_MRTEST_EXAM : "attaches"
USER ||--o{ APPLICATION : "applies"
BATCH ||--o{ APPLICATION : "receives"
APPLICATION ||--o{ COURSE_ASSIGNMENT_HISTORY : "audited by"
USER ||--o{ SAVED_COURSE : "saves"
USER ||--o{ ATTENDANCE : "attends"
COURSE {
uuid id PK
string title
string instructor
decimal price
enum level
enum status
json roadmapItems
int totalModules
}
MODULE {
uuid id PK
uuid courseId FK
string title
int moduleOrder
}
CLASS_SESSION {
uuid id PK
uuid moduleId FK
enum type
timestamp scheduledDate
string recordingUrl
}
ENROLLMENT {
uuid id PK
uuid userId FK
uuid courseId FK
decimal progress
enum status
json moduleProgress
}
COURSE_PLAN {
uuid id PK
uuid courseId FK
uuid createdById FK
enum status
jsonb blueprint
int totalLectures
int generatedLectures
}
COURSE_PLAN_LECTURE {
uuid id PK
uuid coursePlanId FK
uuid aiClassroomId FK
enum status
int globalOrder
}
BATCH {
uuid id PK
string code UK
uuid courseId FK
enum status
decimal enrollmentFee
boolean requiresDocuments
simple-array assignedModuleIds
}
BATCH_MRLEARN_COURSE {
uuid id PK
uuid batchId FK
string roadmapItemId
string mrLearnCourseId
int sequence
}
BATCH_MRTEST_EXAM {
uuid id PK
uuid batchId FK
string mrTestExamId
string prerequisiteCourseId
smallint prerequisiteThreshold
}
APPLICATION {
uuid id PK
uuid userId FK
uuid batchId FK
uuid courseId FK
enum status
boolean paymentVerified
}
SAVED_COURSE {
uuid id PK
uuid userId FK
string courseType
uuid courseId
}
ATTENDANCE {
uuid id PK
uuid studentId FK
date date
enum status
}
MAS_COURSE {
uuid id PK
enum type
string slug UK
enum status
json metadata
int displayOrder
}
MAS_COURSE(marketing cards) is intentionally not joined toCOURSE— they are two independent models.ENROLLMENTjoinsUSER↔COURSE, while access-control joinsUSER↔BATCHviaAPPLICATION.
API surface¶
Mount prefixes from src/routes/index.ts. auth = authMiddleware (JWT → req.user); admin = adminMiddleware (ADMIN/SUPERADMIN). Routes in course.routes.ts enforce admin inside the controller (req.user.role !== ADMIN), not via middleware.
Public catalog — createPublicCoursesRoutes mounted at /api/courses (src/routes/publicCourses.routes.ts)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/courses/cohorts |
public | Placement cohort MasCourse cards (Redis-cached 10 min) |
| GET | /api/courses/accelerators |
public | Accelerator cards |
| GET | /api/courses/deeptech |
public | Deep-tech cards |
| GET | /api/courses/capsules |
public | Capsule cards, grouped by segment |
| GET | /api/courses/all |
public | All published MasCourse rows |
| GET | /api/courses/slug/:slug |
public | Single published card by slug |
| POST | /api/courses/cache/clear |
public* | Flush courses:* Redis keys (admin-intended; not auth-gated in code) |
Structured courses — CourseRoutes mounted at /api (src/routes/course.routes.ts)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/courses |
public | List Course rows (defaults to PUBLISHED) with filters |
| GET | /api/courses/free |
public | Published courses with price = 0 |
| GET | /api/courses/:id |
public | Single course by id |
| GET | /api/student/courses |
auth | Caller's enrolled courses (from Enrollment) |
| GET | /api/student/courses/stats |
auth | Enrollment counts + avg progress |
| POST | /api/student/courses/:id/enroll |
auth | Self-enroll into a course (enrollInCourse) |
| PUT | /api/student/courses/:id/progress |
auth | Update progress / completed modules |
| POST | /api/admin/courses |
auth (role=ADMIN in handler) | Create course (roadmap validated) |
| PUT | /api/admin/courses/:id |
auth (role=ADMIN in handler) | Update course |
| DELETE | /api/admin/courses/:id |
auth (role=ADMIN in handler) | Delete course (cascades enrollments, nulls AiClassroom/Batch refs) |
Paid enrollment — CourseEnrollmentRoutes mounted at /api (src/routes/courseEnrollment.routes.ts)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| POST | /api/course-enrollment/create-payment |
auth | Create Razorpay invoice/order for a batch enrollment |
| POST | /api/course-enrollment/verify-payment |
auth | Verify signature, mark paid, enroll / start PAP workflow |
| GET | /api/course-enrollment/history |
auth | Caller's payment history |
| GET | /api/course-enrollment/check-status |
auth | Payment status for a batch |
| GET | /api/course-enrollment/invoices |
auth | Invoice list (incl. MAS101 PAP) |
| GET | /api/course-enrollment/invoices/:invoiceId/pdf |
auth | Download invoice PDF |
Marketing course CRUD — createMasCourseRoutes mounted at /api/admin/new-courses (src/routes/newCourseRoutes.ts)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/admin/new-courses |
auth | All MasCourse rows (filterable) |
| GET | /api/admin/new-courses/type/:type |
auth | By MasCourseType |
| POST | /api/admin/new-courses/import |
auth | Bulk import from JSON |
| PUT | /api/admin/new-courses/batch/reorder |
auth | Batch update displayOrder |
| GET | /api/admin/new-courses/:id |
auth | Single card |
| POST | /api/admin/new-courses |
auth | Create card |
| PUT | /api/admin/new-courses/:id |
auth | Update card |
| DELETE | /api/admin/new-courses/:id |
auth | Delete card |
| PUT | /api/admin/new-courses/:id/display-order |
auth | Set displayOrder |
Admin LMS builder — AdminRoutes mounted at /api/admin (src/routes/admin.routes.ts)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET/POST/PUT/DELETE | /api/admin/mas/courses[/:id] |
auth + admin | Structured Course CRUD (AdminMasController) |
| POST | /api/admin/mas/courses/:id/roadmap/upload |
auth + admin | Upload a roadmap file |
| GET/POST/PUT/DELETE | /api/admin/mas/batches[/:id] |
auth + admin | Batch CRUD |
| PUT/GET | /api/admin/mas/batches/:id/modules |
auth + admin | Assign / list batch modules |
| PUT/GET | /api/admin/mas/batches/:id/mrlearn-courses |
auth + admin | Set / list attached Mr Learn courses |
| PUT/GET | /api/admin/mas/batches/:id/mrtest-exams |
auth + admin | Set / list attached Mr Test exams |
| PATCH | /api/admin/mas/batches/:id/status |
auth + admin | Change BatchStatus |
| POST/DELETE | /api/admin/mas/batches/:id/roadmap/:itemId/notes[/:noteId] |
auth + admin | Add / delete roadmap note |
| POST | /api/admin/mas/batches/:id/roadmap/:itemId/notes/upload |
auth + admin | Upload roadmap-note file |
| GET/POST/PUT/DELETE | /api/admin/mas/modules[/:id] |
auth + admin | Module CRUD (ModuleService) |
| POST | /api/admin/mas/courses/:courseId/modules/reorder |
auth + admin | Reorder modules |
| POST | /api/admin/mas/modules/:id/duplicate |
auth + admin | Duplicate a module |
| POST | /api/admin/mas/course-plans/generate-blueprint |
auth + admin | AI: generate a course-plan blueprint |
| GET | /api/admin/mas/course-plans[/:id] |
auth + admin | List / fetch course plans |
| PUT | /api/admin/mas/course-plans/:id/blueprint |
auth + admin | Edit blueprint |
| POST | /api/admin/mas/course-plans/:id/approve |
auth + admin | Approve → queue lecture generation |
| POST | /api/admin/mas/course-plans/:id/pause |
auth + admin | Pause generation |
| POST | /api/admin/mas/course-plans/:id/resume |
auth + admin | Resume / retry failed lectures |
| POST | /api/admin/mas/course-plans/:id/lectures/:lectureId/regenerate |
auth + admin | Regenerate one lecture |
| GET | /api/admin/mas/course-plans/:id/progress |
auth + admin | Generation progress |
| DELETE | /api/admin/mas/course-plans/:id |
auth + admin | Delete plan |
| POST/GET | /api/admin/mas/course-plans/:id/export-s3, /import-s3, -s3-exports |
auth + admin | Export / import plan to S3 |
| GET | /api/admin/mentees/batches |
auth + admin | Batch dropdown for provisioning |
| POST | /api/admin/mentees/provision |
auth + admin | Provision a fully-enrolled mentee |
Student-facing — StudentRoutes mounted at /api/student (src/routes/student.routes.ts) and dashboard routes (src/routes/dashboard.routes.ts)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/student/course-plans[/:id] |
auth | Student's course plans + lecture progress |
| GET | /api/student/saved-courses |
auth | "My List" |
| POST | /api/student/saved-courses |
auth | Add to My List |
| DELETE | /api/student/saved-courses/:courseType/:courseId |
auth | Remove from My List |
| GET | /api/student/batches/:batchId/roadmap |
auth + isEnrolled | Batch roadmap (courses + exams per item) |
| POST | /api/student/mrtest-exams/:id/launch |
auth | Launch an attached Mr Test exam (prerequisite-gated) |
| GET | /api/student/batches |
(dashboard) | Available batches |
| GET | /api/student/announcements |
(dashboard) | Active announcements |
College directory — CollegeRoutes mounted at /api (src/routes/college.routes.ts)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/colleges |
public | Paginated list |
| GET | /api/colleges/iit |
public | IIT colleges |
| GET | /api/colleges/search |
public | Fuzzy search |
| GET | /api/colleges/state/:state |
public | By state |
| GET | /api/colleges/stats |
public | Counts |
| POST/PUT/DELETE | /api/colleges[/:id] |
public* | Create/update/delete (not auth-gated at route level) |
Attendance — CMRoutes (/api/cm) and SuperMentorRoutes (/api/supermentor)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| PATCH | /api/cm/students/:studentId/attendance |
CM | Mark a student's attendance |
| GET | /api/cm/students/:studentId/attendance |
CM | Read attendance |
| POST | /api/cm/attendance/upload |
CM | Bulk attendance upload |
| POST | /api/cm/batches/:batchId/batch-meetings |
CM | Schedule a batch meeting |
| GET | /api/supermentor/students/:id/attendance |
Super Mentor | Read attendance for assigned student |
User journeys¶
1. Public visitor browses the course catalog¶
The website fetches marketing cards (MasCourse) grouped by type. Responses are served from Redis when warm.
sequenceDiagram
participant FE as Website
participant API as "/api/courses route"
participant Redis as Redis cache
participant DB as PostgreSQL
FE->>API: GET cohorts
API->>Redis: get courses:cohorts
alt cache hit
Redis-->>API: cached array
API-->>FE: 200 with data
else cache miss
API->>DB: find MasCourse where type is placement_cohort
DB-->>API: rows ordered by displayOrder
API->>API: map rows to card shape from metadata
API->>Redis: setex courses:cohorts ttl 600
API-->>FE: 200 with data
end
The structured catalog at GET /api/courses is separate: CourseService.getAllCourses query-builds against courses, defaulting to PUBLISHED and supporting category, level, search filters.
2. Admin builds a structured course and its curriculum¶
sequenceDiagram
participant ADM as Admin UI
participant API as "/api/admin/mas route"
participant CS as CourseService
participant MS as ModuleService
participant DB as PostgreSQL
ADM->>API: POST mas/courses with title and roadmap
API->>CS: createCourse
CS->>CS: validateRoadmapPayload
alt invalid roadmap
CS-->>API: throw RoadmapValidationError
API-->>ADM: 400 with issues
else valid
CS->>DB: insert into courses
DB-->>CS: course row
CS-->>API: course
API-->>ADM: 201 created
end
ADM->>API: POST mas/modules with courseId
API->>MS: createModule
MS->>DB: validate course exists then insert course_modules
MS->>DB: recount and update courses.totalModules
MS-->>API: module
API-->>ADM: 201 created
ADM->>API: POST mas/courses/:courseId/modules/reorder
API->>MS: reorderModules updates moduleOrder per row
MS-->>API: ok
API-->>ADM: 200 reordered
3. Admin generates an AI Course Plan (blueprint then per-lecture generation)¶
A CoursePlan starts as a DRAFT blueprint. On approval it flips to GENERATING and a BullMQ job fans out one AiClassroom per lecture via coursePlanQueue.
sequenceDiagram
participant ADM as Admin UI
participant API as CoursePlanController
participant DB as PostgreSQL
participant Q as coursePlanQueue
participant W as course-plan.worker
ADM->>API: POST course-plans/generate-blueprint with topic
API->>API: call LLM to draft blueprint of modules and lectures
API->>DB: insert course_plans status draft plus course_plan_lectures pending
API-->>ADM: 200 blueprint ready for review
ADM->>API: PUT course-plans/:id/blueprint edits
API->>DB: update blueprint and lecture rows
ADM->>API: POST course-plans/:id/approve
API->>DB: set plan status generating and startedAt
API->>Q: add generateCoursePlanLectures with coursePlanId
API-->>ADM: 200 generation starting
Q->>W: deliver job
loop for each pending lecture
W->>W: generate AI classroom content
W->>DB: set lecture status completed and link aiClassroomId
W->>DB: increment generatedLectures
end
W->>DB: set plan status completed and completedAt
Note over W,DB: failed lectures set FAILED so resume can retry
pausePlan sets PAUSED; resumePlan resets stuck GENERATING lectures back to PENDING, re-queues, and refuses if there is nothing failed/pending to retry; regenerateLecture clears aiClassroomId and queues a generateSingleLecture job.
4. Admin assembles a Batch and attaches courses + exams¶
sequenceDiagram
participant ADM as Admin UI
participant API as AdminMasController
participant BS as BatchService
participant DB as PostgreSQL
ADM->>API: POST mas/batches with code and courseId
API->>BS: createBatch
BS->>DB: validate course exists and batchLead role
BS->>DB: insert into batches
BS-->>API: batch
API-->>ADM: 201 created
ADM->>API: PUT mas/batches/:id/modules with moduleIds
API->>BS: assignModulesToBatch sets assignedModuleIds
BS-->>API: batch
ADM->>API: PUT mas/batches/:id/mrlearn-courses
API->>BS: setBatchMrLearnCourses per roadmap item
BS->>DB: replace batch_mrlearn_courses rows
ADM->>API: PUT mas/batches/:id/mrtest-exams
API->>BS: setBatchMrTestExams with prerequisite course and threshold
BS->>DB: replace batch_mrtest_exams rows and ensure sync configs
BS-->>API: ok
API-->>ADM: 200 attached
If an admin later changes the batch's courseId and existing Mr Learn attachments reference roadmap-item ids absent from the new course, updateBatch throws a typed REQUIRES_REMAP_CONFIRMATION error (with orphan/preserved counts) unless clearMrLearnOnCourseChange: true is passed — surfaced as a confirm prompt in the UI.
5. Student enrolls with payment (Razorpay)¶
The paid path runs against the Application entity (not Enrollment directly). Pricing is server-authoritative.
sequenceDiagram
participant STU as Student
participant API as CourseEnrollmentController
participant CES as CourseEnrollmentService
participant RZP as Razorpay
participant DB as PostgreSQL
participant Q as emailQueue
STU->>API: POST course-enrollment/create-payment with batchId and amount
API->>CES: createEnrollmentPayment
CES->>DB: reject if already enrolled or applied to another batch
CES->>DB: load batch and compute baseAmount plus GST
alt frontend amount mismatches server total
CES-->>API: throw AMOUNT_MISMATCH
API-->>STU: 400 mismatch
else docs required but missing
CES-->>API: throw DOCUMENTS_MISSING
API-->>STU: 400 with missingDocuments
else ok
CES->>RZP: invoices.create then fallback to orders.create
RZP-->>CES: order id and invoice short url
CES->>DB: upsert application with razorpayOrderId pending
CES-->>API: order plus razorpayKeyId
API-->>STU: 200 open checkout
end
STU->>RZP: complete payment in Checkout
RZP-->>STU: payment id and signature
STU->>API: POST course-enrollment/verify-payment
API->>CES: verifyEnrollmentPayment
CES->>DB: atomic claim set paymentVerified true
CES->>CES: verify HMAC signature trying order and invoice formulas
alt no signature variant matches
CES->>RZP: payments.fetch to confirm captured
RZP-->>CES: status captured
end
alt batch code is mas101
CES->>DB: set status PAYMENT_RECEIVED
CES->>CES: ensureWorkflowForPaymentReceived starts PAP workflow
else other batch
CES->>DB: set status ENROLLED
CES->>CES: ensureEnrollmentForApplication creates Enrollment row
end
CES->>Q: queue payment-invoice email when not a Razorpay hosted invoice
CES-->>API: success
API-->>STU: 200 enrolled or workflow started
Key safeguards in verifyEnrollmentPayment (src/services/CourseEnrollmentService.ts):
- Idempotent claim — an atomic UPDATE ... WHERE paymentVerified = false prevents concurrent verify calls from double-firing emails/WhatsApp/enrollment.
- Multi-formula signature check — tries the order formula (callback + stored order id) and several invoice formulas, then falls back to a live payments.fetch that confirms captured status + matching order/invoice id + amount.
- MAS101 fork — mas101 batches enter the PAP (Pay-After-Placement) workflow instead of immediate enrollment; see Document e-signing / PAP docs.
6. Granted enrollment — admin provisions a fully-enrolled mentee¶
menteeProvision.controller.ts upserts a verified, profile-complete user and writes an ENROLLED Application with every onboarding flag set true. This bypasses payment entirely.
sequenceDiagram
participant ADM as Admin UI
participant API as MenteeProvisionController
participant DB as PostgreSQL
ADM->>API: POST mentees/provision with email password batchId
API->>API: validate email password length and batchId
API->>DB: find batch by id
API->>DB: find user by email case insensitive
alt user exists and confirmRoleChange not set
API-->>ADM: 200 needsConfirm with existing details
else proceed
alt new user
API->>DB: insert verified profile-complete student
else existing user
API->>DB: update password role mentee and flags
end
API->>DB: upsert ENROLLED application with all onboarding flags true
API-->>ADM: 200 provisioned and enrolled
end
7. Self-enrollment into a free / structured course¶
sequenceDiagram
participant STU as Student
participant API as CourseController
participant CS as CourseService
participant DB as PostgreSQL
STU->>API: POST student/courses/:id/enroll
API->>CS: enrollInCourse userId courseId
CS->>DB: verify user and course exist
CS->>DB: check for existing enrollment
alt already enrolled
CS-->>API: throw Already enrolled
API-->>STU: 400 already enrolled
else
CS->>DB: insert enrollment status not_started progress 0
CS-->>API: enrollment
API-->>STU: 201 enrolled
end
STU->>API: PUT student/courses/:id/progress with progress
API->>CS: updateProgress
CS->>DB: update progress and set status from progress value
CS-->>API: enrollment
API-->>STU: 200 updated
8. Enrolled student launches a batch exam (prerequisite-gated)¶
sequenceDiagram
participant STU as Student
participant API as StudentController
participant BS as BatchService
participant DB as PostgreSQL
STU->>API: GET student/batches/:batchId/roadmap
API->>BS: getBatchMrLearnCourses and getBatchMrTestExams
BS->>DB: load attachments per roadmap item
BS-->>API: roadmap with courses and exams plus unlock state
API-->>STU: 200 roadmap
STU->>API: POST student/mrtest-exams/:id/launch
API->>BS: canStudentAttemptTest
BS->>DB: check prerequisite course progress against threshold
alt prerequisite not met
BS-->>API: locked with blocker payload
API-->>STU: 403 locked
else unlocked
BS-->>API: unlocked with takeUrl
API-->>STU: 200 redirect to EzExam take url
end
9. Attendance capture (Community Manager)¶
sequenceDiagram
participant CM as Community Manager
participant API as CMController
participant DB as PostgreSQL
CM->>API: PATCH cm/students/:studentId/attendance with date and status
API->>DB: upsert attendance row for student and date
API-->>CM: 200 marked
CM->>API: POST cm/attendance/upload bulk file
API->>DB: insert many attendance rows
API-->>CM: 200 with counts
10. Announcement publishing and student fetch¶
Announcements are either global or scoped to one courseId. Students read active announcements via the dashboard route.
sequenceDiagram
participant ADM as Admin or CM
participant DB as PostgreSQL
participant STU as Student
participant API as DashboardController
ADM->>DB: insert announcement isGlobal or courseId with isActive true
STU->>API: GET student/announcements
API->>DB: find announcements where isActive true and global or matching course
DB-->>API: rows
API-->>STU: 200 announcements
Background jobs & async¶
| Trigger | Queue | Worker | What it does |
|---|---|---|---|
| Approve / resume course plan | coursePlanQueue (generateCoursePlanLectures) |
src/workers/course-plan.worker.ts |
Iterates pending lectures, generates an AiClassroom per lecture, updates generatedLectures, marks plan COMPLETED/FAILED |
| Regenerate single lecture | coursePlanQueue (generateSingleLecture) |
same worker | Re-generates one lecture's AI classroom |
| Payment verified (non-hosted invoice) | emailQueue (payment-invoice) |
src/workers/email.worker.ts |
Emails an invoice to the student |
| New MAS user created | — (best-effort, inline) | DemoSeedService |
Idempotently seeds demo activity / Mr Test submission / Mr Learn enrollment so the dashboard renders non-empty |
There are no Socket.IO events specific to this domain (those belong to the meetings domain). Public catalog responses are cached in Redis under courses:* keys with a 10-minute TTL; POST /api/courses/cache/clear flushes them.
External integrations¶
| System | Used by | Env vars | Failure / fallback |
|---|---|---|---|
| Razorpay | CourseEnrollmentService |
RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET |
invoices.create is preferred (Razorpay emails a hosted tax invoice); falls back to orders.create if the customer email is missing or Invoices are disabled. Signature verification has a payments.fetch fallback when no HMAC variant matches |
| Redis | public catalog cache, BullMQ | REDIS_HOST, REDIS_PORT |
Cache read/write errors are caught and logged; the route falls through to a DB query |
| Mr Learn / Graphy | BatchMrLearnCourse attachments |
(Graphy creds — see Graphy LMS skill) | Cross-schema by id only; courseUrl snapshot is captured at attach-time so the link survives a future sync wiping the shared mrlearn.courses row |
| Mr Test / EzExam | BatchMrTestExam attachments |
(EzExam sync config) | takeUrl snapshot captured at attach; ensureMrTestSyncConfigs / ensureMrTestOnlineExamsCached keep exam metadata cached. Cross-schema by id only |
| LLM (course-plan blueprints + lectures) | CoursePlanController, course-plan worker |
LLM gateway / provider keys | Lectures that fail generation are set FAILED; resumePlan retries them |
| AWS S3 | course-plan export/import, roadmap-note uploads, batch banners | AWS_* (see repo CLAUDE.md) |
Export/import is explicit admin action |
Status lifecycles¶
Enrollment¶
stateDiagram-v2
[*] --> NOT_STARTED: enroll or ensureEnrollment
NOT_STARTED --> IN_PROGRESS: progress greater than 0
IN_PROGRESS --> COMPLETED: progress reaches 100
NOT_STARTED --> COMPLETED: progress reaches 100
COMPLETED --> [*]
CoursePlan¶
stateDiagram-v2
[*] --> DRAFT: generate blueprint
DRAFT --> GENERATING: approve
GENERATING --> COMPLETED: all lectures done
GENERATING --> PAUSED: pause
PAUSED --> GENERATING: resume
COMPLETED --> GENERATING: resume to retry failed
GENERATING --> FAILED: generation aborts
FAILED --> GENERATING: resume
COMPLETED --> [*]
CoursePlanLecture¶
stateDiagram-v2
[*] --> PENDING
PENDING --> QUEUED: job enqueued
QUEUED --> GENERATING: worker picks up
GENERATING --> COMPLETED: AI classroom created
GENERATING --> FAILED: generation error
FAILED --> PENDING: resume plan
GENERATING --> PENDING: resume resets stuck lectures
COMPLETED --> PENDING: regenerate clears classroom
Application (the real enrollment gate)¶
stateDiagram-v2
[*] --> DRAFT: create-payment or wizard
DRAFT --> APPROVED: admin approves discount
APPROVED --> PAYMENT_RECEIVED: mas101 payment verified
APPROVED --> ENROLLED: non-mas101 payment verified
DRAFT --> ENROLLED: non-mas101 payment verified
PAYMENT_RECEIVED --> ENROLLED: PAP workflow completes
DRAFT --> ENROLLED: admin provision
ENROLLED --> [*]
Batch¶
stateDiagram-v2
[*] --> UPCOMING
UPCOMING --> ACTIVE: start
ACTIVE --> COMPLETED: finish
UPCOMING --> CANCELLED
ACTIVE --> CANCELLED
COMPLETED --> [*]
Edge cases, limits & gotchas¶
- The enrollment gate is
Application, notEnrollment.isEnrolledMiddleware(src/middleware/isEnrolled.middleware.ts) checks for anApplicationin statusENROLLED,BATCH_ALLOCATED, orPAID—PAYMENT_RECEIVEDis intentionally not enough for MAS101 (post-payment PAP steps remain). TheEnrollmentrow is a per-course progress record created after the gate is crossed. - Admin/CM bypass on student routes.
isEnrolledMiddlewareletsADMINandCOMMUNITY_MANAGERthrough without an application, for testing/support. - Two course models, no FK between them.
Course(LMS) andMasCourse(marketing) are unrelated tables. The website readsMasCourse; the dashboard/roadmap readsCourse. course.routes.tsadmin endpoints check role in the handler, not viaadminMiddleware— they require onlyauthMiddlewareat the route level, then return 403 ifreq.user.role !== ADMIN. The richer admin CRUD lives under/api/admin/mas/*.- College create/update/delete and
cache/clearare not auth-gated at the route level (markedpublic*above). Treat as a hardening TODO. - Server-authoritative pricing.
createEnrollmentPaymentrecomputes the amount from the batch'senrollmentFee(or the student's approvedfinalAmount) plus GST and rejects any client amount that doesn't match (AMOUNT_MISMATCH). - Document enforcement before payment. For batches with
requiresDocuments,create-paymentrefuses (DOCUMENTS_MISSING, with the missing-doc list) rather than auto-creating a draft. - Idempotent enrollment.
CourseService.ensureEnrollmentreturns the existing row instead of throwing, so it's safe to call from payment verification and course-assignment flows that may run more than once.enrollInCourse(self-serve) does throw on duplicates. - Course deletion cascades carefully.
CourseService.deleteCourseruns in a transaction: deletesEnrollmentrows, nullsAiClassroom.courseId/moduleId, nullsBatch.courseId, then removes the course. - Mr Learn / Mr Test attachments are cross-schema by id only — no DB FK. URL/name snapshots are stored at attach-time to survive upstream syncs. Changing a batch's course can orphan attachments; the API requires explicit
clearMrLearnOnCourseChangeconfirmation. MenteeProvisionControlleris a test/ops helper — it bypasses the entire payment and onboarding flow and flips all onboarding flags true. Use with care in production.BatchMeetingcarries dual schemas (scheduledDate/scheduledTimefrom the BatchLead path andstartTime/endTimefrom the CM path) due to a historical merge; both column sets exist on the entity.- Multi-tenant (
x-platform) affects auth/branding globally but does not change the LMS data model; courses and batches are shared across platforms.
Related docs¶
- Finance — Payments, GST Invoicing & PAP — Razorpay, MAS101 PAP workflow, invoices
- Applications & Enrollment Pipeline — the
Applicationlifecycle, batch allocation, discount approvals - Assessments — Quizzes & Assignments —
Quiz/Assignmentunder modules - Mr Learn (Graphy) Integration — attached external courses
- Mr Test (EzExam) Integration — attached exams and prerequisite gating
- AI Classroom & Smart Classrooms —
AiClassroomtargets of course-plan lectures - Student Dashboard & Engagement — roadmap, daily cards, demo seeding
- Background Jobs & Queues — BullMQ queues and workers