# Backend API Base URL: `http://localhost:8080` All protected endpoints require: ```http Authorization: Bearer ``` ## 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