Files
ruby-sea-battle/main-server/API.md
T

10 KiB
Raw Blame History

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

Поток взаимодействия

  1. Подключиться к /cable?token=... и подписаться на канал MatchmakingChannel.
  2. Отправить команду начала поиска:
{ "action": "start" }

Рейтинг для подбора берётся из базы данных (users.rating). У новых пользователей он инициализируется нейтральным значением 100 (значение задано в БД); в будущем рейтинг будет пересчитываться по результатам матчей. Игроки подбираются по близости рейтинга; допуск расширяется с ростом времени ожидания.

  1. Дождаться одного из сообщений:

Матч найден:

{
  "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" }
  1. Поиск отменяется автоматически при закрытии WebSocket-подписки. Также можно явно отправить:
{ "action": "stop" }

Примечание: game_token содержит match_id, player1_id, player2_id, game_server и действителен MATCHMAKING_TOKEN_TTL секунд.


Отмена поиска

Отмена поиска происходит через WebSocket: достаточно отключиться от MatchmakingChannel либо отправить { "action": "stop" }.


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\"}"}