Skip to content

feat: tournament setup, roles, and permissions - #59

Merged
ethnjs merged 239 commits into
mainfrom
feat/tournament-setup-roles-and-permissions
Aug 13, 2026
Merged

feat: tournament setup, roles, and permissions#59
ethnjs merged 239 commits into
mainfrom
feat/tournament-setup-roles-and-permissions

Conversation

@ethnjs

@ethnjs ethnjs commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Summary

Replaces Tournament.blocks / Tournament.volunteer_schema / TournamentMembership.positions (opaque JSON blobs) with a relational role/permission system — TournamentRole + TournamentMembershipRole, tournament ownership, rank-gated staff management, visibility/verification fields, join codes, and a per-tournament audit log. This is foundational cleanup several planned features (staff management, public directory, Forms) depend on directly.

Also folded in, along the way: the tournament date timezone-offset bug fix (start_date/end_date datetime → date). #28

What changed

Backend — relational roles & permissions

  • New tables: TournamentRole (key, label, permissions[], rank) and TournamentMembershipRole (junction), replacing volunteer_schema["positions"] and TournamentMembership.positions.
  • Tournament.owner_id — direct FK to creator, not a role, not ranked, sits structurally above rank 1. Transferable only by current owner via POST /tournaments/{id}/transfer-ownership/; outgoing owner keeps their existing role assignments.
  • get_user_permissions() cut over to the relational tables, with an owner short-circuit that grants full permissions independent of role assignments.
  • Rank-gated staff management, split into two permissions rather than the single MANAGE_STAFF originally proposed:
    • MANAGE_ROLES — create/edit/delete/reorder role definitions
    • MANAGE_MEMBERS — assign/remove roles on a membership
    • Both are rank-bound (can't touch a rank at or above your own); only Owner and platform-admin bypass the rank check — MANAGE_TOURNAMENT does not bypass it, intentionally stricter than a flat reading of the original design.
  • Tournaments no longer auto-seed roles on creation — the owner already has full permissions via owner_id, so role setup is a deliberate POST /roles/apply-template/ action (default template, or custom) rather than implicit on create.
  • is_public (TD-controlled) / is_verified (admin-only, via PATCH /admin/tournaments/{id}/verify/) added for the future public directory.
  • TournamentJoinCode — same shape as the existing chapter join-code pattern. Redemption creates a bare TournamentMembership(status="interested") with no roles; staff assign roles afterward.
  • TournamentMembership.status: interested (default) / confirmed. Doesn't gate role/event assignment.
  • AuditLogEntry — records role create/update/delete/reorder, membership-role assignment, join-code lifecycle, staff invites, verification, archive/unarchive, and ownership transfer, each tied to tournament_id + actor_id. New paginated GET audit-log endpoint with action/actor filters.
  • Route/schema reorg: tournament.py / chapters.py split into tournament/ and chapter/ subpackages (core, admin, memberships, roles, join_codes, audit, setup_checklist) mirroring each other; permissions.py moved under core/tournament/.
  • Unified POST /join/ redemption endpoint (tournament + chapter codes) replacing separate per-type routes; join codes for chapters and tournaments merged into one table.
  • Daily auto-archive job for tournaments past end_date; archived tournaments lock general/role settings and deactivate all join codes.
  • Setup checklist derivation (roles configured / invite sent / dates+location set) surfaced on the overview page.

Backend — date timezone fix

  • start_date / end_date changed from datetime to date on the model and schemas — a date-only field needs no timezone handling at all.
  • Migration alters tournaments.start_date/end_date from timestamp to DATE.
  • Existing end_date < start_date validator continues to work unchanged against date objects; still returns 422.

Frontend

  • New roles settings surface: reorderable role list (drag rank, tie-group clustering), per-role edit page with permission toggles and a member-management tab, shared save-bar layout across roles/general/invites settings.
  • Members roster: search, role/status filters, sort, inline role editor (popover + chip input), side-panel member detail, rank-aware lock/remove affordances.
  • Audit log page: filterable, paginated, per-action summary/detail rendering (permission diffs, role snapshots, invite details).
  • Invites (join codes) page: create/deactivate/extend, copy-to-clipboard link, creator hover card, live expiry countdown.
  • /join?code= public redemption page; ?redirect= threaded through sign-in/sign-up/onboarding so a join link survives the auth detour.
  • General settings: visibility toggle, danger zone (archive/unarchive, transfer ownership, delete, leave), location/university xor combobox.
  • TournamentCard's fmt() now parses YYYY-MM-DD locally (lib/date.ts) instead of new Date(d), fixing the UTC-offset display shift; single vs. range date display uses an explicit equality check.
  • NewTournamentModal validates end-date-before-start-date (and past start dates) inline before submit, no round trip.
  • UI component pass done alongside this: SelectDropdown, RadioGroup/RadioOptionButtonGroup, canonicalized variant/locked props, new Toggle, Checkbox, ChipInput, Popover, HoverCard, SidePanel, Card components; toast system.

Deviations from the original issue

  • TournamentDeadline was never built. No model, migration, route, or audit action exists for it — the "TD-defined arbitrary deadlines" pillar of the design doc is entirely out of this PR. interest_due_at/registration_due_at/confirmation_due_at correctly never exist as fixed columns, but no relational replacement landed either.
  • registration_opens_at was scaffolded as a column mid-branch, then dropped before merge — never shipped, no user-facing impact.
  • default_day_start/default_day_end were not implemented.
  • MANAGE_STAFF was split into MANAGE_ROLES + MANAGE_MEMBERS (see above) rather than shipped as a single permission.
  • Roles are not auto-populated on tournament creation — this was a deliberate call since the owner already has full access without any roles existing.

Test plan

  • backend/tests/api/tournament/ — role CRUD incl. rank bounds, rank-gated membership-role assignment (tied ranks, self-modification, owner/admin bypass), ownership transfer (old owner keeps roles), join-code creation/redemption, audit log entries per action, setup checklist, admin verify route.
  • backend/tests/api/chapter/ and test_join.py — unified join-code redemption across chapter/tournament.
  • Date fields: tests send/assert date-only strings; 422 on end-before-start retained.
  • pytest full suite green (see CI).
  • Manually verified role reorder, rank-lock UI, join-link → onboarding → auto-redeem flow, and TournamentCard date display in UTC-8/UTC+9.

ethnjs added 30 commits August 1, 2026 10:56
…ssion expansion with explicit per-role lists
…MembershipRole tables, add owner short-circuit
…packages, extract role schemas and membership-role assignment routes
…apters and tournaments, add TournamentJoinCode routes and admin verify endpoint
…, drop admin special-case from tournaments/me, use require_membership() dependency on GET /tournaments/{id}
…ubpackage (core, admin, memberships, join_codes), mirroring the tournament structure
…ion PATCH and dedicated deactivate DELETE route
…chema/blocks/positions JSON columns, DEFAULT_POSITIONS→DEFAULT_ROLES with rank
ethnjs added 16 commits August 12, 2026 11:47
…sign-in exemptions already covered by proxy.ts
@ethnjs ethnjs linked an issue Aug 13, 2026 that may be closed by this pull request
@ethnjs ethnjs added database Updates database schemas or models tournaments labels Aug 13, 2026
@vercel

vercel Bot commented Aug 13, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
nexus Ready Ready Preview Aug 13, 2026 1:35am

@railway-app

railway-app Bot commented Aug 13, 2026

Copy link
Copy Markdown

🚅 Deployed to the nexus-pr-59 environment in nexus

Service Status Web Updated (UTC)
nexus ✅ Success (View Logs) Aug 13, 2026 at 1:35 am

@ethnjs ethnjs linked an issue Aug 13, 2026 that may be closed by this pull request
@railway-app
railway-app Bot temporarily deployed to nexus / nexus-pr-59 August 13, 2026 01:27 Destroyed
@ethnjs
ethnjs marked this pull request as ready for review August 13, 2026 01:27
@railway-app
railway-app Bot temporarily deployed to nexus / nexus-pr-59 August 13, 2026 01:35 Destroyed
@ethnjs
ethnjs merged commit f9e6a4f into main Aug 13, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend database Updates database schemas or models frontend tournaments

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: tournament model rebuild — roles, permissions, and visibility fix: tournament date offset

1 participant