10 KiB
Sea Battle — Main Server API
Центральный сервер для браузерной онлайн-игры «Морской бой». Предоставляет REST API для регистрации, аутентификации и матчмейкинга.
Базовый URL
http://localhost:3000/api
Аутентификация
Все endpoints (кроме /register и /login) требуют JWT-токен в заголовке:
Authorization: Bearer <token>
Токен выдаётся при регистрации или входе и действителен 7 дней.
Endpoints
Регистрация
Создаёт нового пользователя и возвращает токен.
URL: POST /api/register
Auth: не требуется
Content-Type: application/json или multipart/form-data (при загрузке аватара)
Параметры (JSON):
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
user.nickname |
string | ✅ | Уникальный никнейм (3–24 символа) |
user.password |
string | ✅ | Пароль (минимум 6 символов) |
user.password_confirmation |
string | ✅ | Подтверждение пароля |
user.avatar |
file | — | Аватар (PNG, JPEG, GIF, WebP) |
Пример запроса (JSON):
curl -X POST http://localhost:3000/api/register \
-H "Content-Type: application/json" \
-d '{
"user": {
"nickname": "CaptainNemo",
"password": "secret123",
"password_confirmation": "secret123"
}
}'
Пример запроса (с аватаром):
curl -X POST http://localhost:3000/api/register \
-F "user[nickname]=CaptainNemo" \
-F "user[password]=secret123" \
-F "user[password_confirmation]=secret123" \
-F "user[avatar]=@avatar.png"
Успешный ответ (201 Created):
{
"user": {
"id": 1,
"nickname": "CaptainNemo",
"avatar_url": "http://localhost:3000/rails/active_storage/blobs/...",
"created_at": "2026-09-21T10:00:00.000Z"
},
"token": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjox..."
}
Ошибки (422 Unprocessable Entity):
{
"errors": [
"Nickname has already been taken",
"Password is too short (minimum is 6 characters)"
]
}
Вход
Аутентифицирует пользователя и возвращает токен.
URL: POST /api/login
Auth: не требуется
Content-Type: application/json
Параметры:
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
nickname |
string | ✅ | Никнейм |
password |
string | ✅ | Пароль |
Пример запроса:
curl -X POST http://localhost:3000/api/login \
-H "Content-Type: application/json" \
-d '{
"nickname": "CaptainNemo",
"password": "secret123"
}'
Успешный ответ (200 OK):
{
"user": {
"id": 1,
"nickname": "CaptainNemo",
"avatar_url": "http://localhost:3000/rails/active_storage/blobs/...",
"created_at": "2026-09-21T10:00:00.000Z"
},
"token": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjox..."
}
Ошибка (401 Unauthorized):
{
"error": "Invalid nickname or password"
}
Текущий пользователь
Возвращает данные авторизованного пользователя.
URL: GET /api/me
Auth: требуется
Пример запроса:
curl -X GET http://localhost:3000/api/me \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..."
Успешный ответ (200 OK):
{
"user": {
"id": 1,
"nickname": "CaptainNemo",
"avatar_url": "http://localhost:3000/rails/active_storage/blobs/...",
"created_at": "2026-09-21T10:00:00.000Z"
}
}
Ошибка (401 Unauthorized):
{
"error": "Unauthorized"
}
Выход
Завершает сессию. JWT является stateless — клиент должен удалить токен.
URL: DELETE /api/logout
Auth: требуется
Пример запроса:
curl -X DELETE http://localhost:3000/api/logout \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..."
Успешный ответ (200 OK):
{
"message": "Signed out successfully"
}
Поиск соперника (Матчмейкинг)
Поиск соперника происходит через WebSocket (Action Cable), а не через long-polling: HTTP-поток не блокируется, а результат доставляется сервером push-уведомлением, как только найден соперник.
URL подключения: ws://localhost:3000/cable?token=<JWT>
Auth: токен передаётся параметром token в query string (тот же JWT, что и для HTTP API).
Канал: MatchmakingChannel
Поток взаимодействия
- Подключиться к
/cable?token=...и подписаться на каналMatchmakingChannel. - Отправить команду начала поиска:
{ "action": "start" }
Рейтинг для подбора берётся из базы данных (users.rating). У новых пользователей он инициализируется нейтральным значением 100 (значение задано в БД); в будущем рейтинг будет пересчитываться по результатам матчей. Игроки подбираются по близости рейтинга; допуск расширяется с ростом времени ожидания.
- Дождаться одного из сообщений:
Матч найден:
{
"type": "matched",
"match_id": "145575d6-3a4b-4c5d-9e6f-7a8b9c0d1e2f",
"game_token": "eyJhbGciOiJIUzI1NiJ9.eyJtYXRjaF9pZCI6IjE...",
"game_server": "ws://localhost:8080",
"players": [1, 2]
}
| Поле | Описание |
|---|---|
match_id |
Уникальный идентификатор матча (UUID) |
game_token |
JWT-токен для аутентификации на игровом сервере |
game_server |
WebSocket-адрес игрового сервера |
players |
ID обоих игроков [player1_id, player2_id] |
Таймаут поиска (соперник не найден за MATCHMAKING_QUEUE_TTL секунд):
{ "type": "timeout" }
- Поиск отменяется автоматически при закрытии WebSocket-подписки. Также можно явно отправить:
{ "action": "stop" }
Примечание: game_token содержит match_id, player1_id, player2_id, game_server и действителен MATCHMAKING_TOKEN_TTL секунд.
Отмена поиска
Убирает игрока из очереди матчмейкинга.
URL: DELETE /api/matchmaking
Auth: требуется
Пример запроса:
curl -X DELETE http://localhost:3000/api/matchmaking \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..."
Успешный ответ (200 OK):
{
"message": "Matchmaking cancelled"
}
Health Check
Проверка доступности сервера.
URL: GET /up
Auth: не требуется
Успешный ответ (200 OK):
<!DOCTYPE html><html><body style="background-color: green"></body></html>
Коды ошибок
| Код | Описание |
|---|---|
200 |
Успешный запрос |
201 |
Ресурс создан (регистрация) |
401 |
Неавторизован (отсутствует/невалиден токен) |
422 |
Ошибка валидации |
500 |
Внутренняя ошибка сервера |
Переменные окружения
| Переменная | По умолчанию | Описание |
|---|---|---|
REDIS_URL |
redis://localhost:6380/0 |
Подключение к Redis (матчмейкинг и pub/sub Action Cable) |
GAME_SERVER_URL |
ws://localhost:8080 |
Адрес игрового сервера для матчмейкинга |
MATCHMAKING_QUEUE_TTL |
60 |
Время ожидания соперника до таймаута, сек |
MATCHMAKING_PENDING_TTL |
15 |
Время ожидания финализации пары, сек |
MATCHMAKING_MATCH_TTL |
120 |
Время жизни данных матча, сек |
MATCHMAKING_TOKEN_TTL |
120 |
Время жизни game_token, сек |
MATCHMAKING_BASE_RANGE |
100 |
Начальный допуск по рейтингу |
MATCHMAKING_RANGE_PER_SECOND |
50 |
Рост допуска за секунду ожидания |
MATCHMAKING_MAX_RANGE |
1000 |
Максимальный допуск по рейтингу |
MATCHMAKING_SWEEP_INTERVAL |
2 |
Периодичность фоновой сверки очереди, сек |
CORS_ORIGINS |
* |
Разрешённые origins для CORS (через запятую) |
SECRET_KEY_BASE |
из credentials.yml.enc |
Секретный ключ для JWT |
Пример workflow: от регистрации до матча
# 1. Регистрация
TOKEN=$(curl -s -X POST http://localhost:3000/api/register \
-H "Content-Type: application/json" \
-d '{"user":{"nickname":"Player1","password":"secret123","password_confirmation":"secret123"}}' \
| jq -r '.token')
# 2. Проверка профиля
curl -s http://localhost:3000/api/me \
-H "Authorization: Bearer $TOKEN"
# 3. Поиск матча — WebSocket (например, через wscat):
# wscat -c "ws://localhost:3000/cable?token=$TOKEN"
# > {"command":"subscribe","identifier":"{\"channel\":\"MatchmakingChannel\"}"}
# > {"command":"message","identifier":"{\"channel\":\"MatchmakingChannel\"}","data":"{\"action\":\"start\"}"}