ReviewOS

stacks/wildloop

Clubs: backend model/endpoints + wire Create/Join buttons

#964
Closed glennmichael123 opened this 9 days ago ·
9 days ago

Problem

Clubs are pure frontend fiction. clubs.stx Create/Join buttons have no @click handlers and only mutate seed seedClubs; there is no Club model/table/endpoint.

Acceptance

  • Club model + membership; /api/clubs CRUD + join/leave.
  • Wire Create (modal/form) and Join buttons; club detail page (members, feed, leaderboard).
9 days ago

Done across 05d0843, 657131d, e516b8c.

Backend — Club + ClubMember models (club_members has a unique (club_id, user_id) index); full CRUD + membership:

  • GET /clubs — member counts + this-week distance/activity stats DERIVED from rows; private clubs only listed to members
  • GET /clubs/{id} — members (names + roles), recent member-activity feed, weekly-distance leaderboard; private clubs gated
  • POST /clubs — create + auto-enroll creator as 'owner'
  • POST /clubs/{id}/join — idempotent join/leave (owners can't leave; db dedup)
  • DELETE /clubs/{id} — owner-only, cascades memberships

Frontend — clubs.stx hydrates from the API; the Create button opens a real create-club modal; Join buttons toggle membership optimistically; club names link to a new club/[id].stx detail page (header + join/delete, recent feed, leaderboard, members, loading/error/not-found). useClubs composable + game-api client + store helpers.

Security fix from adversarial review: the public /clubs reads originally fell back to a body user_id for the private-club gate — an anonymous HTTP caller could pass ?user_id=<a member id> to unmask private clubs (IDOR). Fixed with a harness-only fallback that's null over real HTTP, dropped the raw member-id roster from the public listing, and added a regression check (spoofed ?user_id → 403).

Verified by scripts/verify-clubs.ts (25 checks incl. the IDOR regression + cascade delete). All suites green.

Sign in to comment on this issue.