A programming competition platform for university teams. Problems are platform-hosted (statement + hidden test cases) and evaluated automatically via Judge0.
| Entity | MongoDB Collection | Notes |
|---|---|---|
| User | users |
Has teamCode linking to a team |
| TeamCode | teams |
Unique code, accumulated points, submissions[] |
| Competition | competition |
Contains teams[] (codes), problems[], scoring, podium[] (if maze mode enabled) |
| Problem | Embedded in Competition | difficulty: easy/medium/hard, statement, testCases[] (testcases collection), hidden_instructions (anti-AI) |
| Submission | Embedded in TeamCode | status always "AC", time in seconds from competition start |
| MazeConfig | maze_configs |
nodes[], doors[], startNodeId, goalNodeId |
| MazeProgress | maze_progress |
currentNodeId, unlockedDoors[], spentPoints, earnedPoints per team |
- Admin creates a competition (
POST /competition/create) with problems and per-difficulty scoring. - A user creates or joins a team (
/teams). - The team registers for the competition (
POST /competition/join). - During the competition, any team member submits a solution (
POST /competition/submission/{competitionId}/{problemId}).- Backend calculates points based on
competition.scoring[difficulty]. - Adds points to the team (
teams.points += points). - Records a submission in
teams.submissions[]withtime= seconds sincecompetition.date.
- Backend calculates points based on
- The leaderboard is queried in real-time (
GET /ranking/{competitionId}).
- Admin creates a maze configuration (
POST /maze/{competitionId}) with nodes (graph positions) and doors (bidirectional edges, each with a point cost). - Registered teams see the maze in the "Maze" tab with their starting position (
startNodeId). - A team spends points earned from problems to unlock doors and move between nodes (
POST /maze/{competitionId}/unlock).- Each door is bidirectional: can be crossed from either of its two nodes.
- Backend validates atomically: adjacency, door not already unlocked, sufficient points.
- Team's
maze_progressupdates:currentNodeId,spentPoints += door.cost,unlockedDoors[].
- Win Condition: When a team reaches
goalNodeId, they enter the podium automatically (no need to solve all problems).- Podium capacity: min(3, number of registered teams).
- Only the first 3 teams to reach the goal enter the podium.
- Game Over: When the podium is full,
competition.statuschanges to"completed"and no more doors can be unlocked.- The winning team is the first to reach the goal (
podium[0]).
- The winning team is the first to reach the goal (
- Submissions: Code is validated against problem test cases via Judge0 (see "Submission Validation" below).
- Scoring:
easy/medium/hard→ values configurable per competition. - Leaderboard:
ORDER BY points DESC, totalTime ASC.totalTime= time of team's last submission. - Teams:
maxMembersdefined per team. A team can participate in multiple competitions. - Auth: JWT 120-min expiry stored in
localStorage. User'steamCodeincluded in token and/auth/verifyresponse. - Private Competition:
GET /competition/private/{id}returns competition data + authenticated user's team data in a single call.
The platform supports two code validation modes (determined by environment variables):
-
Judge0 Mode (when
JUDGE0_API_KEYis set):- Source code is executed against hidden test cases stored in the
testcasescollection. - Time limits (
time_limit) and memory limits (memory_limit) from the problem are enforced. - Submission is accepted as
"AC"only if all test cases pass. - Client sends
source_codeandlanguage_id.
- Source code is executed against hidden test cases stored in the
-
Fallback Mode (when
VALIDATION_CODEis set, but NOTJUDGE0_API_KEY):- Simplified validation: client sends a secret code (
validation_code) that must match exactlyVALIDATION_CODEon the server. - Useful for development environments, demos, or when Judge0 integration is unavailable.
- Does not execute or validate user code.
- Simplified validation: client sends a secret code (
In both modes, if validation passes, the submission is recorded as "AC" and points are awarded to the team. Submissions cannot be changed afterward.
- Doors are bidirectional: can be traversed from either of their two nodes (
from_nodeorto_node). - Door cost is fixed and deducted from team's
spentPointswhen unlocked. availablePoints=earnedPoints - spentPoints. Teams can only spend points they have earned.- A team with no unlocked doors has no
maze_progressdocument until attempting to unlock the first door (lazy initialization). - The maze is playable only while the competition is active (
status = "active"); if competition ends (status = "completed"), no more doors can be unlocked. - A team cannot appear twice on the podium: if they already reached the goal, subsequent maze iterations do not count them again.
Real-time communication uses MQTT with WebSockets as transport. The server publishes events on two distinct channels:
Topic: {MQTT_TOPIC_PREFIX}/ranking/{competitionId} (e.g. code-arena/ranking/comp-123)
Events affecting all teams in the competition:
-
new_submission: A team solved a problem.{ "event": "new_submission", "data": { "teamCode": "abc123", "problem": "prob-id", "points": 100, "member": "username", "time": 234 } } -
door_unlocked: A team unlocked a maze door.{ "event": "door_unlocked", "data": { "teamCode": "abc123", "doorId": "door-1", "newNode": "node-5", "cost": 50 } } -
team_finished: A team reached the goal and earned a podium place (game continues until podium is full).{ "event": "team_finished", "data": { "teamCode": "abc123", "teamName": "Python Masters", "position": 2, "podiumTarget": 3 } } -
game_over: The podium is full (top 3 in maze); game ended for all.{ "event": "game_over", "data": { "teamCode": "winning-team", "teamName": "Winners", "podium": [ {"teamCode": "t1", "teamName": "Gold Team"}, {"teamCode": "t2", "teamName": "Silver Team"}, {"teamCode": "t3", "teamName": "Bronze Team"} ], "goalNodeId": "goal" } } -
cheer: A team member sent an encouraging message (visible to all).{ "event": "cheer", "data": { "teamCode": "abc123", "member": "username", "message": "Go team!" } }
Topic: {MQTT_TOPIC_PREFIX}/team/{teamCode} (e.g. code-arena/team/abc123)
Events affecting only members of a specific team, independent of any competition:
team_joined_competition: Team registered for a competition (confirms successful registration).{ "event": "team_joined_competition", "data": { "competitionId": "comp-123", "teamCode": "abc123" } }
useCompetitionSocket(competitionId, onMessage): Subscribe to competition channel.useTeamSocket(teamCode, onMessage): Subscribe to team channel.
Both hooks handle automatic reconnection (10-second delay) and detect fatal auth errors (no retry on credential errors).
- MongoDB: Async Motor client. Locally runs as Docker container (
mongo:7). In production, can point to any external instance viaMONGO_URL. - MQTT: MQTT broker for real-time events (e.g. Mosquitto or managed cloud service). Environment variables:
MQTT_HOST,MQTT_WS_PORT,MQTT_WS_PATH,MQTT_USERNAME,MQTT_PASSWORD,MQTT_TOPIC_PREFIX. - Judge0: Code execution API (only if
JUDGE0_API_KEYis set). Falls back to fallback mode if unavailable. - Vercel: Backend and frontend deployment (single
vercel.jsonat root defines both services; automatic deployment on push tomain). - MongoDB Atlas: Production database (free M0 tier available).