MSCL Website is a real‑time leaderboard and stats site for the Minecraft Speedrunning Community Leagues weekly event.
All the data is pushed by the organizers, and this codebase is designed to be a "dumb data layer."
- Frontend: Astro + React + TypeScript
- Styling: Tailwind + shadcn/ui + custom components
- Backend: Convex
Organizer tooling pushes players, matches, and week transitions via HTTP. The website is read‑only for the public.
High‑level flow:
- Initial setup
- Organizer scripts call
POST /api/write/playersto create all players and assign them to starting leagues with initial ELOs. This step is only required before the first week starts and is not supposed to be used after.
- Organizer scripts call
- During a week
- For each match, the organizer app calls
POST /api/write/match. - If scores or times are corrected, the same
(weekNumber, leagueTier, matchNumber)is re‑posted, and Convex overwrites the prior results.
- For each match, the organizer app calls
- Week transition phase
- Organizer tooling computes the next week’s leagues.
- That tooling calls
POST /api/write/weeks/transitionwith:- The week just finished (
weekNumber). - The upcoming week number (
newWeek). - The full “next state” of all players.
- The week just finished (
- Convex writes
weeklyStandingsfor the finished week and advancesisCurrentto the new week.
The schema lives in convex/schema.ts. Below is the simplified schema:
playersname: stringelo: numbercurrentLeagueId: Id<"leagues">
leaguesname: string(e.g. "League 1")tierLevel: number(1 = highest, 6 = lowest)
weeksweekNumber: number(1, 2, 3, …)isCurrent: boolean
matchesweekId: Id<"weeks">leagueId: Id<"leagues">rankedMatchId: string(The ID of the match on the MSCR ranked api)matchNumber: number
matchResultsmatchId: Id<"matches">playerId: Id<"players">pointsWon: numbertimeMs: numberplacement: number(1 = first, etc.)
weeklyStandingsweekId: Id<"weeks">weekNumber: numberleagueId: Id<"leagues">leagueNumber: number(tier level at that week)playerId: Id<"players">finalPlacement: numbertotalPoints: numbermovement: "promoted" | "relegated" | "stayed" | "new"
This schema aims to simplify:
- Week transition logic and promotion/relegation.
- Per‑player historical stats.
All ingestion is done via Convex HTTP actions defined in convex/http.ts.
Every request must include an x-api-key header.
- Invalid/Missing Key: Returns
401 Unauthorizedor403 ForbiddenJSON errors. - Validation: Request bodies are validated for specific structures. On validation failure, the APIs return a structured
400 Bad Requestdetailing the validation issues.
Upserts players and their league tiers.
Headers:
x-api-key:<your_api_key>(Required)
Request Body (PlayersSchema):
[
{
"name": "string",
"elo": 1500,
"leagueTier": 1 // Accepts 1, 2, 3, 4, 5, or 6
}
]Behavior:
- For each entry:
- Ensures a
leaguesrow exists forleagueTier(createsTier <tier>if missing). - Looks up the player by
name.- If found: updates
eloandcurrentLeagueId. - If missing: inserts a new
playersdocument.
- If found: updates
- Ensures a
Response:
// 200 OK
{ "success": true, "updated": 42 }Upserts a single match and its per‑player results. Re‑posting the same (weekNumber, leagueTier, matchNumber) with updated results fully replaces prior results.
Headers:
x-api-key:<your_api_key>(Required)
Request Body (MatchSchema):
{
"weekNumber": 1, // Integer >= 1
"matchNumber": 1, // Integer >= 1
"leagueTier": 1, // Accepts 1 through 6
"rankedMatchId": "string", // The ID of the match on the MSCR ranked api
"results":[
{
"playerName": "string",
"pointsWon": 10,
"timeMs": 15000,
"placement": 1 // Integer >= 1
}
]
}Behavior:
- Ensures a
weeksrecord exists forweekNumber. - Ensures a
leaguesrecord exists forleagueTier. - Looks up a
matchesrow for(weekId, leagueId, matchNumber):- If exists: Deletes all existing
matchResultsfor that match so it can fill the updated ones. - If missing: Creates a new
matchesrow.
- If exists: Deletes all existing
- For each result:
- Validates that
placementis a positive integer and unique within the match. - Ensures that the
playerNamealready exists in the database (creates one withelo=0if not). - Inserts a
matchResultsrow recordingpointsWon,timeMs, andplacement.
- Validates that
Response:
// 200 OK
{ "success": true, "matchId": "...", "resultsInserted": 8 }Finalizes the week by computing standings, promotions, and relegations based on the updated player data.
Headers:
x-api-key:<your_api_key>(Required)
Request Body (WeekTransitionSchema):
{
"weekNumber": 1, // The week being finalized
"newWeek": 2, // The week number that will become current
"players":[ // Declarative “next state” for all players
{
"name": "string",
"elo": 1550,
"leagueTier": 1 // Accepts 1 through 6
}
]
}Behavior:
- Week Initialization: Ensures a
weeksrecord exists forweekNumber. - Player Movement: For each player in
players:- Ensures a
leaguesrow exists for the providedleagueTier. - Looks up the existing player by
name(creates if missing). - Computes
movement:"promoted"(old tier > new tier)"relegated"(old tier < new tier)"stayed"(old tier == new tier)"new"(player did not previously exist)
- Updates the player’s
eloandcurrentLeagueId. - Groups players by their old tier for standings.
- Ensures a
- Standings Calculation: For each old tier:
- Finds all matches for
(weekId, oldLeagueId). - Aggregates
pointsWonacross all matches per player. - Sorts players by total points descending.
- Writes one
weeklyStandingsrow per player containing:finalPlacement,totalPoints,movement,leagueNumber(old tier),weekNumber,weekId,leagueId, andplayerId.
- Finds all matches for
- Current Week Update:
- Sets all existing weeks
isCurrentfield to false. - Inserts a new
weeksrow{ weekNumber: newWeek, isCurrent: true }.
- Sets all existing weeks
Responses:
// 200 OK
{ "success": true, "count": 42 }
// 409 Conflict (If standings exist and overwrite is false)
{ "error": "Standings already exist for week 1." }