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
slotsis reused through its whole life: it starts as an available template (isAvailable=true, no student), then gets astudentIdand a non-tentativestatuswhen 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=trueandstudentId IS NULL. The count of future ones is mirrored ontoMentor.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.SuperMentorMeetingMeetingStatus: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(seesrc/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=true—MentorBookingsControllerprojection overSlotsfor the student dashboard (src/routes/student.routes.ts).GET /api/student/batch-meetings?upcoming=true—StudentMeetingsControlleroverBatchMeeting(mounted viatokenGovernance.routes.ts; LMS-owned).
User journeys¶
1. Browse mentors by category / subcategory¶
MentorsController.getMentorsByCategory → MentorService.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.createSlot → MentorService.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
5. Mentor approves the booking → Calendar + meeting link + emails¶
MentorsController.approveMeeting → MentorService.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}(seeGoogleCalendarService.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=xxx →
MeetingActionTokenService.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.completeMeeting → SlotCompletionService.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
TokenUsagerow → 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.addEarningscreditstokensUsed × TOKEN_VALUE(with the mentor multiplier) and setsearningsCredited=true/earningsCreditedAt. Double-credit is guarded by theearningsCreditedflag.
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
changeMeetingStatuspath (PUT .../meetings/:meetingId/status) routes acancelledstatus throughhandleCancellation, which also refunds, deletes calendar events, and re-creates an available slot. A student calling it can only move a meeting tocancellation_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.createMeeting → SuperMentorService.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):
cleanupExpiredUnbookedSlots— deletes past slots that areisAvailablewith nostudentId. Includes a hard safety check that refuses to delete any row that has astudentId.expireUnacceptedMeetings— slots stuck inunder_reviewwhosestartDateTimehas passed are setcancelled, the token is refunded, and a fresh available slot is created.cleanupExpiredRequestedSlots— deletespendingrequested_slotswhose parsed end time is more than 1 hour in the past.updateMentorSlotStatistics— keeps mentorslotsLeftin 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-meetingsis 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_requestedis 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/statushas noauthMiddleware; it trustsrequestorRole/requestorIdin the request body and enforces "students may only request cancellation" in both the controller andMentorService. 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 withoutauthMiddleware. - Slots are single-row but cancellation forks. A cancelled booking is not reopened in place — a
brand-new
tentativeavailable slot is inserted for the same window. The original row stayscancelledfor history. This affects any query that assumes one row per time window. slotsLeftis derived, not authoritative. It is decremented on booking but always reconciled bysyncMentorSlots(count of future unbooked slots). Cleanup also recomputes it.- Earnings idempotency.
earningsCreditedguards double-payment; completion can be called repeatedly and will not re-credit. Free slots (isFree) and slots with noTokenUsagenever 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 getsmentor_call_completedXP regardless (it fires before the earnings branch). - Email-action vs in-app approval diverge. The
meeting-actiontoken path only flips status and sends emails; it does not create calendar events. Full calendar creation happens only via the in-appapproveMeeting. 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_reviewpast 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-infois intentionally unauthenticated for the recording bot; it returns a reduced projection. - Multi-platform. These endpoints are platform-agnostic; the
x-platformheader 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 * 1000ms (15 minutes).