feat(alumni): add verified alumni directory with capacity-guarded mentorship (fixes #283) - #288
Open
MOHITKOURAV01 wants to merge 1 commit into
Open
Conversation
Gives the school a way to keep in touch with people who have left, and current students a way to ask them for guidance. The two things this module is actually about: - Nothing reaches a student until staff have verified it. The browse query fixes verificationStatus to 'verified' rather than taking it from a parameter, an unverified profile 404s even by direct link, and the field is absent from the document the create handler builds so a request cannot verify itself. - Contact details are released by an accepted mentorship, not by browsing. redactFor() is the single place that decides this, so there is one answer rather than one per handler. A directory that hands out everyone's email is a directory alumni stop registering for. Accepting a request is the only operation that consumes something finite, so it is the one that is atomic: capacity and the pending check both live in the filter of one findOneAndUpdate, with the counter incremented in the same write. An alumnus who offered two places cannot end up with three because three students were accepted in the same minute — and the person let down there is a volunteer. Editing the identity fields of a verified profile sends it back to the queue, so "verified" cannot come to mean "was verified once, possibly describing somebody else". Closes Sitaram8472#283
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related Issue
Closes #283
Description
Gives the school a record of the people who have left, and current students a way to ask them for guidance. Two things drive the whole design.
1. Nothing reaches a student until staff have verified it
This is the reason the module exists in the form it does. Putting an unverified stranger's contact details in front of a child is the failure mode, so verification is enforced in three places rather than in the UI:
verificationStatus: 'verified'in the filter — it is not derived from a query parameter, so an unverified profile cannot be reached by asking for it.GET /profiles/:idreturns 404, not 403, for an unverified profile the caller does not own. A 403 confirms the profile exists, which is itself a small disclosure.verificationStatus,verifiedByandverifiedAtare absent from the document the create handler constructs. A request carryingverificationStatus: 'verified'has that field dropped rather than honoured.Editing the identity fields of a verified profile sends it back to the queue. Otherwise "verified" quietly comes to mean "was verified once, possibly describing somebody else now".
2. Contact details are released by an accepted mentorship, not by browsing
An alumni directory complete with everyone's email is how you get alumni to stop registering. Email, phone and LinkedIn are stripped from every response except for:
redactFor(viewer)on the schema is the single place that decides this, in the same style asMeetingSlot.redactFor— so there is one answer rather than one per handler, and adding an endpoint later cannot accidentally leak by forgetting a rule. Other students' requests are stripped for the same reason: a directory showing who else asked for help with what is a directory nobody uses honestly.The atomic bit
Accepting a request is the only operation here that consumes something finite, so it is the only one that has to be atomic. Both halves of the invariant sit in the filter of one
findOneAndUpdate:with the status flip and the counter increment in the same write. An alumnus who offered two places cannot end up with three because three requests were accepted in the same minute — and the person let down there is a volunteer, which is a good way to lose volunteers.
activeMenteeCountexists precisely so the check is expressible in the filter. Counting the array in application code would move the check outside the write, which is the race it is there to prevent.Completing a mentorship returns the seat, guarded on the request still being
acceptedso a double-tap cannot decrement the counter twice and leave the mentor looking free when they are not.Small things I decided deliberately
Pages / Components Added or Modified
backend/models/AlumniProfile.js— profile, verification, mentorship requests,redactFor()backend/controllers/alumniController.js— directory, verification queue, the capacity-guarded acceptbackend/routes/alumniRoutes.js—/api/alumnibackend/server.js— route registration (2 lines)frontend/src/pages/Alumni.jsx— directory with filters, request dialog, my-requestsfrontend/src/components/alumni/AlumniVerificationPanel.jsx— the office's approve/reject queuefrontend/src/App.jsx—/alumniroute (8 lines)Screenshots
Not attached — the directory is empty without seeded alumni and a shot of an empty state does not tell you much. I can seed a demo set and add screenshots if that would help review.
Checklist
npx eslint .passes — 18 problems, the same count as pristinemainnpx vite buildsucceeds;Alumnicode-splits into its own chunknode --checkclean on every backend fileNotes for review
The place I would most welcome a second opinion is the redaction rule. I chose "contact details unlock on acceptance" rather than "on request" because a request costs the student nothing, so unlocking on request would let anyone harvest the directory by requesting everybody. If you would rather the office released details manually instead, that is a small change to
redactForand nowhere else — which is the reason it is in one place.No
TeacherDashboard.jsxtab: the verification queue lives inside the/alumnipage itself, which keeps this branch off the most contended file in the repo. Merges cleanly againstmainand against every other open PR, in both orders.