Skip to content

feat(profile): editable identity + username-led leaderboard - #202

Merged
barunaniket merged 1 commit into
mainfrom
feat/editable-profile-username-leaderboard
Sep 7, 2026
Merged

feat(profile): editable identity + username-led leaderboard#202
barunaniket merged 1 commit into
mainfrom
feat/editable-profile-username-leaderboard

Conversation

@barunaniket

Copy link
Copy Markdown
Collaborator

Summary

Makes a student's identity editable from /profile (name, username, SRN, PRN), and reworks the CP Arena leaderboard to identify solvers by Solver / Name / SRN columns instead of a single SRN-else-PRN cell.

Nothing about a user's identity could be changed after registration — the profile page was read-only, there was no update endpoint, and PATCH /api/admin/users/[id] only toggles role flags. So a typo'd roll number was permanent, and a first year who registered before their SRN was assigned could never add it later, which blocked them outright since /api/monstr/join requires a non-null SRN.

Type of change

  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that changes existing behaviour) — the public leaderboard now shows different fields (see Notes for reviewers)
  • Documentation only — challenges/README.md + a committed authoring template

Related issues

Closes #

How was this tested?

Manually against a local dev server (npm run dev) with two registered accounts, plus a third seeded with name = NULL and srn = NULL to exercise both fallbacks.

Profile editing — PATCH /api/profile

Case Result
Save with no changes 200 — proves the ne(users.id, userId) self-exclusion works
Username already taken 409 "That username is taken."
SRN / PRN already taken 409 with the field-specific message
Invalid username (AB, has spaces) 400
Empty name / empty PRN 400
Empty request body 400 "Nothing to update."
SRN over the length cap 400
No session cookie 401
11 requests in a minute 429 (rate limit holds)
Rename, then log in with the new username 200; the old username correctly fails with 401
Clear the SRN field saves as null

Session survives a rename (it is keyed by user id, and getCurrentUser() re-reads the row per request, so edits are live with no re-issue).

Leaderboard/api/leaderboard returns display / name / identity on all three scopes (today, month, all). Verified an account with no SRN falls back to its PRN, and an account with no name renders . LeaderboardTable was server-rendered for both the today and month column sets to confirm the headers, the @ prefix, the fallbacks, and the "you" highlight.

Checksnpx tsc --noEmit, npm run lint (0 errors; the 4 warnings are pre-existing and unchanged on a clean tree), npm run test (72/72), npm run build, npm run cf:build (OpenNext bundle builds and includes /api/profile), npm run challenges:validate.

Checklist

  • I ran the checks locally and they pass: npx tsc --noEmit, npm run lint, npm run test, npm run buildnote: on Node 24, not Node 22; worth a look at the CI run
  • I did not commit unrelated package-lock.json changes — my working tree had the native-binding lockfile churn described in CONTRIBUTING.md §3 and it was deliberately left out of this branch
  • My change is focused and single-purpose — it is two related changes (profile editing + leaderboard identity); happy to split if you'd prefer
  • I added / updated tests where it made sense — no tests added; there is no existing harness for the leaderboard or for route handlers, and adding one felt out of scope here. Flagging rather than hiding it
  • I updated documentation where needed
  • For UI changes: it looks correct in both light and dark modenot visually verified. The new markup reuses the existing text-charcoal/… / mecha-* tokens, which are already theme-aware, but nobody has actually looked at it in dark mode
  • For problems: npm run challenges:validate passes
  • I did not commit any secrets

Notes for reviewers

1. The leaderboard change makes member data public — please make this a conscious call. /leaderboard and /api/leaderboard need no session, so the board now exposes every solver's login handle, real name, and SRN to anyone. username is what /api/auth/login looks users up by, so the board becomes an enumerable list of valid login identifiers paired with names and roll numbers.

This deliberately reverses 73505aa, whose message reads "The login handle (username) and email stay private" — and goes further by adding name and SRN. It is what was asked for, and passwords plus rate limiting remain the real gate, but it is a one-way door once it is live and indexed. The cheap mitigation, if wanted later, is a getCurrentUser() gate on the leaderboard route.

2. Known staleness. aggregateLeaderboard sits behind the existing 30s AGGREGATE_TTL_MS cache, so after an edit the month/all-time boards and the profile rank can show old values — and briefly drop the "you" highlight — for up to 30s. Today's board is uncached. Self-healing; no invalidation added.

3. No SRN/PRN format validation. None has ever existed in this codebase, so existing rows may not match any pattern we invent, and re-validating an untouched value on save would block otherwise-valid edits. Length-capped only. Say the word if you want a real PESU SRN regex plus a backfill plan.

4. Validation is now triplicated. USERNAME_RE + normalization + the uniqueness clash-check now exist in api/auth/register, api/admin/users, and updateProfile. Extracting a shared helper would mean touching two auth-critical routes this PR does not otherwise change, so I left it — worth a follow-up.

5. challenges/EXAMPLE.json needed two supporting changes to work at all: a !/challenges/EXAMPLE.json negation in .gitignore (the folder's *.json is ignored, so the template would never have reached contributors), and a skip in scripts/seed-challenges.ts (which otherwise publishes every .json it finds, including to production D1 on --target remote). It is still validated, so it cannot rot. I also corrected the README's schema table, which marked date as required — the Zod schema has it .nullish(), and omitting it is the meaningful "unscheduled pool" state.

6. No migration. username, name, srn and prn all already exist with their constraints.

Students could not change any of their identity after registering: the
profile page was read-only, there was no update endpoint, and the admin
PATCH route only toggled role flags. A typo'd roll number was permanent,
and a first year who registered before their SRN was assigned could never
add it — which blocked them outright, since joining a Monstr contest
requires a non-null SRN.

Profile editing
- updateProfile() in server/profile.ts: applies only the fields sent, so a
  partial PATCH works. Normalizes exactly as registration does (lowercase
  username, uppercase roll numbers) so edited values stay comparable to
  registered ones, and re-checks the three unique columns with
  ne(users.id, userId) — without that, every save collides with its own row.
  Returns a discriminated result, mirroring deleteUser().
- PATCH /api/profile: session-gated, rate limited 10/min per user. No
  session re-issue needed — getCurrentUser() re-reads the row per request,
  so edits are live immediately; sessionEpoch stays reserved for resets.
- ProfileEditForm: an "Edit details" toggle over the Identity panel.
  Email stays read-only; it drives the OTP verification flow.
- No SRN/PRN format regex: none has ever existed, so existing rows may not
  match one we invent, and re-validating an untouched value would block
  otherwise-valid saves. Length-capped instead.

Leaderboard
- Solver / Name / SRN columns, replacing the single SRN-else-PRN cell.
  Solver shows @username; SRN falls back to PRN; a missing name renders "—".
- Board identity moves back to username across all five comparison sites,
  so the "you" highlight and profile rank matching stay correct.
- Note: the board is unauthenticated, so this makes usernames, real names
  and SRNs public. Reverses 73505aa, which had hidden them deliberately.

No migration: every column already exists.
@vercel

vercel Bot commented Sep 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
code-chef-pesuecc-chapter Ready Ready Preview Sep 7, 2026 8:54pm UTC

@barunaniket
barunaniket merged commit b9144e0 into main Sep 7, 2026
3 checks passed
@barunaniket
barunaniket deleted the feat/editable-profile-username-leaderboard branch September 7, 2026 21:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant