Student Portal — Profile, Activity & Dashboard¶
The student-facing surface of the MAS suite: the profile a learner fills in, the dashboard they land on, the activity feed and progress roll-up that track their journey, the profile-view analytics mentors generate, and the consolidated AI student-profile endpoint an agent calls to reason about a single learner. This domain is the read/write hub that stitches together enrollments, applications, quizzes, the token/XP economy, and the external Mr Learn / Mr Test platforms into one coherent "what is this student doing" view.
Status: documented from source on this branch.
Overview¶
The Student Portal domain answers three questions for three audiences:
- "Who am I and is my profile done?" — the learner editing their
StudentProfile, uploading a resume, verifying email changes, watching a profile-completion percentage climb. Backed byStudentProfileService(src/services/StudentProfileService.ts) and thestudent.controllerprofile methods. - "What should I do next?" — the learner's dashboard: enrolled courses, upcoming classes, pending assignments, deadlines, recent activity, announcements, application/PAP status, and a streak/level ribbon. Assembled by
StudentController.getDashboardData(src/controllers/student.controller.ts) with help fromDashboardService(src/services/DashboardService.ts) andStudentProgressService. - "How is this student really doing?" — two analytic lenses: a mentor/expert viewing a student profile (tracked by
ProfileServiceasProfileViewrows), and an AI agent or batch-lead pulling a 360 snapshot fromAiStudentProfileService(src/services/AiStudentProfileService.ts), which fuses Mr Test diagnostics, Mr Learn course progress, and generated insights.
Personas / roles touched:
| Persona | Role(s) | What they do here |
|---|---|---|
| Student / learner | USER |
Edits profile, sees dashboard, accrues activity & XP |
| Mentor / expert | EXPERT |
Views a student profile (/api/profile/student/:id), generating a ProfileView |
| Admin / batch lead | ADMIN, SUPERADMIN |
Views student 360 via batch dashboards |
| AI agent / internal tooling | (internal route, network-gated) | Pulls consolidated profile from /api/ai/students/:identifier/profile |
This domain sits in the Mr Mentor / MAS LMS slice of the backend. It owns a small set of entities but reads heavily from the LMS (Enrollment, Course, Module, Class, Quiz, Assignment), the application/onboarding flow (Application, MAS101 PAP), the gamification layer (StudentProgress, XpEvent, badges), and the external sync entities (MrLearn*, MrTest*).
Key concepts & entities¶
Glossary
- StudentProfile — the rich profile a learner fills in on the portal's Profile page (personal, address, education, career, social links, resume, notification prefs). Distinct from the
Userrow, which holds auth + a denormalized subset thatStudentProfileServicekeeps in sync. - Profile completeness — two different notions co-exist (see Status lifecycles):
User.isProfileComplete(a one-way flag flipped on when every profile section is filled) and the dashboard'sprofileCompletionpercentage (derived fromUser+ latestApplication). - StudentActivity — an append-only feed of meaningful learning events (quiz completed, module completed, etc.) shown as "recent activity" on the dashboard.
- StudentProgress — a per-student cached XP/streak/level snapshot (the gamification aggregate). Documented in depth in the engagement doc; surfaced here because the dashboard ribbon reads it.
- ProfileView — an analytics row recorded whenever one user views another user's profile (deduped to one row per viewer→viewed per hour).
- AI student profile — a read-only, computed 360 bundle (no table) that resolves a student by UUID / email / roll number and merges Mr Test + Mr Learn + insights.
- Stage —
User.stage(UserStage:SIGNUP=1,OTP_VERIFIED=2,PROFILE_COMPLETE=3) — the signup funnel position.
TypeORM entities owned by this domain
| Entity | Table | File | Notes |
|---|---|---|---|
StudentProfile |
student_profiles |
src/entities/StudentProfile.ts |
1:1 with User; resume/profilePicture are text (signed S3 URLs exceed 500 chars) |
StudentActivity |
student_activities |
src/entities/StudentActivity.ts |
Enum ActivityType; metadata JSON |
StudentProgress |
student_progress |
src/entities/StudentProgress.ts |
PK is userId; cached XP/streak/level snapshot |
ProfileView |
profile_views |
src/entities/ProfileView.ts |
Indexed on (viewerId, viewedUserId) and (viewedUserId, createdAt) |
Closely-read companion entities (owned elsewhere, consumed here): User (src/entities/User.ts), Application, Enrollment, Course, Module, Class, Quiz, Assignment, Announcement, XpEvent, and the external MrLearn* / MrTest* entities.
Architecture¶
flowchart TD
subgraph Clients["Clients"]
FE["Student portal (mas-website-live)"]
MENTOR["Mentor / Admin UI"]
AGENT["AI agent / internal tooling"]
end
subgraph Routes["Express routes"]
R1["/api/student (StudentRoutes)"]
R2["/api/student (createDashboardRoutes)"]
R3["/api/profile (ProfileRoutes)"]
R4["/api/ai (AiRoutes)"]
end
subgraph Controllers["Controllers"]
C1["StudentController"]
C2["StudentProgressController"]
C3["DashboardController (legacy)"]
C4["ProfileController"]
C5["AiStudentController"]
end
subgraph Services["Services"]
S1["StudentProfileService"]
S2["DashboardService"]
S3["StudentProgressService"]
S4["ProfileService"]
S5["AiStudentProfileService"]
S6["BadgeService"]
S7["AuthService (OTP)"]
S8["S3Service"]
end
subgraph Data["PostgreSQL (TypeORM)"]
D1["student_profiles"]
D2["student_activities"]
D3["student_progress / xp_events"]
D4["profile_views"]
D5["users / applications / enrollments"]
D6["mrtest_* / mrlearn_* (synced)"]
end
subgraph Ext["External"]
E1["AWS S3 (resume, photo)"]
E2["Groq LLM (AI recs)"]
E3["Gmail SMTP (email OTP)"]
end
FE --> R1 --> C1
FE --> R2 --> C1
FE --> R1 --> C2
MENTOR --> R3 --> C4
AGENT --> R4 --> C5
C1 --> S1 & S6 & S7 & S8
C1 --> S2
C2 --> S3 & S6
C3 --> S2
C4 --> S4
C5 --> S5
S1 --> D1 & D5
S2 --> D2 & D5
S3 --> D3
S4 --> D4 & D5
S5 --> D5 & D6 & E2
S6 --> D3
S7 --> E3
S8 --> E1
Note: /api/student is mounted twice in src/routes/index.ts — first createDashboardRoutes(...) then StudentRoutes.router. Both define overlapping paths (e.g. GET/POST /profile, GET /dashboard) wired to the same StudentController methods, so Express's first-match wins but the behavior is identical. The dashboard-routes mount adds the webinar endpoints and the narrow PATCH /profile/contact.
Data model¶
erDiagram
USER ||--o| STUDENT_PROFILE : "has one"
USER ||--o{ STUDENT_ACTIVITY : "generates"
USER ||--o| STUDENT_PROGRESS : "has snapshot"
USER ||--o{ PROFILE_VIEW : "is viewed in"
USER ||--o{ PROFILE_VIEW : "views"
USER {
uuid id PK
string email
string secondaryEmail
string fullName
string phone
string college
int graduationYear
string targetProfile
boolean isProfileComplete
int stage
int activeWarningCount
boolean escalatedToBatchLead
}
STUDENT_PROFILE {
uuid id PK
uuid userId FK
string firstName
string lastName
string email
string phone
date dateOfBirth
string addressLine1
string city
string state
string pincode
string highestQualification
string institutionName
int yearOfPassing
string fieldOfStudy
string targetProfile
string linkedinUrl
text profilePicture
text resume
simplearray skills
boolean emailNotifications
boolean whatsappAlerts
boolean calendarSync
timestamp createdAt
timestamp updatedAt
}
STUDENT_ACTIVITY {
uuid id PK
uuid userId FK
enum type
string title
text description
json metadata
timestamp createdAt
}
STUDENT_PROGRESS {
uuid userId PK
int totalXp
int meaningfulActions
smallint level
int currentStreak
int longestStreak
date lastActiveDate
int version
}
PROFILE_VIEW {
uuid id PK
uuid viewerId FK
uuid viewedUserId FK
string viewerType
string viewedUserType
timestamp createdAt
}
Notable enums / status fields
ActivityType(src/entities/StudentActivity.ts):course_started,module_completed,quiz_completed,assignment_submitted,class_attended,certification_earned.ProfileView.viewerType/viewedUserType:'user' | 'expert'.UserStage(src/types/UserStage.ts):SIGNUP=1,OTP_VERIFIED=2,PROFILE_COMPLETE=3.User.isProfileComplete: boolean, one-way latch (only ever flipped on).
API surface¶
All paths below are absolute (mount prefix + route). Auth column: auth = authMiddleware (JWT → req.user); expert = expertMiddleware; enrolled = isEnrolledMiddleware; none = no middleware on the route.
Profile (StudentController, mounted at /api/student)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| POST | /api/student/profile |
auth | Create/update the rich StudentProfile (upsert); syncs subset to User; re-evaluates badges |
| GET | /api/student/profile |
auth | Fetch own profile; resume rewritten to a 1h signed S3 URL; merges secondaryEmail from User |
| PUT | /api/student/profile |
auth | Update User-level fields (name, phone, college, etc.); flips isProfileComplete=true |
| PATCH | /api/student/profile/contact |
auth | Narrow update: phone + address only; rejected (409) once enrollment is complete |
| GET | /api/student/profile/complete |
auth | Returns { isComplete } (basic + education + career check) |
| GET | /api/student/profile/resume/download |
auth | Streams the saved resume through the backend (CORS workaround) |
| DELETE | /api/student/profile |
auth | Delete the StudentProfile row |
| POST | /api/student/profile/send-email-otp |
auth | Send OTP to a candidate new email (primary or secondary) |
| POST | /api/student/profile/verify-email-otp |
auth | Verify OTP and commit the primary/secondary email change |
Dashboard & progress¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/student/dashboard |
auth | Full dashboard payload (courses, deadlines, activity, application, profile completion) |
| GET | /api/student/me/progress |
auth | Streak/level/XP ribbon snapshot + unseen badge ids |
| GET | /api/student/me/badges |
auth | All badges for the user |
| POST | /api/student/me/badges/seen |
auth | Clear the unseen flag on all badges |
| GET | /api/student/batches |
none | Available batches (public listing) |
| GET | /api/student/webinars |
auth | Public webinars + per-user watch progress |
| GET | /api/student/webinars/:id |
auth | Single webinar + progress; increments view count |
| POST | /api/student/webinars/:id/progress |
auth | Upsert watch progress |
| POST | /api/student/webinars/:id/complete |
auth | Mark a webinar complete |
Legacy dashboard (DashboardController — still mounted)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/student/upcoming-classes |
none* | Upcoming classes (token verified inside handler) |
| GET | /api/student/pending-assignments |
none* | Pending assignments |
| GET | /api/student/recent-activity |
none* | Recent StudentActivity feed |
| GET | /api/student/announcements |
none* | Active announcements |
| GET | /api/student/profile-completion |
none* | Mock profile-completion checklist |
* These legacy routes call authService.verifyToken(req) themselves rather than using authMiddleware; getProfileCompletion returns hardcoded mock data and is superseded by /api/student/dashboard.
Profile views (ProfileController, mounted at /api)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/profile/mentor/:mentorId |
auth | View a mentor profile; masks contact unless viewer has a booking; records a ProfileView |
| GET | /api/profile/student/:studentId |
auth + expert | Mentor views a student profile; records a ProfileView |
| GET | /api/profile/views |
auth | Paginated list of who viewed me |
| GET | /api/profile/views/stats |
auth | { totalViews, uniqueViewers } |
AI student profile (AiStudentController, mounted at /api/ai)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/ai/students/:identifier/profile |
none | Consolidated 360 (Mr Test + Mr Learn + insights). :identifier = UUID, email, or roll number |
User journeys¶
1. Student completes / edits their profile¶
The learner fills the Profile page and saves. StudentProfileService.saveOrUpdateProfile upserts the student_profiles row and mirrors a subset onto the User row (name, phone, college, graduation year, branch, target profile, secondary email) so other surfaces stay consistent. When every section (basic + contact + education + resume) is filled, User.isProfileComplete latches on and a non-blocking badge re-evaluation fires (unlocks the "All Star" badge).
sequenceDiagram
participant FE as Student portal
participant API as POST /api/student/profile
participant SC as StudentController
participant SPS as StudentProfileService
participant DB as PostgreSQL
participant BS as BadgeService
FE->>API: profile fields in body
API->>SC: saveProfile
SC->>SPS: saveOrUpdateProfile userId and data
SPS->>DB: find User by id
alt user missing
SPS-->>SC: throw User not found
SC-->>FE: 500 Failed to save profile
else user exists
SPS->>SPS: sync name phone college etc onto User
SPS->>SPS: validate secondaryEmail public domain
SPS->>DB: upsert student_profiles row
SPS->>SPS: isProfileFullyComplete check
opt all sections filled
SPS->>DB: set User.isProfileComplete true
end
SPS->>DB: save User if changed
SPS-->>SC: saved profile
SC->>BS: evaluateForUser fire and forget
SC-->>FE: 200 saved profile
end
Resume read-back. On GET /api/student/profile, if resume is set the controller lazily imports S3Service and rewrites the stored value into a 1-hour signed URL (getStudentDocumentSignedUrl). If signing fails it falls back to the raw stored value. GET /api/student/profile/resume/download instead streams the object through the backend, because the S3 bucket CORS only allows the production origin.
Narrow contact edit. PATCH /api/student/profile/contact whitelists only phone + address fields, validates phone (10 digits) and pincode (6 digits), and refuses (409) once the latest Application is ENROLLED / BATCH_ALLOCATED / PAID. It also keeps Application.phone in sync so the application view does not show a stale number.
2. Verified email change (OTP)¶
Changing the primary or secondary email is gated by an email OTP so a learner cannot silently take over another account's address.
sequenceDiagram
participant FE as Student portal
participant API as Backend
participant SC as StudentController
participant AS as AuthService
participant MAIL as Gmail SMTP
FE->>API: POST /profile/send-email-otp with new email
API->>SC: sendEmailOtp
SC->>SC: validate email format
SC->>SC: check email not taken by another user
alt email already used
SC-->>FE: 409 already associated
else free
SC->>AS: sendOtp email
AS->>MAIL: deliver OTP
SC-->>FE: 200 OTP sent
end
FE->>API: POST /profile/verify-email-otp email otp field
API->>SC: verifyEmailOtp
SC->>AS: checkAndDeleteOtp no side effects
alt invalid or expired
SC-->>FE: 400 invalid OTP
else valid and field primary
SC->>SC: re-check email uniqueness
SC->>API: set User.email and save
SC-->>FE: 200 email updated
else valid and field secondary
SC->>SC: validate public domain
SC->>API: set User.secondaryEmail and save
SC-->>FE: 200 email updated
end
3. Activity is recorded across the app¶
StudentActivity is an append-only feed written by whichever subsystem produced the event — there is no single "record activity" endpoint. The clearest example is quiz submission: StudentQuizService.logQuizActivity writes a QUIZ_COMPLETED row (wrapped in try/catch so a logging failure never breaks the submission). In parallel, the gamification layer grants XP through StudentProgressService.grantXp, which is idempotent per (userId, type, sourceId). The dashboard later reads the last 10 StudentActivity rows.
sequenceDiagram
participant FE as Student portal
participant API as POST /api/student/quizzes/:id/submit
participant QS as StudentQuizService
participant DB as PostgreSQL
participant SPS as StudentProgressService
FE->>API: quiz answers
API->>QS: submitQuiz
QS->>DB: save QuizSubmission
QS->>DB: insert student_activities row QUIZ_COMPLETED
Note over QS,DB: logQuizActivity is try-catch wrapped so a write failure is swallowed
QS->>SPS: grantXp for the action
SPS->>DB: idempotent xp_events insert plus student_progress update
QS-->>FE: 200 submission result
Other writers seen in source: MissOzoneService, DemoSeedService, AskMasService, and the Mr Test sync controller. Activity rows for a user are bulk-deleted by database.worker during account cleanup.
4. Student dashboard analytics¶
The dashboard is one fat read. getDashboardData branches on whether the student has enrollments. Enrolled students get courses + stats + upcoming classes + pending assignments + a merged upcomingDeadlines feed + the recent-activity feed; non-enrolled students get a free-course discovery payload. Everyone gets the user block, profileCompletion, and (if present) the latest Application with its MAS101 PAP workflow.
sequenceDiagram
participant FE as Student portal
participant API as GET /api/student/dashboard
participant SC as StudentController
participant DB as PostgreSQL
FE->>API: bearer token
API->>SC: getDashboardData
SC->>DB: load User
alt user missing
SC-->>FE: 404 User not found
else found
SC->>DB: load enrollments with courses
SC->>DB: load global active announcements
alt has enrollments
SC->>DB: compute course stats
SC->>DB: upcoming classes via modules
SC->>DB: pending assignments via modules
SC->>DB: last 10 student_activities
SC->>SC: build upcomingDeadlines feed
else no enrollments
SC->>DB: load free published courses for discovery
end
SC->>DB: load latest Application
SC->>SC: calculateProfileCompletion User plus Application
opt application exists
SC->>DB: resolve batch and course names plus PAP workflow
end
SC-->>FE: 200 dashboard payload
end
The separate streak/level ribbon is fetched by GET /api/student/me/progress (see engagement doc): it reads the cached student_progress snapshot, computes per-axis percentages (xp / actions / streak), the blocking axis, and the list of unseen badges for the celebration modal.
5. Mentor views a student / student views a mentor (profile views)¶
Viewing a profile records a ProfileView, deduped to one row per viewer→viewed per hour. When a student views a mentor, contact info is masked with a lock glyph unless the viewer has a confirmed/completed/tentative Slots booking or an approved RequestedSlot with that mentor.
sequenceDiagram
participant FE as Viewer UI
participant API as GET /api/profile/mentor/:mentorId
participant PC as ProfileController
participant PS as ProfileService
participant DB as PostgreSQL
FE->>API: bearer token
API->>PC: getMentorProfile
PC->>PS: getMentorProfile mentorId and viewerId
PS->>DB: load mentor with mentorProfile
alt mentor missing
PS-->>PC: throw Mentor not found
PC-->>FE: 500 error
else found
PS->>DB: check Slots booking for viewer
opt no slot booking
PS->>DB: check approved RequestedSlot
end
alt no booking
PS->>PS: mask email phone linkedin with lock glyph
end
PS-->>PC: mentor profile
opt viewer not self
PC->>PS: trackProfileView viewer and mentor
PS->>DB: find recent view within last hour
alt no recent view
PS->>DB: insert profile_views row
else recent exists
PS-->>PC: reuse existing row no insert
end
end
PC-->>FE: 200 mentor profile
end
The reverse, GET /api/profile/student/:studentId, is expertMiddleware-gated and records a ProfileView with viewedUserType=user. Analytics for "who viewed me" come from GET /api/profile/views (paginated) and GET /api/profile/views/stats (totalViews + distinct uniqueViewers).
6. AI-generated student profile insights (360)¶
An agent or batch lead requests a single learner's full picture. AiStudentProfileService.getProfile first resolves the student by UUID, email, or roll number, then fans out to collect Mr Test submissions/analyses and Mr Learn enrollments/module progress in parallel, derives upcoming tests, builds deterministic insights, and finally appends a few Groq-generated recommendations grounded in the same facts. The AI step is fail-soft.
sequenceDiagram
participant AGENT as AI agent
participant API as GET /api/ai/students/:identifier/profile
participant AC as AiStudentController
participant AISP as AiStudentProfileService
participant DB as PostgreSQL
participant GROQ as Groq LLM
AGENT->>API: identifier UUID email or roll number
API->>AC: getProfile
AC->>AISP: getProfile identifier
AISP->>DB: resolveStudent try UUID then email then roll number
alt not resolvable
AISP-->>AC: null
AC-->>AGENT: 404 Student not found
else resolved
par collect in parallel
AISP->>DB: collectMrTest submissions and analyses
and
AISP->>DB: collectMrLearn enrollments and modules
end
AISP->>DB: collectUpcomingTests via batch and submissions
AISP->>AISP: buildInsights summary recs riskFlags
AISP->>GROQ: generateAiRecommendations grounded facts
alt groq ok
GROQ-->>AISP: 2 to 3 extra recs
AISP->>AISP: append with AI prefix
else no key or timeout or bad json
AISP->>AISP: keep algorithmic recs only
end
AISP-->>AC: profile bundle plus generatedAt
AC-->>AGENT: 200 data
end
The insights block also emits riskFlags such as no-passing-test-scores, guessing-pattern, all-courses-unstarted, learning-stale-14d+, missed-scheduled-test, and blocked-by-prereq, plus a one-line summary.
7. Admin views a student 360¶
Admins/batch leads do not hit a dedicated single-student endpoint in this domain; they reach student detail through batch dashboards (GET /api/admin/dashboard/batch/:batchName/students, handled by the admin controller / AdminDashboardService, src/services/AdminDashboardService.ts). For a deep per-student diagnostic snapshot they consume the same /api/ai/students/:identifier/profile payload as journey 6. The dashboard user block additionally exposes activeWarningCount and escalatedToBatchLead so staff can see at-risk students.
Background jobs & async¶
- Badge re-evaluation — fired non-blocking from
StudentController.fireBadgeEvaluationaftersaveProfileandupdateProfile; runsBadgeService.evaluateForUserin a detached async IIFE so it never delays or fails the HTTP response. - Daily-login XP —
StudentProgressService.recordDailyLoginis invoked on login (auth.controller) and from sync/quiz/slot-completion paths; idempotent per IST calendar day viasourceId. - XP grants —
StudentProgressService.grantXpwraps each grant in a transaction withSELECT … FOR UPDATEonstudent_progressand dedupes against thexp_eventsunique constraint; streak/action milestones recurse once (bounded byskipStreakUpdate). - Account cleanup —
database.worker(src/workers/database.worker.ts) deletes a user'sStudentActivityrows during account teardown. - Webinar view count —
GET /api/student/webinars/:idincrements the webinar view counter as a side effect (viaWebinarService.incrementViewCount).
This domain has no dedicated BullMQ queue, cron, socket events, or inbound webhooks of its own.
External integrations¶
| Integration | Where | Env / config | Failure / fallback |
|---|---|---|---|
| AWS S3 | resume + profile picture storage; signed-URL read; backend stream proxy | AWS_*, AWS_S3_STUDENT_DOCUMENTS_BUCKET |
Signed-URL failure falls back to raw stored value; stream errors return 500 |
| Groq LLM | extra AI recommendations in AiStudentProfileService |
GROQ_API_KEY, GROQ_TEXT_ENDPOINT (default api.groq.com/openai/v1/chat/completions), GROQ_TEXT_MODEL (default llama-3.3-70b-versatile) |
Missing key / timeout / bad JSON → returns [], deterministic recs only |
| Gmail SMTP | email-change OTP delivery (AuthService.sendOtp) |
EMAIL_USER, EMAIL_PASS |
OTP send failure surfaces as 500 |
| Mr Learn / Mr Test | source data for AI profile (synced tables mrlearn_*, mrtest_*) |
(sync jobs elsewhere) | Empty collections degrade to "no tests / no courses" insights |
No explicit feature flags gate this domain; the Groq enrichment self-disables when GROQ_API_KEY is unset.
Status lifecycles¶
User.isProfileComplete (one-way latch)¶
stateDiagram-v2
[*] --> Incomplete
Incomplete --> Complete : all sections filled (basic + contact + education + resume) OR PUT /profile
Complete --> Complete : further edits never regress it
StudentProfileService only ever sets isProfileComplete to true (never back to false); PUT /api/student/profile also sets it true unconditionally.
UserStage signup funnel¶
stateDiagram-v2
[*] --> SIGNUP
SIGNUP --> OTP_VERIFIED : email OTP verified at signup
OTP_VERIFIED --> PROFILE_COMPLETE : profile data added
note right of PROFILE_COMPLETE
Email-change OTP verify does NOT
advance stage (no side effects)
end note
Dashboard profile-completion checklist¶
calculateProfileCompletion computes a percentage from five items, derived from User + the latest Application:
stateDiagram-v2
[*] --> Personal : name + phone
Personal --> Education : college + graduationYear
Education --> Professional : profession + domain
Professional --> AppSubmitted : application not in DRAFT
AppSubmitted --> PaymentDone : onboardingFeePaid or paid status
PaymentDone --> [*]
Each completed item adds 20%. Note the legacy DashboardService.getProfileCompletion returns hardcoded mock data and is not the source of truth.
Edge cases, limits & gotchas¶
- Two profile-completeness definitions.
StudentProfileService.isProfileComplete(used byGET /profile/complete) requires basic + education + career (targetProfile). The latch insaveOrUpdateProfile.isProfileFullyCompleterequires basic + contact + education + resume (no career). They are intentionally different checks — do not assume one implies the other. /api/studentmounted twice. OverlappingGET/POST /profileand/dashboardroutes exist in bothcreateDashboardRoutesandStudentRoutes, wired to the same handlers. Adding a new/profile-prefixed route in only one of them can be shadowed by the first mount.- Hardening note (internal). A security/hardening observation for this area is tracked in the team's private notes (
internal/security-and-hardening-notes.md) and is intentionally not published on this site. - Secondary-email domain rule. A
secondaryEmailmust use a public domain (gmail/yahoo/outlook/…); the same validation runs in bothStudentProfileServiceandupdateProfile. Editing email requires OTP via the dedicated endpoints; the email-change OTP verify is side-effect-free (no stage change, noisVerifiedflip). - Profile-view dedup window is 1 hour and per viewer→viewed pair. Rapid repeat views do not inflate counts;
getUniqueViewersCountusesCOUNT(DISTINCT viewerId). - Resume/profilePicture columns are
text, notvarchar. Signed S3 URLs can exceed 500 chars and would otherwise fail the whole profile save. - Hardening note (internal). A security/hardening observation for this area is tracked in the team's private notes (
internal/security-and-hardening-notes.md) and is intentionally not published on this site. - Resolver last resort. When an identifier matches no user and no Mr Test submission,
resolveStudentstill returns a stub identity (roll-number echo) rather than null, so the AI profile can render an empty-but-valid shape. - Activity logging is best-effort.
logQuizActivityswallows errors; a failed activity write never blocks the underlying action. There is no idempotency onStudentActivity(unlikexp_events), so duplicate events are possible if a writer retries. - Multi-platform. The
x-platformheader routes products, but the profile/dashboard handlers here key only onreq.user.idand are platform-agnostic.
Related docs¶
- Student Engagement & Gamification — XP, streaks, levels, badges, risk scores, daily cards (the
StudentProgress/XpEventmachinery referenced by the dashboard ribbon). - Education & LMS — Courses, Batches, Quizzes — the
Enrollment,Course,Module,Class,Quiz,Assignmententities the dashboard reads. - Identity & Access —
User, JWT auth middleware, roles, signup/OTP,UserStage. - Mentorship & Meetings —
Slots/RequestedSlotbookings that gate mentor-profile contact unmasking. - Token Economy — adjacent learner-facing economy.