Skip to content

Mentorship & Meetings (Mr. Mentor Core)

The original heart of the platform: students discover expert mentors, browse and request 30-minute availability slots, spend platform tokens to book a call, and meet over the in-app WebRTC video room. Mentors publish availability, approve or reject booking requests (in-app or straight from an email link), run the call, and get earnings credited once participation is verified. The same Slots table also powers a lighter "Super Mentor" 1:1 coaching track for batch students. This document covers discovery, availability, booking, the meeting lifecycle, feedback, cancellation, expired-slot cleanup, Google Calendar sync, and recording metadata.

Status: documented from source on this branch.

The realtime side of a live call (Socket.IO signalling, WebRTC offer/answer/ICE, the recording media upload pipeline) lives in the realtime/meeting-room doc — this doc cross-links to it and only documents the recording metadata model and REST endpoints here.


Overview

This domain serves three primary personas:

Persona Role(s) What they do
Student / Mentee USER Browse mentors, request availability, book confirmed slots with tokens, join the call, leave feedback, request cancellations.
Mentor / Expert EXPERT Publish availability slots, approve/reject booking requests, run calls, rate the student, earn per completed call.
Super Mentor EXPERT / staff Run scheduled 1:1 coaching meetings (onboarding, progress review, mock-interview prep) for assigned batch students.
Admin ADMIN, SUPERADMIN Inspect recordings, get notified on cancellation requests, monitor slot statistics.
Recording bot (unauthenticated, public route) Reads a public "bot-info" projection of a meeting to join and record.

Where it sits in the suite: this is the Mr. Mentor product. It depends on the Token economy (book = hold/spend a token; cancel/expire = refund), feeds Mentor earnings/payouts (complete = credit), emits student gamification XP (mentor_call_completed), and integrates with Google Calendar for scheduling. The MAS website (mas-website-live) and the admin/expert frontend (mr-mentor-frontend) both consume these endpoints.


Key concepts & entities

Glossary

  • Slot — a single bookable 30-minute window owned by a mentor. One row in slots is reused through its whole life: it starts as an available template (isAvailable=true, no student), then gets a studentId and a non-tentative status when booked. Cancelling a booked slot does not resurrect the old row — it spawns a new available slot for the same time.
  • Availability — mentor-published slots with isAvailable=true and studentId IS NULL. The count of future ones is mirrored onto Mentor.slotsLeft.
  • Requested slot — a soft "please open a slot at this time" ask from a student when no availability exists. Stored separately in requested_slots; it is not a booking.
  • Token hold — booking deducts 1 token (promotional token preferred over paid token) inside a DB transaction; cancellation/expiry refunds 1 token.
  • Meeting action token — a one-time, 7-day, 64-hex secret embedded in the mentor's approval email so they can approve/reject without logging in.
  • Participation gate — earnings are only credited if meeting logs show both mentor and student were present for at least 15 minutes.
  • Mentor multiplier — a per-mentor earnings multiplier (mentorMultiplier, multiplierData).

TypeORM entities (all under src/entities/)

Entity File Table Notes
Mentor src/entities/Mentor.ts mentors Mentor profile; OneToOne to User. category, subCategory, slotsLeft, rating, mentorMultiplier, isApplicationMentor.
Slots src/entities/Slots.ts slots The booking/meeting row. Holds status (MeetingStatus), isAvailable, isFree, source, actual start/end times, durationMinutes, earningsCredited, Google Calendar event IDs.
RequestedSlot src/entities/RequestedSlot.ts requested_slots Student availability request; status = pending/approved/rejected.
MeetingLogs src/entities/MeetingLogs.ts meeting_logs Per-event audit (MeetingLogEventType) used for participation analysis.
MeetingRecording src/entities/MeetingRecording.ts meeting_recordings Recording metadata + S3 location; status = RecordingStatus.
MeetingActionToken src/entities/MeetingActionToken.ts meeting_action_tokens Email approve/reject tokens; actionType, used, expiresAt.
SlotsFeedBack src/entities/SlotsFeedBack.ts slots_feedback Dual feedback (student↔mentor); Experience enum, ratings, "joined on time" flags.
SuperMentorAssignment src/entities/SuperMentorAssignment.ts super_mentor_assignments Maps a super mentor to a student/batch.
SuperMentorMeeting src/entities/SuperMentorMeeting.ts super_mentor_meetings Scheduled coaching meeting; own MeetingStatus + MeetingType enums.
ProfileView src/entities/ProfileView.ts profile_views Tracks who viewed whose profile (discovery analytics).

Status enums

  • MeetingStatus (src/entities/Slots.ts): confirmed, tentative, cancelled, completed, cancellation_requested, meeting_requested, under_review.
  • RequestedSlotStatus: pending, approved, rejected.
  • MeetingLogEventType: meeting_initialized, user_joined, user_left, meeting_started, meeting_ended, recording_started, recording_stopped.
  • ParticipantRole: mentor, student, unknown, observer.
  • RecordingStatus: recording, processing, uploaded, failed, deleted.
  • Experience (feedback): excellent, good, average, below_average, poor.
  • SuperMentorMeeting MeetingStatus: scheduled, completed, cancelled, no_show; MeetingType: 1:1 Onboarding, Progress Review, Skill Assessment, Mock Interview Prep, Career Guidance, Other.

