cab-backend/API.md
2026-03-30 21:00:35 +03:00

699 lines
11 KiB
Markdown

# Backend API
Base URL: `http://localhost:8080`
All protected endpoints require:
```http
Authorization: Bearer <token>
```
## Access Rules
- Public:
- `POST /api/auth/login`
- `POST /api/auth/register`
- `GET /api/invites/validate/:code`
- Authorized users:
- `GET /api/auth/me`
- read endpoints for games, modules, languages, leaderboards, notifications
- `admin` / `moderator`:
- create/update/delete for games, modules inside games, variables, notifications, leaderboards, groups, languages, invites, image uploads
## Auth
### Login
`POST /api/auth/login`
```json
{
"email": "admin@admin.com",
"password": "admin123"
}
```
### Register
`POST /api/auth/register`
```json
{
"email": "user@example.com",
"username": "player1",
"password": "secret123",
"invite_code": "abcd1234"
}
```
### Current User
`GET /api/auth/me`
## Games
### List Games
`GET /api/games`
Each game includes connected modules.
Example response:
```json
[
{
"id": 5,
"name": "Black & White",
"slug": "b&d",
"description": "Sandbox",
"image_url": "",
"is_active": true,
"modules": [
{
"id": 1,
"key": "variables",
"name": "Variables"
},
{
"id": 2,
"key": "notification",
"name": "Notification"
}
]
}
]
```
Supports search by game `name` or `slug`:
- `GET /api/games?search=black`
- `GET /api/games?search=b&d`
### Get Game
`GET /api/games/:id`
Useful when client is inside a module page and needs the current game `slug` to restore game search state on exit.
Example response:
```json
{
"id": 5,
"name": "Black & White",
"slug": "b&d",
"description": "Sandbox",
"image_url": "",
"is_active": true,
"modules": [
{
"id": 1,
"key": "variables",
"name": "Variables"
},
{
"id": 2,
"key": "notification",
"name": "Notification"
}
]
}
```
### Create Game
`POST /api/games`
```json
{
"name": "My Game",
"slug": "my-game",
"description": "Description",
"image_url": ""
}
```
### Update Game
`PUT /api/games/:id`
### Delete Game
`DELETE /api/games/:id`
## Modules
### List Available Modules
`GET /api/modules`
Default modules:
- `variables`
- `notification`
- `leaderboard`
### List Game Modules
`GET /api/games/:id/modules`
### Connect Module To Game
`POST /api/games/:id/modules`
```json
{
"module_key": "variables"
}
```
### Disconnect Module From Game
`DELETE /api/games/:id/modules/:module_key`
## Languages
Default seeded languages:
- `en` / `English`
- `ru` / `Русский`
### List Languages
`GET /api/languages`
### Get Language
`GET /api/languages/:id`
### Create Language
`POST /api/languages`
```json
{
"code": "de",
"name": "Deutsch"
}
```
### Update Language
`PUT /api/languages/:id`
### Delete Language
`DELETE /api/languages/:id`
## User-Game Connections
### Search Users In Game
`GET /api/games/:id/users?search=...`
Returns only users connected to game `:id`.
Search behavior:
- if `search` is an integer, search by user `id`
- otherwise search by `username` or `email`
Examples:
- `GET /api/games/5/users`
- `GET /api/games/5/users?search=12`
- `GET /api/games/5/users?search=denis`
- `GET /api/games/5/users?search=denis@example.com`
Example response:
```json
[
{
"id": 12,
"email": "denis@example.com",
"username": "denis",
"role": "user",
"is_active": true,
"created_at": "2026-03-23T20:00:00Z",
"updated_at": "2026-03-23T20:00:00Z"
}
]
```
### List User Games
`GET /api/users/:id/games`
### Connect Game To User
`POST /api/users/:id/games`
```json
{
"game_id": 5
}
```
### Disconnect Game From User
`DELETE /api/users/:id/games/:game_id`
## Variables
All variable endpoints work only if the game has module `variables`.
### List Variables
`GET /api/games/:id/variables`
### Create Variable
`POST /api/games/:id/variables`
Number variable:
```json
{
"key": "max_bet",
"type": "number",
"number_value": 100
}
```
String variable:
```json
{
"key": "welcome_text",
"type": "string",
"string_value": "hello"
}
```
Table variable with numbers:
```json
{
"key": "rates",
"type": "table",
"table_value_type": "number",
"items": [
{ "key": "bronze", "number_value": 1.1 },
{ "key": "silver", "number_value": 1.3 }
]
}
```
Table variable with strings:
```json
{
"key": "titles",
"type": "table",
"table_value_type": "string",
"items": [
{ "key": "bronze", "string_value": "Bronze" },
{ "key": "silver", "string_value": "Silver" }
]
}
```
Vector variable with numbers:
```json
{
"key": "steps",
"type": "vector",
"table_value_type": "number",
"items": [
{ "index": 0, "number_value": 10 },
{ "index": 1, "number_value": 20 }
]
}
```
Vector variable with strings:
```json
{
"key": "messages",
"type": "vector",
"table_value_type": "string",
"items": [
{ "index": 0, "string_value": "hello" },
{ "index": 1, "string_value": "world" }
]
}
```
### Update Variable
`PUT /api/games/:id/variables/:var_id`
### Delete Variable
`DELETE /api/games/:id/variables/:var_id`
### Variable Rules
- `type = number` uses only `number_value`
- `type = string` uses only `string_value`
- `type = table` uses only `table_value_type` and `items`
- `type = vector` uses only `table_value_type` and `items`
- all table items must use the same value type
- all vector items must use `index` instead of `key`
- vector indexes must be unique and `>= 0`
- variable `key` is unique inside one game
## Images
### Upload Image
`POST /api/images`
Content type: `multipart/form-data`
Form field:
- `file`
Example response:
```json
{
"name": "f6e7b0b2-6f8d-4a33-8fc8-2a2f8f6d8c4b.png",
"path": "/uploads/images/f6e7b0b2-6f8d-4a33-8fc8-2a2f8f6d8c4b.png",
"url": "http://localhost:8080/uploads/images/f6e7b0b2-6f8d-4a33-8fc8-2a2f8f6d8c4b.png",
"content_type": "image/png"
}
```
Static file access:
`GET /uploads/images/:filename`
## Notifications
All notification endpoints work only if the game has module `notification`.
Notification structure:
- one `notification` has:
- `name`
- multilingual `descriptions`
- shared custom `variables`
- many `entries`
- each `entry` has its own:
- `time_second`
- `image`
- `login`
- values for all shared custom variables
Macros returned by API always include:
- `{{time_second}}`
- `{{image}}`
- `{{login}}`
- all custom variable keys
### List Notifications
`GET /api/games/:id/notifications`
### Get Notification
`GET /api/games/:id/notifications/:notification_id`
### Create Notification
`POST /api/games/:id/notifications`
```json
{
"name": "Welcome bonus",
"descriptions": [
{
"language_id": 1,
"description": "Hello {{login}}, promo {{promo_code}} after {{time_second}} seconds"
},
{
"language_id": 2,
"description": "Привет {{login}}, промокод {{promo_code}} через {{time_second}} секунд"
}
],
"variables": [
{ "key": "promo_code" },
{ "key": "reward" }
],
"entries": [
{
"time_second": 30,
"image": "http://localhost:8080/uploads/images/a.png",
"login": "Denis",
"variables": [
{ "key": "promo_code", "value": "WELCOME30" },
{ "key": "reward", "value": "100" }
]
},
{
"time_second": 60,
"image": "http://localhost:8080/uploads/images/b.png",
"login": "Alex",
"variables": [
{ "key": "promo_code", "value": "WELCOME60" },
{ "key": "reward", "value": "200" }
]
}
]
}
```
### Update Notification
`PUT /api/games/:id/notifications/:notification_id`
### Delete Notification
`DELETE /api/games/:id/notifications/:notification_id`
### Notification Rules
- `name` is required
- `descriptions` must contain at least one item
- `entries` must contain at least one item
- one `language_id` cannot repeat inside `descriptions`
- top-level `variables` define the shared custom variable set
- reserved variable keys are forbidden:
- `time_second`
- `image`
- `login`
- each entry must contain:
- `time_second >= 0`
- `image`
- `login`
- full set of custom variable values
- entry variable keys must exactly match top-level custom variable keys
## Leaderboards
All leaderboard endpoints work only if the game has module `leaderboard`.
### List Game Leaderboards
`GET /api/games/:id/leaderboards`
### Get Leaderboard
`GET /api/games/:id/leaderboards/:leaderboard_id`
### Create Leaderboard
`POST /api/games/:id/leaderboards`
```json
{
"key": "top_balance",
"name": "Top Balance",
"sort_order": "desc",
"period_type": "all_time",
"is_active": true
}
```
`sort_order`:
- `asc`
- `desc`
`period_type`:
- `all_time`
- `daily`
- `weekly`
- `monthly`
### Update Leaderboard
`PUT /api/games/:id/leaderboards/:leaderboard_id`
### Delete Leaderboard
`DELETE /api/games/:id/leaderboards/:leaderboard_id`
### List Leaderboard Groups
`GET /api/leaderboards/:leaderboard_id/groups`
### Create Group
`POST /api/leaderboards/:leaderboard_id/groups`
```json
{
"key": "vip",
"name": "VIP",
"is_default": false
}
```
### Update Group
`PUT /api/leaderboard-groups/:group_id`
### Delete Group
`DELETE /api/leaderboard-groups/:group_id`
### Add Group Member
`POST /api/leaderboard-groups/:group_id/members`
```json
{
"user_id": 12
}
```
User must already be connected to the same game.
### Delete Group Member
`DELETE /api/leaderboard-groups/:group_id/members/:user_id`
### Save Score
`POST /api/leaderboards/:leaderboard_id/scores`
```json
{
"user_game_id": 5,
"score": 1200
}
```
If a score already exists for this user in this leaderboard, it is updated.
### Get Rankings
`GET /api/leaderboards/:leaderboard_id/rankings`
Query params:
- `group_id` optional
- `limit` optional, default `50`, max `200`
Example:
`GET /api/leaderboards/1/rankings?group_id=2&limit=20`
Response:
```json
{
"leaderboard": {
"id": 1,
"game_id": 5,
"key": "top_balance",
"name": "Top Balance",
"sort_order": "desc",
"period_type": "all_time",
"is_active": true
},
"items": [
{
"rank": 1,
"user_id": 12,
"user_game_id": 5,
"username": "arnold",
"score": 1200
},
{
"rank": 2,
"user_id": 17,
"user_game_id": 8,
"username": "german",
"score": 950
}
]
}
```
### Leaderboard Rules
- leaderboard `key` is unique inside one game
- group `key` is unique inside one leaderboard
- score is stored once per `leaderboard_id + user_game_id`
- groups do not duplicate scores, they only filter ranking members
## Balance
### Top Up Balance
`POST /api/user-games/:ug_id/topup`
```json
{
"amount": 100,
"comment": "manual topup"
}
```
### List Transactions
`GET /api/user-games/:ug_id/transactions`
## Invites
### Validate Invite
`GET /api/invites/validate/:code`
### Create Invite
`POST /api/invites`
```json
{
"role": "user",
"max_uses": 10
}
```
### List Invites
`GET /api/invites`
### Delete Invite
`DELETE /api/invites/:code`
## Common Error Patterns
- `400` invalid path param or invalid request body
- `401` invalid or missing token
- `403` module is not enabled for the target game, or role is insufficient
- `404` entity not found
- `409` duplicate key / duplicate connection / duplicate membership