LocalElo
Competitive ranking for BJJ academies
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
| Metric | Value |
|---|---|
| Unit tests (invite system) | 35 |
| Integration tests (tRPC routers) | 36 |
| Total tests passing | 71 |
| Starting rating / RD / volatility | 1500 / 350 / 0.06 |
| Glicko-2 state per player | rating + RD + volatility |
| XP league tiers | 6 (bronze to champion) |
| Membership statuses | pending / approved / banned |
| Membership roles | member / 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,
challengesCore 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
maxUsesandexpiresAt. 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
ratingBeforesnapshots.deletedAt/deletedByaudit 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
fardinin one gym andfiin another. Uniqueness enforced on(organizationId, username), not globally. - Webhook-driven user sync. Clerk
user.created/updated/deletedevents write directly tousers; 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
| Layer | Technology |
|---|---|
| Framework | Next.js 15 (App Router) |
| Language | TypeScript |
| Database | PostgreSQL |
| ORM | Drizzle |
| API | tRPC (end-to-end type-safe) |
| Auth | Clerk (webhook-synced users table) |
| UI | Tailwind CSS 4, Radix primitives, shadcn/ui |
| Charts | Recharts |
| Validation | Zod |
| Tests | Vitest |
| Deploy | Vercel |