699 lines
11 KiB
Markdown
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
|