Skip to content

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:

  1. Public catalog / marketing layer — two parallel course models:
  2. Course (courses table) — the structured course used inside the LMS: title, description, instructor, level, price, a roadmap, and child Module/Class rows.
  3. MasCourse (mas_courses table, 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-form metadata JSON blob (price labels, badges, icons) and are Redis-cached for the website.
  4. Authoring layer (admin) — admins build structured Course curricula (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.
  5. Enrollment / delivery layer — students reach a course either by a paid enrollment (Razorpay, via the Application entity) 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 Application status, not by the Enrollment row. 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 · ARCHIVED
  • CourseLevel: BEGINNER · INTERMEDIATE · ADVANCED · ALL_LEVELS
  • MasCourseType (MasCourse): PLACEMENT_COHORT · CAPSULE · ACCELERATOR · DEEP_TECH
  • MasCourseStatus: DRAFT · PUBLISHED · ARCHIVED · SOLD_OUT · COMING_SOON
  • EnrollmentStatus (Enrollment): NOT_STARTED · IN_PROGRESS · COMPLETED
  • CoursePlanStatus (CoursePlan): DRAFT · APPROVED · GENERATING · PAUSED · COMPLETED · FAILED
  • LectureStatus (CoursePlanLecture): PENDING · QUEUED · GENERATING · COMPLETED · FAILED
  • BatchStatus (Batch): UPCOMING · ACTIVE · COMPLETED · CANCELLED
  • BatchMeetingStatus: SCHEDULED · COMPLETED · CANCELLED
  • AttendanceStatus: PRESENT · ABSENT · LATE · EXCUSED
  • AnnouncementType: INFO · SUCCESS · WARNING · ERROR

Note on scope drift: there is no CourseAccessGrant entity or CourseAccessService/courseAccess controller in this branch. "Access grant" in practice is implemented through the Application lifecycle (paid or admin-provisioned ENROLLED state) and the idempotent Enrollment row. 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 to COURSE — they are two independent models. ENROLLMENT joins USERCOURSE, while access-control joins USERBATCH via APPLICATION.


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)
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 forkmas101 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, not Enrollment. isEnrolledMiddleware (src/middleware/isEnrolled.middleware.ts) checks for an Application in status ENROLLED, BATCH_ALLOCATED, or PAIDPAYMENT_RECEIVED is intentionally not enough for MAS101 (post-payment PAP steps remain). The Enrollment row is a per-course progress record created after the gate is crossed.
  • Admin/CM bypass on student routes. isEnrolledMiddleware lets ADMIN and COMMUNITY_MANAGER through without an application, for testing/support.
  • Two course models, no FK between them. Course (LMS) and MasCourse (marketing) are unrelated tables. The website reads MasCourse; the dashboard/roadmap reads Course.
  • course.routes.ts admin endpoints check role in the handler, not via adminMiddleware — they require only authMiddleware at the route level, then return 403 if req.user.role !== ADMIN. The richer admin CRUD lives under /api/admin/mas/*.
  • College create/update/delete and cache/clear are not auth-gated at the route level (marked public* above). Treat as a hardening TODO.
  • Server-authoritative pricing. createEnrollmentPayment recomputes the amount from the batch's enrollmentFee (or the student's approved finalAmount) plus GST and rejects any client amount that doesn't match (AMOUNT_MISMATCH).
  • Document enforcement before payment. For batches with requiresDocuments, create-payment refuses (DOCUMENTS_MISSING, with the missing-doc list) rather than auto-creating a draft.
  • Idempotent enrollment. CourseService.ensureEnrollment returns 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.deleteCourse runs in a transaction: deletes Enrollment rows, nulls AiClassroom.courseId/moduleId, nulls Batch.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 clearMrLearnOnCourseChange confirmation.
  • MenteeProvisionController is a test/ops helper — it bypasses the entire payment and onboarding flow and flips all onboarding flags true. Use with care in production.
  • BatchMeeting carries dual schemas (scheduledDate/scheduledTime from the BatchLead path and startTime/endTime from 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.