Skip to main content
WorkProjects

LocalElo

Competitive ranking for BJJ academies

stable
View raw

Glicko-2 rating platform for competitive communities. Built for BJJ academies, esports teams, chess clubs, racket sport ladders, and any group that runs its own ranking. Each organization gets an isolated space, its own leaderboards, its own members, and its own rating history.

What it is

A multi-tenant ranking system. Organizations create leaderboards. Members log matches. Ratings update via Glicko-2 on match completion. Admins approve members, invalidate matches, and mint invite links. Rating history is charted per leaderboard, per member.

By the numbers

MetricValue
Unit tests (invite system)35
Integration tests (tRPC routers)36
Total tests passing71
Starting rating / RD / volatility1500 / 350 / 0.06
Glicko-2 state per playerrating + RD + volatility
XP league tiers6 (bronze to champion)
Membership statusespending / approved / banned
Membership rolesmember / admin / owner

Architecture

src/
  app/
    (auth)/              Clerk sign-in / sign-up
    (dashboard)/         Authenticated app shell
      dashboard/         User home, org list, org creation
      org/[slug]/        Org home + leaderboard detail
      rankings/          Cross-org rankings
    api/
      trpc/[trpc]/       tRPC HTTP handler
      webhooks/clerk/    Clerk -> users table sync
    join/[slug]/         Public org join
    invite/[code]/       Invite-code redemption
  lib/                   tRPC client, Glicko-2 math
  middleware.ts          Clerk route protection
server/api/
  trpc.ts                Context, procedures, auth guards
  routers/               organizations, leaderboards, matches,
                         invites, ratings, activity, streaks,
                         achievements, goals, memberships
db/
  schema.ts              users, orgs, memberships, invites,
                         leaderboards, ratings, matches,
                         matchParticipants, ratingHistory,
                         streaks, XP, leagues, achievements,
                         challenges

Core entities: users synced from Clerk by clerkId; organizations with slug and visibility; memberships pairing (organizationId, userId) with per-org username; leaderboards scoped to an org; ratings holding the Glicko-2 triple plus W/L/D counters scoped to (leaderboardId, membershipId); matches with nullable winnerMembershipId for draws and soft-delete fields; matchParticipants with before/after rating snapshots; ratingHistory as immutable points for charting.

Key features

  • Multi-org isolation. Separate leaderboards, members, ratings, and history per org. A member can hold distinct ratings across orgs and across leaderboards within an org.
  • Glicko-2 ratings. Tracks rating, deviation, and volatility. New players gain certainty as they play. Inactivity inflates RD.
  • Member approval workflow. Public orgs auto-join. Private orgs queue pending members for admin approval.
  • Invite links. Signed codes with optional maxUses and expiresAt. Join by slug or by invite code.
  • Match logging. Members log their own results. Admins log matches between any two players. Ratings update atomically inside the match transaction; before/after snapshots are stored on every MatchParticipant.
  • Match invalidation with rollback. Soft-delete a match and every downstream rating mutation is reversed via stored ratingBefore snapshots. deletedAt / deletedBy audit trail retained. No orphaned history rows.
  • Analytics. Rating history charts (Recharts), win-rate trends, recent-opponent surfaces, head-to-head stats between any two players.
  • Gamification. Daily streaks with freeze tokens and weekend amulets, weekly XP leagues across six tiers, achievement badges, rating milestones, member-to-member challenges.

What makes it stand out

  • Rating math isolated. Glicko-2 lives in a single module of pure functions with no I/O. Rating updates happen inside the match- creation transaction so partial writes cannot corrupt the ladder.
  • True rollback on invalidation. Not a tombstone — the history tail is replayed and every participant's rating is reversed to its stored snapshot.
  • Per-org usernames. A user can be fardin in one gym and fi in another. Uniqueness enforced on (organizationId, username), not globally.
  • Webhook-driven user sync. Clerk user.created / updated / deleted events write directly to users; the app never reads Clerk at request time.
  • 71 tests, all passing. Coverage targets the rating engine, invite lifecycle, and every tRPC router mutation. Fixtures build an in-memory org + leaderboard + members via a factory module.
  • Vercel cron. Handles weekly league resets and daily streak reminders.

Stack

LayerTechnology
FrameworkNext.js 15 (App Router)
LanguageTypeScript
DatabasePostgreSQL
ORMDrizzle
APItRPC (end-to-end type-safe)
AuthClerk (webhook-synced users table)
UITailwind CSS 4, Radix primitives, shadcn/ui
ChartsRecharts
ValidationZod
TestsVitest
DeployVercel