Repository navigation
feat(profile): show reputation badges and history - #27
Conversation
|
Implementation note for Trello card https://trello.com/c/6Fprw569: This PR builds the profile + badges layer on top of the existing reputation snapshot model rather than changing reputation scoring or voting weight. Implemented:
Verified locally:
|
|
Vercel preview is ready: https://hivelore-bdj31m4u9-kareemn1936s-projects.vercel.app Commit: 52e0938 |
GET /profiles/:username is unauthenticated, but the profile query filtered only on the user. contributionDrafts and loreEntries were selected with no status filter, so any visitor could read another user's unsubmitted draft titles and the world they were drafting in. - contributionDrafts: restrict to ContributionStatus.SUBMITTED - loreEntries: restrict to LoreStatus.PUBLISHED_CANON, so DRAFT, SUBMITTED, APPROVED_FOR_PUBLICATION and ARCHIVED stay private to the author The existing test stub returned fixture rows regardless of the query, so a missing filter could not surface. It now honours the nested `where` the way Prisma would, and the fixture carries an unsubmitted draft and a non-canon lore entry that the new test asserts are absent. Removing either filter fails two tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mcfarhat
left a comment
There was a problem hiding this comment.
Approving with one fix pushed directly to your branch (4fdf70d), since you're the only one on this now and it was small.
The badge work is solid
Tests genuinely bite — I mutated the logic and each of these failed a test: shifting the Canon Keeper threshold above the tested score, swallowing InvalidHiveUsernameError, and changing nextScore. Validation and the 404 path are properly covered.
What I fixed — a privacy leak on a public endpoint
GET /profiles/:username has no auth hook, and the profile query filtered only on the user. Both relations were selected with no status filter:
contributionDrafts: { orderBy: …, select: { title, status, world … }, take: 10 }
loreEntries: { orderBy: …, select: { title, status, slug … } }ContributionStatus is DRAFT | SUBMITTED, so any unauthenticated visitor could read another user's unsubmitted draft titles and which world they were drafting in. Same for non-canon loreEntries (DRAFT, SUBMITTED, APPROVED_FOR_PUBLICATION, ARCHIVED).
Fix:
contributionDrafts→where: { status: 'SUBMITTED' }loreEntries→where: { status: 'PUBLISHED_CANON' }
Why the tests didn't catch it — worth knowing for next time
The stub's findUnique returned the fixture rows regardless of the query, so a missing where could never surface: the mock answered the same whether you filtered or not. The fixture also only contained SUBMITTED/PUBLISHED_CANON rows, so there was nothing private to leak.
I changed the stub to honour the nested where the way Prisma does, and added an unsubmitted draft plus a non-canon lore entry to the fixture, with a test asserting neither appears in the response. Removing either filter now fails two tests — verified by mutation both ways.
That's the general lesson: a mock that ignores the query can only test your assumptions, not your code. When the stub answers identically for every query, absence-of-filter is invisible.
Gates
Quality and Vercel Preview both pass. Full api suite: 20 failures locally both before and after my change — all environmental (no local Postgres), zero regression from the fix.
Summary
Trello
Verification