Architecture

flowchart TD
  subgraph FE["Frontends"]
    WEB["mas-website-live"]
    ADMIN["mr-mentor-frontend (expert/admin)"]
    BOT["Recording bot"]
  end

  subgraph ROUTES["Express routes (mounted under /api)"]
    R1["mentor.routes.ts"]
    R2["slotCompletion.routes.ts"]
    R3["meetingAction.routes.ts"]
    R4["meetingLogs.routes.ts"]
    R5["recording.routes.ts"]
    R6["googleCalendar.routes.ts"]
    R7["superMentor.routes.ts"]
  end

  subgraph CTRL["Controllers"]
    C1["MentorsController"]
    C2["SlotCompletionController"]
    C3["MeetingActionController"]
    C4["MeetingLogsController"]
    C5["RecordingController"]
    C6["GoogleCalendarController"]
    C7["SuperMentorController"]
  end

  subgraph SVC["Services"]
    S1["MentorService"]
    S2["SlotCompletionService"]
    S3["MeetingActionTokenService"]
    S4["MeetingLogsService"]
    S5["MeetingRecordingService"]
    S6["GoogleCalendarService"]
    S7["SuperMentorService"]
    S8["SlotCleanupService"]
    S9["MentorEarningsService"]
    S10["TokenService"]
    H1["MeetingNotificationHelper"]
  end

  subgraph DATA["PostgreSQL (TypeORM)"]
    D1[("slots")]
    D2[("mentors / users")]
    D3[("requested_slots")]
    D4[("meeting_logs")]
    D5[("meeting_recordings")]
    D6[("meeting_action_tokens")]
    D7[("slots_feedback")]
    D8[("super_mentor_meetings")]
    D9[("google_auth_tokens")]
  end

  subgraph EXT["External & async"]
    Q["BullMQ emailQueue / databaseQueue / cleanupQueue"]
    GC["Google Calendar API"]
    S3S["AWS S3 (recordings)"]
    RD["Redis cache"]
    SIO["Socket.IO meeting room"]
  end

  WEB --> R1 & R2 & R3 & R6
  ADMIN --> R1 & R2 & R4 & R5 & R6 & R7
  BOT --> R1

  R1 --> C1 --> S1
  R2 --> C2 --> S2
  R3 --> C3 --> S3
  R4 --> C4 --> S4
  R5 --> C5 --> S5
  R6 --> C6 --> S6
  R7 --> C7 --> S7

  S1 --> D1 & D2 & D3 & D7
  S1 --> S6 & H1 & S10
  S2 --> D1 & S9 & S4
  S3 --> D6 & D1
  S4 --> D4
  S5 --> D5 --> S3S
  S6 --> D9 & GC
  S7 --> D8
  S8 --> D1 & D3 & S10
  H1 --> Q
  S1 --> RD
  C5 -. live media .- SIO

Data model

