Implement leaderboard service and POC HTTP handlers with Swagger documentation

This commit is contained in:
2026-07-15 11:29:19 +04:00
parent c25e0bc581
commit 4a894a4399
10 changed files with 1300 additions and 3 deletions
+138
View File
@@ -1012,6 +1012,72 @@ const docTemplate = `{
}
}
},
"/api/leaderboard": {
"get": {
"description": "Returns paginated leaderboard list. Supports overall leaderboard or track-specific leaderboard for the current year.",
"produces": [
"application/json"
],
"tags": [
"leaderboard"
],
"summary": "Leaderboard",
"parameters": [
{
"type": "string",
"description": "Track ID. If empty, returns overall leaderboard.",
"name": "track_id",
"in": "query"
},
{
"type": "integer",
"description": "Year. Defaults to current year.",
"name": "year",
"in": "query"
},
{
"type": "integer",
"description": "Limit (1..200, default 50)",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"description": "Offset (default 0)",
"name": "offset",
"in": "query"
},
{
"type": "string",
"description": "Current driver ID. Defaults to driver-seed-001 (mock Alice).",
"name": "X-Driver-Id",
"in": "header"
}
],
"responses": {
"200": {
"description": "Leaderboard page and current driver position",
"schema": {
"$ref": "#/definitions/transport.LeaderboardResponse"
}
},
"400": {
"description": "Invalid input",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"/api/races": {
"get": {
"description": "Returns one keyset page of races. Live races (status lobby|racing) come from in-memory lobby.Service; finished races come from Postgres. The two sources are merged and ordered by (started_ms DESC, id ASC). The cursor is opaque: pass back the next_cursor verbatim from a previous page.\n## Filters\n- ` + "`" + `status` + "`" + ` (comma-separated, e.g. \"racing,finished\"). Recognised values: all, lobby, racing, finished.\n- ` + "`" + `track_id` + "`" + ` (exact match against a physical track id; custom lobbies are not supported in this build).\n- ` + "`" + `limit` + "`" + ` (1..200, default 50)",
@@ -2315,6 +2381,78 @@ const docTemplate = `{
}
}
},
"transport.LeaderboardEntry": {
"type": "object",
"properties": {
"avatar_url": {
"type": "string",
"example": "https://cdn.example.com/u/ace.png"
},
"best_pos": {
"description": "Only populated for track-specific leaderboard",
"type": "integer",
"example": 1
},
"best_time_ms": {
"type": "integer",
"example": 15000
},
"clan_id": {
"type": "string",
"example": "clan-seed-001"
},
"clan_tag": {
"type": "string",
"example": "ACE"
},
"driver_id": {
"type": "string",
"example": "driver-seed-001"
},
"name": {
"type": "string",
"example": "Alice"
},
"nickname": {
"type": "string",
"example": "ACE"
},
"points": {
"type": "integer",
"example": 25
},
"rank": {
"type": "integer",
"example": 1
}
}
},
"transport.LeaderboardResponse": {
"type": "object",
"properties": {
"current_driver": {
"$ref": "#/definitions/transport.LeaderboardEntry"
},
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/transport.LeaderboardEntry"
}
},
"limit": {
"type": "integer",
"example": 50
},
"offset": {
"type": "integer",
"example": 0
},
"total": {
"type": "integer",
"example": 8
}
}
},
"transport.LobbyRace": {
"type": "object",
"properties": {
+138
View File
@@ -1010,6 +1010,72 @@
}
}
},
"/api/leaderboard": {
"get": {
"description": "Returns paginated leaderboard list. Supports overall leaderboard or track-specific leaderboard for the current year.",
"produces": [
"application/json"
],
"tags": [
"leaderboard"
],
"summary": "Leaderboard",
"parameters": [
{
"type": "string",
"description": "Track ID. If empty, returns overall leaderboard.",
"name": "track_id",
"in": "query"
},
{
"type": "integer",
"description": "Year. Defaults to current year.",
"name": "year",
"in": "query"
},
{
"type": "integer",
"description": "Limit (1..200, default 50)",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"description": "Offset (default 0)",
"name": "offset",
"in": "query"
},
{
"type": "string",
"description": "Current driver ID. Defaults to driver-seed-001 (mock Alice).",
"name": "X-Driver-Id",
"in": "header"
}
],
"responses": {
"200": {
"description": "Leaderboard page and current driver position",
"schema": {
"$ref": "#/definitions/transport.LeaderboardResponse"
}
},
"400": {
"description": "Invalid input",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"/api/races": {
"get": {
"description": "Returns one keyset page of races. Live races (status lobby|racing) come from in-memory lobby.Service; finished races come from Postgres. The two sources are merged and ordered by (started_ms DESC, id ASC). The cursor is opaque: pass back the next_cursor verbatim from a previous page.\n## Filters\n- `status` (comma-separated, e.g. \"racing,finished\"). Recognised values: all, lobby, racing, finished.\n- `track_id` (exact match against a physical track id; custom lobbies are not supported in this build).\n- `limit` (1..200, default 50)",
@@ -2313,6 +2379,78 @@
}
}
},
"transport.LeaderboardEntry": {
"type": "object",
"properties": {
"avatar_url": {
"type": "string",
"example": "https://cdn.example.com/u/ace.png"
},
"best_pos": {
"description": "Only populated for track-specific leaderboard",
"type": "integer",
"example": 1
},
"best_time_ms": {
"type": "integer",
"example": 15000
},
"clan_id": {
"type": "string",
"example": "clan-seed-001"
},
"clan_tag": {
"type": "string",
"example": "ACE"
},
"driver_id": {
"type": "string",
"example": "driver-seed-001"
},
"name": {
"type": "string",
"example": "Alice"
},
"nickname": {
"type": "string",
"example": "ACE"
},
"points": {
"type": "integer",
"example": 25
},
"rank": {
"type": "integer",
"example": 1
}
}
},
"transport.LeaderboardResponse": {
"type": "object",
"properties": {
"current_driver": {
"$ref": "#/definitions/transport.LeaderboardEntry"
},
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/transport.LeaderboardEntry"
}
},
"limit": {
"type": "integer",
"example": 50
},
"offset": {
"type": "integer",
"example": 0
},
"total": {
"type": "integer",
"example": 8
}
}
},
"transport.LobbyRace": {
"type": "object",
"properties": {
+97
View File
@@ -252,6 +252,58 @@ definitions:
type:
$ref: '#/definitions/transport.MessageType'
type: object
transport.LeaderboardEntry:
properties:
avatar_url:
example: https://cdn.example.com/u/ace.png
type: string
best_pos:
description: Only populated for track-specific leaderboard
example: 1
type: integer
best_time_ms:
example: 15000
type: integer
clan_id:
example: clan-seed-001
type: string
clan_tag:
example: ACE
type: string
driver_id:
example: driver-seed-001
type: string
name:
example: Alice
type: string
nickname:
example: ACE
type: string
points:
example: 25
type: integer
rank:
example: 1
type: integer
type: object
transport.LeaderboardResponse:
properties:
current_driver:
$ref: '#/definitions/transport.LeaderboardEntry'
items:
items:
$ref: '#/definitions/transport.LeaderboardEntry'
type: array
limit:
example: 50
type: integer
offset:
example: 0
type: integer
total:
example: 8
type: integer
type: object
transport.LobbyRace:
properties:
created_ms:
@@ -1408,6 +1460,51 @@ paths:
summary: Get / update / delete a driver by id
tags:
- drivers
/api/leaderboard:
get:
description: Returns paginated leaderboard list. Supports overall leaderboard
or track-specific leaderboard for the current year.
parameters:
- description: Track ID. If empty, returns overall leaderboard.
in: query
name: track_id
type: string
- description: Year. Defaults to current year.
in: query
name: year
type: integer
- description: Limit (1..200, default 50)
in: query
name: limit
type: integer
- description: Offset (default 0)
in: query
name: offset
type: integer
- description: Current driver ID. Defaults to driver-seed-001 (mock Alice).
in: header
name: X-Driver-Id
type: string
produces:
- application/json
responses:
"200":
description: Leaderboard page and current driver position
schema:
$ref: '#/definitions/transport.LeaderboardResponse'
"400":
description: Invalid input
schema:
additionalProperties: true
type: object
"500":
description: Internal server error
schema:
additionalProperties: true
type: object
summary: Leaderboard
tags:
- leaderboard
/api/races:
get:
description: |-