feat(groups): add scoped leaderboards with invites - #524
Merged
junhoyeo merged 3 commits intoMay 24, 2026
Conversation
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
There was a problem hiding this comment.
8 issues found across 38 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/frontend/src/lib/groups/getGroupLeaderboard.ts">
<violation number="1" location="packages/frontend/src/lib/groups/getGroupLeaderboard.ts:1">
P2: All-time leaderboard ranking is non-deterministic for ties because SQL ordering lacks tie-breakers before rank assignment</violation>
</file>
<file name="packages/frontend/src/app/api/groups/[slug]/leave/route.ts">
<violation number="1" location="packages/frontend/src/app/api/groups/[slug]/leave/route.ts:41">
P2: Cache revalidation failure can mask a successful DB delete, causing inconsistent client-visible state</violation>
</file>
<file name="packages/frontend/src/app/(main)/groups/[slug]/GroupDetailClient.tsx">
<violation number="1" location="packages/frontend/src/app/(main)/groups/[slug]/GroupDetailClient.tsx:491">
P2: Clipboard copy is awaited without error handling, so copy failures can become unhandled promise rejections and leave the UI with no feedback.</violation>
</file>
<file name="packages/frontend/src/lib/groups/utils.ts">
<violation number="1" location="packages/frontend/src/lib/groups/utils.ts:37">
P1: Regex ordering bug: underscores removed before they can be converted to hyphens, causing avoidable slug collisions</violation>
</file>
<file name="packages/frontend/src/app/api/submit/route.ts">
<violation number="1" location="packages/frontend/src/app/api/submit/route.ts:434">
P2: Async group leaderboard revalidation before username path revalidation creates a cascading failure point — a DB query failure in revalidateUserGroupLeaderboards silently prevents revalidateUsernamePaths from running, leaving user profile pages stale.</violation>
</file>
<file name="packages/frontend/src/app/(main)/groups/[slug]/page.tsx">
<violation number="1" location="packages/frontend/src/app/(main)/groups/[slug]/page.tsx:71">
P1: Private group slug enumeration via differing HTTP status codes (200 vs 404)</violation>
</file>
<file name="packages/frontend/src/app/api/groups/[slug]/route.ts">
<violation number="1" location="packages/frontend/src/app/api/groups/[slug]/route.ts:110">
P2: PATCH accepts non-string `description`/`avatarUrl` values and silently turns them into `null`, which can wipe existing group data instead of rejecting invalid input.</violation>
</file>
<file name="packages/frontend/src/lib/groups/invites.ts">
<violation number="1" location="packages/frontend/src/lib/groups/invites.ts:188">
P1: TOCTOU race condition in acceptGroupInvite: invite eligibility is checked outside the transaction and never re-validated inside, allowing concurrent double-use of single-use invite tokens</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
IvGolovach
force-pushed
the
codex/group-scoped-leaderboards
branch
from
May 10, 2026 07:47
d5fc5ca to
9e0b55f
Compare
IvGolovach
force-pushed
the
codex/group-scoped-leaderboards
branch
from
May 12, 2026 17:11
9e0b55f to
edd29f2
Compare
Validation * Validation tier: Tier 3 — frontend database migration, access-controlled API routes, and user-facing group leaderboard flow. * git diff --check: PASS * git diff --cached --check: PASS * bunx vitest run: PASS, 25 files and 188 tests * bun run lint: PASS, 0 errors; existing warnings remain outside this change * bun run build: PASS * Ledger: not applicable — not required for selected validation tier/change family. * Version: not applicable — not required for selected validation tier/change family. Rollback * git revert HEAD
Validation * Validation tier: Tier 3 — post-review corrections for group access control, invite mutation atomicity, cache invalidation behavior, and leaderboard ordering. * git diff --check: PASS * git diff --cached --check: PASS * bunx vitest run packages/frontend/__tests__/lib/groupHelpers.test.ts packages/frontend/__tests__/lib/groupInvites.test.ts packages/frontend/__tests__/lib/getGroupLeaderboard.test.ts packages/frontend/__tests__/api/groupRoute.test.ts packages/frontend/__tests__/api/groupLeaveRoute.test.ts packages/frontend/__tests__/api/groupLeaderboardRoute.test.ts packages/frontend/__tests__/api/groupInviteJoinRoute.test.ts packages/frontend/__tests__/api/submitAuth.test.ts packages/frontend/__tests__/api/settingsSubmittedDataDelete.test.ts: PASS, 9 files and 28 tests * bunx vitest run packages/frontend/__tests__: PASS, 28 files and 195 tests * bun run lint: PASS, 0 errors; existing warnings remain outside this change * bun run build: PASS * Ledger: not applicable — not required for selected validation tier/change family. * Version: not applicable — not required for selected validation tier/change family. Rollback * git revert HEAD
…o 0009 PATCH /groups/[slug]/members/[userId]/role already rejects nextRole === "owner" and only permits actors whose role outranks the target's, so the sole owner of a group could still demote themselves to admin/member and orphan the group (no owner, no further role-change recourse without DB intervention). Add an explicit guard: when demoting an existing owner, count the remaining owners; if zero would remain, refuse with 400 and tell the caller to transfer ownership first. Matches the spirit of the existing /leave route, which already forbids owners from leaving without first transferring or deleting the group. Also renamed the migration from 0007_add_groups.sql to 0009_add_groups.sql to avoid filename collision with main's 0007_add_submitted_devices.sql and 0008_add_time_metrics_to_submissions.sql. Constraint: cannot promote-to-owner via this route (existing line 36 check) Rejected: enforce in canManageGroupRole | wrong layer — that helper doesn't know group cardinality Confidence: high Scope-risk: narrow Directive: do NOT relax this without first adding an explicit "transfer ownership" route — there is currently no recovery path for an orphaned group from the public API Not-tested: no unit test for the last-owner branch yet (filed as a followup)
junhoyeo
force-pushed
the
codex/group-scoped-leaderboards
branch
from
May 24, 2026 16:31
edd29f2 to
fc2fc1a
Compare
This was referenced May 25, 2026
Owner
|
@IvGolovach this has been merged to v3.0.0: https://github.com/junhoyeo/tokscale/releases/tag/v3.0.0 thanks for the contribution! |
This was referenced May 30, 2026
Closed
junhoyeo
added a commit
that referenced
this pull request
Jul 6, 2026
… modernize cache invalidation Review follow-up after merging main into this branch. Main moved on while this PR was open, so DELETE /api/settings/account is brought up to the current baseline: - Authenticate via getSessionFromRequest(request) instead of bare getSession(), inheriting the CSRF Origin-allowlist gate that #615 (f2bf654) retrofitted onto every other cookie-mutating settings route (submitted-data, devices, tokens). - Use normalizeUsernameCacheKey() for all user:/user-rank:/embed-user: revalidateTag calls and revalidateUsernamePaths() for path revalidation, matching the post-#485 baseline. - Call revalidateUserGroupLeaderboards() (#524) before the users-row delete, since the helper needs the group membership rows that the delete cascades away. Best-effort like all other revalidation. - Update the route tests for the request-based signature: requests now carry an Origin header exercised against the real getSessionFromRequest, plus new cases asserting missing/disallowed Origin yields 401 with no DB delete, mirroring the sibling routes' CSRF tests. Constraint: Cookie-auth mutations must pass the Origin allowlist convention adopted in #615 Constraint: revalidateUserGroupLeaderboards reads group membership rows that ON DELETE CASCADE removes Rejected: Bearer personal-token path for account deletion | web-Settings-UI action; session-only is the conservative default and answers the parity question raised in review Rejected: Invalidate group leaderboards after the delete | membership rows are already cascaded away by then Confidence: high Scope-risk: narrow Not-tested: CSRF_ALLOWED_ORIGINS env override branch (covered indirectly by requestSession's own tests)
junhoyeo
added a commit
that referenced
this pull request
Jul 6, 2026
…letion (#379) * feat(settings): add Danger Zone with self-service data and account deletion Add a Danger Zone section to the Settings page with two destructive actions: - Delete submitted data: removes leaderboard entries, profile stats, and usage history while keeping the account active - Delete account: permanently deletes the user and all associated data (sessions, tokens, submissions) via ON DELETE CASCADE Both flows use a 3-step confirmation modal (intent → warning → typed confirmation) matching the CLI's triple-confirmation pattern. New API route: DELETE /api/settings/account New tests: settingsAccountDelete.test.ts (5 cases) * Update packages/frontend/src/app/settings/SettingsClient.tsx Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(settings): gate account deletion behind CSRF Origin allowlist and modernize cache invalidation Review follow-up after merging main into this branch. Main moved on while this PR was open, so DELETE /api/settings/account is brought up to the current baseline: - Authenticate via getSessionFromRequest(request) instead of bare getSession(), inheriting the CSRF Origin-allowlist gate that #615 (f2bf654) retrofitted onto every other cookie-mutating settings route (submitted-data, devices, tokens). - Use normalizeUsernameCacheKey() for all user:/user-rank:/embed-user: revalidateTag calls and revalidateUsernamePaths() for path revalidation, matching the post-#485 baseline. - Call revalidateUserGroupLeaderboards() (#524) before the users-row delete, since the helper needs the group membership rows that the delete cascades away. Best-effort like all other revalidation. - Update the route tests for the request-based signature: requests now carry an Origin header exercised against the real getSessionFromRequest, plus new cases asserting missing/disallowed Origin yields 401 with no DB delete, mirroring the sibling routes' CSRF tests. Constraint: Cookie-auth mutations must pass the Origin allowlist convention adopted in #615 Constraint: revalidateUserGroupLeaderboards reads group membership rows that ON DELETE CASCADE removes Rejected: Bearer personal-token path for account deletion | web-Settings-UI action; session-only is the conservative default and answers the parity question raised in review Rejected: Invalidate group leaderboards after the delete | membership rows are already cascaded away by then Confidence: high Scope-risk: narrow Not-tested: CSRF_ALLOWED_ORIGINS env override branch (covered indirectly by requestSession's own tests) --------- Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
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.
Summary
/groupsUI flow for listing groups, creating groups, joining by invite, viewing a scoped leaderboard, copying invite links, leaving groups, and managing members.Why
The global leaderboard is useful for public ranking, but it does not cover private teams or custom communities that need scoped usage comparisons. This change keeps the existing global leaderboard intact and adds a membership-gated leaderboard surface for groups. Related to #462.
Diff scope
groups,group_members, andgroup_invitesthroughpackages/frontend/src/lib/db/migrations/0006_add_groups.sql, with schema relations and supporting indexes in Drizzle.packages/frontend/src/lib/groups/getGroupLeaderboard.tswith all-time, weekly, and monthly scoped ranking support over group membership./groups,/groups/new,/groups/[slug], and/groups/join/[token], plus navigation entry.Branch integrity
main3a9045bc1b9aa245ca44ffcdca68bdaaa09520ebIvGolovach:codex/group-scoped-leaderboards68ad3c4f25ac75f43b68ffbf24c7f1e2fee0ea370 behind, 1 ahead3a9045bc1b9aa245ca44ffcdca68bdaaa09520eborigin/mainis an ancestor of this branch.Commit integrity
68ad3c4f25ac75f43b68ffbf24c7f1e2fee0ea37 feat(groups): add scoped leaderboards with invitesDiff hygiene
git diff --name-status origin/main...HEAD: PASS, 38 intended frontend files changed.git diff --check origin/main...HEAD: PASS, no output.Validation mode and proof
git fetch --no-tags origin main:refs/remotes/origin/main: PASSgit diff --check: PASSgit diff --cached --check: PASSgit diff --check origin/main...HEAD: PASS, no outputbunx vitest run: PASS, 25 files and 188 testsbun run lint: PASS, 0 errors; existing warnings remain outside this changebun run build: PASSMigration notes
packages/frontend/src/lib/db/migrations/0006_add_groups.sqlgroups,group_members,group_invites.IF NOT EXISTS; group slugs, group membership, and invite token hashes are protected by uniqueness constraints.Runtime safety
Documentation integrity
Not applicable — no docs, runbooks, release procedures, commands, or operational instructions changed.
Rollback plan
Known residual risks