erDiagram
  USER ||--o| MENTOR : "has profile"
  USER ||--o{ SLOTS : "mentors"
  USER ||--o{ SLOTS : "books as student"
  SLOTS ||--o| SLOTS_FEEDBACK : "has"
  SLOTS ||--o{ MEETING_LOGS : "logs"
  SLOTS ||--o{ MEETING_RECORDINGS : "records"
  SLOTS ||--o{ MEETING_ACTION_TOKENS : "approve or reject"
  SLOTS ||--o{ TOKEN_USAGE : "token hold"
  USER ||--o{ REQUESTED_SLOTS : "requests"
  USER ||--o{ SUPER_MENTOR_ASSIGNMENTS : "assigned"
  USER ||--o{ SUPER_MENTOR_MEETINGS : "coaches"
  USER ||--o{ PROFILE_VIEWS : "viewed"

  MENTOR {
    uuid id PK
    string company
    string role
    string category
    string subCategory
    int slotsLeft
    float rating
    float mentorMultiplier
    bool isApplicationMentor
  }
  SLOTS {
    uuid id PK
    timestamp startDateTime
    timestamp endDateTime
    uuid mentorId FK
    uuid studentId FK
    bool isAvailable
    enum status
    bool isFree
    string source
    timestamp actualStartTime
    timestamp actualEndTime
    int durationMinutes
    bool earningsCredited
    string googleCalendarEventIdMentor
    string googleCalendarEventIdStudent
  }
  REQUESTED_SLOTS {
    uuid id PK
    uuid studentId FK
    uuid mentorId FK
    string requestedDate
    string requestedTime
    enum status
  }
  MEETING_LOGS {
    uuid id PK
    uuid slotId FK
    string roomId
    uuid userId FK
    enum userRole
    enum eventType
    timestamp eventTime
    jsonb metadata
  }
  MEETING_RECORDINGS {
    uuid id PK
    uuid slotId FK
    uuid recordedBy FK
    string sessionId
    string s3Key
    enum status
    int durationSeconds
    bigint fileSizeBytes
  }
  MEETING_ACTION_TOKENS {
    uuid id PK
    string token UK
    uuid slotId FK
    uuid mentorId FK
    enum actionType
    bool used
    timestamp expiresAt
  }
  SLOTS_FEEDBACK {
    uuid id PK
    uuid slotId FK
    uuid mentor_id FK
    uuid student_id FK
    float rating
    enum experience
    bool feedbackSubmittedByStudent
    float ratingByMentor
    enum experienceByMentor
    bool feedbackSubmittedByMentor
  }
  SUPER_MENTOR_MEETINGS {
    uuid id PK
    uuid superMentorId FK
    uuid studentId FK
    enum type
    date scheduledDate
    time scheduledTime
    string meetingLink
    enum status
  }

API surface

All routes below are mounted under the /api prefix in src/routes/index.ts unless otherwise noted. Auth column: none = public, JWT = authMiddleware (sets req.userId/req.user), EXPERT = expertMiddleware, ADMIN = adminMiddleware.

Mentor discovery, availability & booking — src/routes/mentor.routes.ts

Method Path Auth/role Purpose
GET /api/mentors none List mentors.
GET /api/mentors/paginated none Paginated mentor list.
GET /api/mentors/category?category=&subCategory= none Discovery by category/subcategory (Redis-cached).
GET /api/mentors/testimonials none Homepage testimonials.
GET /api/mentors/filters/companies none Distinct companies for filters.
GET /api/mentors/filters/expertise none Distinct expertise areas.
GET /api/mentors/:mentorId/profile none Single mentor profile.
GET /api/mentors/:mentorId/slots none All slots for a mentor.
GET /api/mentors/:mentorId/slots/available none Future bookable slots.
POST /api/mentors/slots/available JWT + EXPERT Mentor publishes availability.
DELETE /api/mentors/slots/available JWT + EXPERT Mentor un-publishes (unbooked) slots.
POST /api/mentors/slots none Book an available slot (token hold).
POST /api/mentors/request-meeting none Request availability when none exists.
GET /api/mentors/requested-meetings/:studentId JWT Student's pending requests.
GET /api/mentors/users/:userId/meetings none A user's meetings.
GET /api/mentors/:mentorId/meetings none Mentor's meetings (expert dashboard).
GET /api/mentors/:mentorId/slot-statistics JWT Slot stats for a mentor.
PUT /api/mentors/users/:userId/meetings/:meetingId/status none* Change meeting status (role-guarded in body).
POST /api/mentors/meetings/:meetingId/request-cancellation none Student requests cancellation.
PUT /api/mentors/meetings/:meetingId/approve-cancellation none Mentor approves cancellation.
PUT /api/mentors/meetings/:meetingId/approve-meeting none Mentor approves a booking under review.
POST /api/mentors/users/:userId/meetings/:meetingId/feedback none Submit feedback (student or mentor).
GET /api/mentors/meetings/:meetingId/bot-info none Public projection for the recording bot.
GET /api/mentors/meetings/:meetingId/details JWT Full meeting detail (mentor/student only).
GET /api/mentors/leaderboard JWT + EXPERT Mentor leaderboard.
GET /api/mentors/:mentorId/leaderboard JWT + EXPERT Single mentor's leaderboard row.
POST/GET /api/mentors/multiplier JWT + EXPERT Set / read mentor earnings multiplier.

* The status-change endpoint enforces role rules from the request body (requestorRole/requestorId) rather than middleware — see Edge cases.

Meeting lifecycle & completion — src/routes/slotCompletion.routes.ts

Method Path Auth/role Purpose
POST /api/slots/:slotId/start JWT Mark meeting started (actualStartTime).
POST /api/slots/:slotId/complete JWT Complete meeting, run participation gate, credit earnings.
GET /api/slots/:slotId/details JWT Meeting detail incl. duration + earnings status.

Email-based actions — src/routes/meetingAction.routes.ts

Method Path Auth/role Purpose
GET /api/meeting-action?token=xxx none (token is the auth) Approve/reject from the mentor's email link.

Meeting logs — src/routes/meetingLogs.routes.ts (mounted at /api/meeting-logs)

Method Path Auth/role Purpose
GET /api/meeting-logs/slot/:slotId JWT All event logs for a slot.
GET /api/meeting-logs/slot/:slotId/participation JWT Participation summary.
GET /api/meeting-logs/slot/:slotId/report JWT Human-readable participation report.
GET /api/meeting-logs/slot/:slotId/stats JWT Meeting stats.
GET /api/meeting-logs/room/:roomId JWT Logs by room id.
GET /api/meeting-logs/ JWT All logs (paginated/filtered).

Recordings (metadata) — src/routes/recording.routes.ts

Method Path Auth/role Purpose
GET /api/recordings/db/slot/:slotId JWT Recordings for a slot.
GET /api/recordings/db/session/:sessionId JWT Recording by session.
GET /api/recordings/db/all JWT + ADMIN All recordings.
GET /api/recordings/db/status/:status JWT + ADMIN By status.
DELETE /api/recordings/db/:sessionId JWT + ADMIN Soft-delete recording.
POST /api/recordings/db/upload-chunk JWT S3 multipart chunk upload.
POST /api/recordings/db/upload JWT Legacy full-file upload.
POST /api/recordings/db/upload-url JWT Presigned upload URL (client-side).
POST /api/recordings/db/confirm-upload JWT Confirm a client-side upload.
GET /api/recordings/db/stats/storage JWT + ADMIN Storage stats.
GET/DELETE /api/recordings, /api/recordings/:filename none Legacy local-file routes (back-compat).

The recording media pipeline (presign → multipart → confirm) is documented in the realtime/meeting-room doc; only the REST/metadata surface is listed here.

Google Calendar — src/routes/googleCalendar.routes.ts (mounted at /api/google-calendar)

Method Path Auth/role Purpose
GET /api/google-calendar/auth-url JWT Get OAuth consent URL.
GET /api/google-calendar/callback none (from Google) OAuth callback, stores tokens.
GET /api/google-calendar/status JWT Is calendar connected.
DELETE /api/google-calendar/disconnect JWT Remove stored tokens.

GET /auth/google/callback (top-level) redirects to /api/google-calendar/callback (see src/routes/index.ts).

Super Mentor — src/routes/superMentor.routes.ts (mounted at /api/supermentor, all JWT)

Method Path Purpose
GET /api/supermentor/meetings List the super mentor's meetings.
POST /api/supermentor/meetings Schedule a 1:1 meeting (emails student).
PATCH /api/supermentor/meetings/:id/status Update status (onboarding has a diagnostic gate).
DELETE /api/supermentor/meetings/:id Delete a meeting.

(The same router also exposes dashboard/batches/students/ratings/skills endpoints documented in the LMS / student-engagement domain.)

Adjacent read-only surfaces

  • GET /api/student/mentor-bookings?upcoming=trueMentorBookingsController projection over Slots for the student dashboard (src/routes/student.routes.ts).
  • GET /api/student/batch-meetings?upcoming=trueStudentMeetingsController over BatchMeeting (mounted via tokenGovernance.routes.ts; LMS-owned).

User journeys

1. Browse mentors by category / subcategory

MentorsController.getMentorsByCategoryMentorService.getMentorsByCategory. Results are cached in Redis; the DB query joins User (role EXPERT) with mentorProfile filtered by category and subcategory and limits to 4. Sensitive fields (password, googleId, etc.) are stripped before caching.

sequenceDiagram
  participant FE as Frontend
  participant API as MentorsController
  participant SVC as MentorService
  participant RD as Redis
  participant DB as PostgreSQL

  FE->>API: GET /api/mentors/category with category and subCategory
  API->>SVC: getMentorsByCategory
  SVC->>RD: get cached mentors for this key
  alt cache hit
    RD-->>SVC: cached mentor list
    SVC-->>FE: mentors from cache
  else cache miss
    SVC->>DB: find EXPERT users with matching mentorProfile take 4
    DB-->>SVC: mentor rows
    SVC->>SVC: strip password and other sensitive fields
    SVC->>RD: cache result for 60000 seconds
    SVC-->>FE: mentor list
  end

2. Mentor publishes availability slots

MentorsController.markSlotsAvailable (JWT + EXPERT) → MentorService.markSlotsAvailable. Runs in a transaction; for each window it either flips an existing unbooked slot to available or creates a new one, then re-syncs Mentor.slotsLeft against the count of future unbooked slots.

sequenceDiagram
  participant FE as Mentor Frontend
  participant API as MentorsController
  participant SVC as MentorService
  participant DB as PostgreSQL

  FE->>API: POST /api/mentors/slots/available with slot windows
  API->>SVC: markSlotsAvailable for mentorId
  SVC->>DB: begin transaction
  loop each window
    SVC->>DB: find existing unbooked slot for window
    alt found
      SVC->>DB: set isAvailable true
    else not found
      SVC->>DB: insert new available slot studentId null
    end
  end
  SVC->>DB: commit transaction
  SVC->>SVC: syncMentorSlots recounts future unbooked slots
  SVC->>DB: update mentor.slotsLeft
  SVC-->>FE: created slots

3. Student requests availability (no open slot)

When a mentor has no published slot at the desired time, the student files a RequestedSlot. MentorService.requestMeeting enforces a per-student pending-request cap (MAX_SLOT_REQUESTS, default 5), saves the request as pending, then emails the mentor. The email is best-effort and must not fail the request.

sequenceDiagram
  participant FE as Student Frontend
  participant API as MentorsController
  participant SVC as MentorService
  participant DB as PostgreSQL
  participant MAIL as EmailService

  FE->>API: POST /api/mentors/request-meeting
  API->>SVC: requestMeeting
  SVC->>DB: count pending requests for student
  alt at or over limit
    SVC-->>FE: error max requests reached
  else under limit
    SVC->>DB: insert requested_slot status pending
    SVC->>MAIL: send mentor notification non blocking
    Note over SVC,MAIL: email failure is swallowed and logged
    SVC-->>FE: request sent successfully
  end

4. Student books a confirmed availability slot (token hold)

MentorsController.createSlotMentorService.createSlot. The slot must match an existing available window. The whole booking runs in one transaction: prefer a non-expired promotional token (1 meet-token), otherwise a paid token (must have at least 1). On success the slot is attached to the student and set to under_review, a TokenUsage row is written, slotsLeft is decremented, and an approval email is queued to the mentor.

sequenceDiagram
  participant FE as Student Frontend
  participant API as MentorsController
  participant SVC as MentorService
  participant DB as PostgreSQL
  participant H as MeetingNotificationHelper
  participant Q as emailQueue

  FE->>API: POST /api/mentors/slots
  API->>SVC: createSlot
  SVC->>DB: find matching available slot for mentor
  alt no matching slot
    SVC-->>FE: time slot not available for booking
  else slot found
    SVC->>DB: begin transaction
    SVC->>DB: check promotional token balance
    alt promo token available
      SVC->>DB: set slot studentId and status under_review
      SVC->>DB: deduct 1 promo meet token and write TokenUsage
    else no promo
      SVC->>DB: verify paid token balance at least 1
      alt insufficient tokens
        SVC->>DB: rollback transaction
        SVC-->>FE: insufficient tokens
      else enough tokens
        SVC->>DB: set slot studentId and status under_review
        SVC->>DB: deduct 1 paid token and write TokenUsage
      end
    end
    SVC->>DB: decrement mentor slotsLeft
    SVC->>DB: commit transaction
    SVC->>H: sendMeetingApprovalEmail
    H->>Q: queue meeting-approval email to mentor
    SVC->>SVC: syncMentorSlots
    SVC-->>FE: saved slot under_review
  end

MentorsController.approveMeetingMentorService.approveMeeting. Only the assigned mentor can approve, and only a slot in under_review. Status flips to confirmed; then Google Calendar events are created for both parties (if they have connected calendars), the calendar event IDs are stored back on the slot, and confirmation emails go to both student and mentor. Calendar failure does not fail approval.

sequenceDiagram
  participant FE as Mentor Frontend
  participant API as MentorsController
  participant SVC as MentorService
  participant GC as GoogleCalendarService
  participant GAPI as Google Calendar API
  participant H as MeetingNotificationHelper
  participant MAIL as EmailService

  FE->>API: PUT /api/mentors/meetings/:id/approve-meeting
  API->>SVC: approveMeeting for mentorId
  alt not under_review or wrong mentor
    SVC-->>FE: error cannot approve
  else valid
    SVC->>SVC: set status confirmed and save
    SVC->>GC: createMeetingCalendarEvents
    GC->>GAPI: insert event for mentor with meeting link
    GC->>GAPI: insert event for student with meeting link
    GAPI-->>GC: event ids or null
    GC-->>SVC: mentorEventId and studentEventId
    SVC->>SVC: store calendar event ids on slot
    SVC->>H: sendMeetingConfirmationEmail to student
    SVC->>MAIL: sendMentorApprovalConfirmation to mentor
    Note over SVC,MAIL: calendar or email failure is logged not fatal
    SVC-->>FE: confirmed meeting
  end

The "meeting link" placed in the calendar event is the platform URL ${FRONTEND_URL}/meetings/{meetingId} (see GoogleCalendarService.createMeetingCalendarEvents), attached as a video conference entry point. The actual call is the in-app WebRTC room, not a native Google Meet conference (inferred from the link construction).

6. Email-based approve / reject (no login)

When the approval email is sent, two MeetingActionToken rows (approve + reject, 7-day expiry) are generated. The mentor clicks a link → GET /api/meeting-action?token=xxxMeetingActionTokenService.processAction. The token must be unused, unexpired, and the slot must be in meeting_requested, tentative, or under_review. Approve → confirmed (+ confirmation emails); reject → cancelled. Both tokens of the pair are then marked used.

sequenceDiagram
  participant M as Mentor email client
  participant API as MeetingActionController
  participant SVC as MeetingActionTokenService
  participant DB as PostgreSQL
  participant MAIL as EmailService

  M->>API: GET /api/meeting-action with token
  API->>SVC: processAction token
  SVC->>DB: find token with slot relations
  alt missing or used or expired
    SVC-->>M: invalid expired or already processed
  else slot not in allowed status
    SVC-->>M: meeting cannot be actioned in current status
  else valid
    alt actionType approve
      SVC->>DB: set slot status confirmed
      SVC->>MAIL: send confirmation to mentee and mentor
    else actionType reject
      SVC->>DB: set slot status cancelled
    end
    SVC->>DB: mark this token used and mark pair token used
    SVC-->>M: success message
  end

Note: the email-action path flips status directly and does not itself create Google Calendar events or refund tokens — those happen in the in-app approveMeeting / cancellation flows.

7. Join the live meeting

The student/mentor open ${FRONTEND_URL}/meetings/{meetingId}; the client authenticates and joins the Socket.IO room. MentorsController.getMeetingDetails (JWT, mentor/student only) and the public getBotMeetingDetails provide the room context. Join/leave and recording events are written to meeting_logs via MeetingLogsService.logEvent for the later participation gate.

sequenceDiagram
  participant FE as Meeting Room UI
  participant API as MentorsController
  participant SIO as Socket.IO server
  participant LOG as MeetingLogsService
  participant DB as PostgreSQL

  FE->>API: GET /api/mentors/meetings/:id/details
  API-->>FE: meeting and participant info
  FE->>SIO: join-room for slot
  SIO->>LOG: logEvent user_joined with role
  LOG->>DB: insert meeting_logs row
  Note over FE,SIO: WebRTC offer answer ice and recording handled in realtime doc
  FE->>SIO: leave-room or meeting-ended
  SIO->>LOG: logEvent user_left or meeting_ended
  LOG->>DB: insert meeting_logs row

See the realtime/meeting-room doc for full signalling and recording detail.

8. Meeting completion → participation gate → earnings + XP

SlotCompletionController.completeMeetingSlotCompletionService.completeMeeting. The service computes durationMinutes, sets status completed, fires a non-blocking student XP grant (mentor_call_completed, idempotent on slotId) and badge re-eval, then decides earnings:

  • Free slot (isFree=true) → no earnings.
  • No TokenUsage row → no earnings.
  • Otherwise call MeetingLogsService.shouldCreditEarnings — both mentor and student must each have ≥ 15 minutes of presence. If not, complete but don't credit.
  • If the gate passes and earnings weren't already credited, MentorEarningsService.addEarnings credits tokensUsed × TOKEN_VALUE (with the mentor multiplier) and sets earningsCredited=true / earningsCreditedAt. Double-credit is guarded by the earningsCredited flag.
sequenceDiagram
  participant FE as Meeting Room UI
  participant API as SlotCompletionController
  participant SVC as SlotCompletionService
  participant LOG as MeetingLogsService
  participant EARN as MentorEarningsService
  participant XP as StudentProgressService
  participant DB as PostgreSQL

  FE->>API: POST /api/slots/:slotId/complete
  API->>SVC: completeMeeting
  SVC->>DB: load slot with tokenUsage
  alt already completed and credited
    SVC-->>FE: already completed
  else proceed
    SVC->>SVC: compute durationMinutes set status completed
    SVC->>XP: grant mentor_call_completed xp non blocking
    alt slot isFree
      SVC->>DB: save slot no earnings
      SVC-->>FE: free meeting completed
    else paid
      SVC->>DB: read token usage for slot
      alt no token usage
        SVC->>DB: save slot
        SVC-->>FE: completed but earnings not credited
      else has token usage
        SVC->>LOG: shouldCreditEarnings checks 15 min each side
        alt participation insufficient
          SVC->>DB: save slot earningsCredited false
          SVC-->>FE: completed earnings withheld with reason
        else participation valid
          SVC->>EARN: addEarnings tokensUsed and duration
          SVC->>DB: set earningsCredited true and creditedAt
          SVC-->>FE: completed earnings credited amount
        end
      end
    end
  end

9. Cancellation (student request → mentor approval → refund)

Students cannot cancel directly. requestMeetingCancellation requires a confirmed meeting owned by the student, sets it to cancellation_requested, and notifies the mentor + all admins via the email queue. approveMeetingCancellation (mentor only) refunds 1 token, sets cancelled, deletes the Google Calendar events, and spawns a fresh available slot for the same time so it can be re-booked.

sequenceDiagram
  participant ST as Student Frontend
  participant MT as Mentor Frontend
  participant API as MentorsController
  participant SVC as MentorService
  participant TOK as TokenService
  participant GC as GoogleCalendarService
  participant Q as emailQueue
  participant DB as PostgreSQL

  ST->>API: POST /api/mentors/meetings/:id/request-cancellation
  API->>SVC: requestMeetingCancellation
  alt not confirmed or not owner
    SVC-->>ST: error cannot request
  else ok
    SVC->>DB: set status cancellation_requested
    SVC->>Q: queue emails to mentor and all admins
    SVC-->>ST: cancellation requested awaiting mentor
  end

  MT->>API: PUT /api/mentors/meetings/:id/approve-cancellation
  API->>SVC: approveMeetingCancellation
  SVC->>DB: begin transaction
  SVC->>TOK: refund 1 token to student
  SVC->>DB: set status cancelled
  SVC->>DB: insert new available slot for same time
  SVC->>DB: commit transaction
  SVC->>GC: delete mentor and student calendar events
  SVC->>SVC: syncMentorSlots
  SVC-->>MT: meeting cancelled and refunded

The generic changeMeetingStatus path (PUT .../meetings/:meetingId/status) routes a cancelled status through handleCancellation, which also refunds, deletes calendar events, and re-creates an available slot. A student calling it can only move a meeting to cancellation_requested.

10. Post-meeting feedback (dual-sided)

createSlotsFeedback stores one slots_feedback row per slot, with separate columns for the student's view of the mentor and the mentor's view of the student. On a student submission it queues a update-mentor-rating job (database queue) so the mentor's aggregate rating is recomputed.

sequenceDiagram
  participant FE as Frontend
  participant API as MentorsController
  participant SVC as MentorService
  participant DB as PostgreSQL
  participant Q as databaseQueue

  FE->>API: POST /api/mentors/users/:userId/meetings/:id/feedback
  API->>SVC: createSlotsFeedback with submittedBy
  SVC->>DB: load slot
  SVC->>DB: find existing feedback for slot
  alt exists
    SVC->>DB: update student side or mentor side fields
  else new
    SVC->>DB: insert feedback row with one side filled
  end
  alt submittedBy student
    SVC->>Q: queue update-mentor-rating job
  end
  SVC-->>FE: saved feedback

11. Super Mentor 1:1 coaching meeting

SuperMentorController.createMeetingSuperMentorService.createMeeting. Validates an active SuperMentorAssignment, saves a SuperMentorMeeting (scheduled), and emails the student. Marking a 1:1 Onboarding meeting completed is gated on the student's diagnostic test being done.

sequenceDiagram
  participant FE as Super Mentor Frontend
  participant API as SuperMentorController
  participant SVC as SuperMentorService
  participant DB as PostgreSQL
  participant Q as emailQueue

  FE->>API: POST /api/supermentor/meetings
  API->>SVC: createMeeting
  SVC->>DB: verify active assignment for student
  alt not assigned
    SVC-->>FE: student not assigned
  else assigned
    SVC->>DB: insert super_mentor_meeting status scheduled
    SVC->>Q: queue sm-meeting-scheduled email to student
    SVC-->>FE: meeting scheduled
  end

  FE->>API: PATCH /api/supermentor/meetings/:id/status completed
  API->>SVC: updateMeetingStatus
  alt onboarding and diagnostic pending
    SVC-->>FE: blocked complete diagnostic first
  else allowed
    SVC->>DB: update status
    SVC-->>FE: status updated
  end

Background jobs & async

Mechanism Where Trigger / schedule Effect
cleanupQueue repeatable job QueueService.scheduleSlotCleanup (called from src/index.ts) Every 15 minutes (despite "24h" comments) Runs SlotCleanupService cleanups (see below).
emailQueue jobs MeetingNotificationHelper, MentorService, MeetingActionTokenService, SuperMentorService On booking / approval / cancellation / scheduling Meeting-approval, meeting-confirmation, cancellation-requested (mentor + admins), mentor approval confirmation, sm-meeting-scheduled. Retries: 3 attempts, exponential backoff.
databaseQueue update-mentor-rating MentorService.createSlotsFeedback On student feedback Recomputes mentor aggregate rating.
Student XP (fire-and-forget) SlotCompletionService.creditStudentForCompletedSlot On slot completion Grants mentor_call_completed XP + badge re-eval, idempotent on slotId.
Socket.IO logging MeetingLogsService.logEvent On join/leave/start/end/recording events Writes meeting_logs rows powering the participation gate.

SlotCleanupService cleanups (run by cleanup.worker.ts):

  1. cleanupExpiredUnbookedSlots — deletes past slots that are isAvailable with no studentId. Includes a hard safety check that refuses to delete any row that has a studentId.
  2. expireUnacceptedMeetings — slots stuck in under_review whose startDateTime has passed are set cancelled, the token is refunded, and a fresh available slot is created.
  3. cleanupExpiredRequestedSlots — deletes pending requested_slots whose parsed end time is more than 1 hour in the past.
  4. updateMentorSlotStatistics — keeps mentor slotsLeft in sync.

External integrations

Integration Where Env vars Failure / fallback
Google Calendar / OAuth GoogleCalendarService GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REDIRECT_URI, APPLICATION_MEETING_TIME_ZONE (default Asia/Kolkata) Per-user tokens in google_auth_tokens. If a user hasn't connected, createCalendarEvent returns null and the meeting still proceeds. All calendar ops are wrapped so failures are logged, not fatal. Events use sendUpdates: 'all' and email/popup reminders (1 day / 30 min).
AWS S3 (recordings) MeetingRecordingService, RecordingController AWS_*, AWS_S3_BUCKET_NAME Presigned multipart upload; metadata tracked with RecordingStatus. Detail in realtime doc.
Email (SMTP via Nodemailer) EmailService + emailQueue EMAIL_USER, EMAIL_PASS All meeting emails are queued and best-effort; send failures never roll back the booking/approval/cancel.
Redis MentorService (category list, leaderboard), SlotCompletionService (paused-ms) REDIS_HOST, REDIS_PORT Cache reads/writes are wrapped — on error the code falls back to the DB.
Frontend meeting link GoogleCalendarService FRONTEND_URL (default http://localhost:3000) Builds /meetings/{id}.

Feature flags / config

  • MAX_SLOT_REQUESTS — cap on a student's pending availability requests (default 5).
  • TOKEN_VALUE — rupee value per token used in earnings (@/utils/tokenValue).
  • ALLOW_EARLY_MEETING_JOIN, MEETING_JOIN_BUFFER_MINUTES — early-join behaviour (realtime doc).
  • /api/student/batch-meetings is part of Token-Governance routes (feature-flagged OFF by default).

Status lifecycles

Slot / meeting (MeetingStatus)

stateDiagram-v2
  [*] --> tentative : mentor publishes availability
  tentative --> under_review : student books and token held
  under_review --> confirmed : mentor approves in app or email
  under_review --> cancelled : mentor rejects or auto expire refund
  confirmed --> cancellation_requested : student requests cancel
  cancellation_requested --> cancelled : mentor approves refund
  confirmed --> completed : meeting completed
  completed --> [*]
  cancelled --> [*]
  note right of cancelled
    cancelling spawns a fresh
    available tentative slot
    for the same time window
  end note

meeting_requested is also accepted by the email-action path as a valid pre-confirm state.

Requested slot (RequestedSlotStatus)

stateDiagram-v2
  [*] --> pending : student requests availability
  pending --> approved : mentor opens a slot
  pending --> rejected : mentor declines
  pending --> [*] : auto-deleted by cleanup when expired

Meeting recording (RecordingStatus)

stateDiagram-v2
  [*] --> recording : capture started
  recording --> processing : capture stopped
  processing --> uploaded : S3 upload confirmed
  recording --> failed : capture or upload error
  processing --> failed : upload error
  uploaded --> deleted : admin soft-delete
  failed --> [*]
  deleted --> [*]

Super mentor meeting (SuperMentorMeeting.MeetingStatus)

stateDiagram-v2
  [*] --> scheduled : super mentor schedules
  scheduled --> completed : marked done with diagnostic gate
  scheduled --> cancelled : cancelled
  scheduled --> no_show : student did not attend
  completed --> [*]
  cancelled --> [*]
  no_show --> [*]

Edge cases, limits & gotchas

  • Body-based authorization on status change. PUT /api/mentors/users/:userId/meetings/:meetingId/status has no authMiddleware; it trusts requestorRole/requestorId in the request body and enforces "students may only request cancellation" in both the controller and MentorService. This is weaker than middleware-based auth — treat it as a known gotcha. Several mentor write routes (approve-meeting, request-cancellation, approve-cancellation, createSlot, request-meeting, feedback) are likewise mounted without authMiddleware.
  • Slots are single-row but cancellation forks. A cancelled booking is not reopened in place — a brand-new tentative available slot is inserted for the same window. The original row stays cancelled for history. This affects any query that assumes one row per time window.
  • slotsLeft is derived, not authoritative. It is decremented on booking but always reconciled by syncMentorSlots (count of future unbooked slots). Cleanup also recomputes it.
  • Earnings idempotency. earningsCredited guards double-payment; completion can be called repeatedly and will not re-credit. Free slots (isFree) and slots with no TokenUsage never credit.
  • Participation gate is strict. Both mentor and student need ≥ 15 minutes of logged presence (MIN_DURATION_SECONDS = 15 * 60). Short or one-sided calls complete but withhold earnings; the student still gets mentor_call_completed XP regardless (it fires before the earnings branch).
  • Email-action vs in-app approval diverge. The meeting-action token path only flips status and sends emails; it does not create calendar events. Full calendar creation happens only via the in-app approveMeeting. Tokens expire in 7 days and the pair is invalidated together.
  • Token preference order. Booking spends a non-expired promotional meet-token first, then a paid token; insufficient balance rolls the whole transaction back.
  • Auto-expiry refunds. A booking left under_review past its start time is auto-cancelled and refunded by the 15-minute cleanup job — so an un-approved request silently returns the token.
  • Recording bot route is public. GET /api/mentors/meetings/:id/bot-info is intentionally unauthenticated for the recording bot; it returns a reduced projection.
  • Multi-platform. These endpoints are platform-agnostic; the x-platform header is handled at the app/CORS layer, not within this domain's services.
  • Scheduling comment drift. Code comments say "every 24 hours" but the cleanup repeatable job is configured for 15 * 60 * 1000 ms (15 minutes).