uVersion
Русский
Скачать →

Wiki

REST API

Полный справочник HTTP-эндпоинтов сервера uVersion: auth, repos, files, locks, comments, watchlist, modification requests, builds, admin.

Сервер uVersion предоставляет JSON HTTP API с аутентификацией по JWT. Эта страница документирует все эндпоинты, используемые десктопным клиентом, плагинами редактора и CLI uversion. Вы можете вызывать их напрямую, чтобы интегрировать uVersion в ваш внутренний инструментарий (кастомный дашборд, скрипты аудита, webhook и т. д.).

Соглашения

Базовый URL

Все документированные пути указаны относительно URL вашего инстанса. В примерах используется https://uversion.mygamestudio.com. Замените на свой.

Обязательные заголовки

ЗаголовокЗначение
AuthorizationBearer <jwt> на всех маршрутах /api/*, кроме /api/auth/* и /api/server-info/health)
Content-Typeapplication/json для POST/PUT с JSON-телом. application/octet-stream для бинарных загрузок (build files).
Acceptapplication/json рекомендуется (сервер по умолчанию возвращает JSON)

Единый формат ответа

Все маршруты отвечают следующей оболочкой:

{
  "success": true,
  "data": { /* payload */ }
}

В случае ошибки:

{
  "success": false,
  "error": "Permission denied: capability 'manage_users' required"
}

Поле success присутствует всегда и указывает, была ли запрос успешным. Поле data присутствует в случае успеха. Поле error присутствует в случае неудачи. Никогда оба вместе.

Пагинация

Маршруты, возвращающие потенциально длинные списки, принимают limit и offset в query-параметрах (?limit=20&offset=40). Ответ включает total и has_more для облегчения итерации. Максимальный limit: 100.

Ограничение частоты запросов

Глобальное ограничение частоты запросов было удалено (см. CLAUDE.md, раздел Security). Только /api/auth/login имеет ограничение: 5 неудачных попыток на username каждые 15 минут. Валидные попытки не учитываются.

Версионирование токенов

Каждый JWT содержит поле tv (token version), которое соответствует users.token_version в БД. Middleware аутентификации проверяет это значение при каждом запросе. Когда пользователь выполняет POST /api/auth/logout, когда админ сбрасывает его пароль или когда админ деактивирует его аккаунт, token_version увеличивается, мгновенно аннулируя все существующие токены этого пользователя на всех его машинах.

Curl

Чтобы вызвать API из терминала:

TOKEN=$(curl -s -X POST https://uversion.mygamestudio.com/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"secret"}' | jq -r '.data.token')

curl -H "Authorization: Bearer $TOKEN" \
  https://uversion.mygamestudio.com/api/repositories

Аутентификация

POST /api/auth/login

Аутентифицирует пользователя и возвращает JWT на 30 дней.

Body:

{
  "username": "alice",
  "password": "secret"
}

Response 200:

{
  "success": true,
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIs...",
    "expires_at": "2026-06-13T14:30:00Z",
    "user": {
      "id": 12,
      "username": "alice",
      "email": "alice@mygamestudio.com",
      "role": "lead",
      "is_active": true
    }
  }
}

Ошибки:

  • 401: неверные учётные данные или неактивный пользователь
  • 429: достигнуто 5 неудачных попыток для этого username в окне 15 мин

Сервер всегда выполняет argon2id::verify_password (против фиктивного хеша, если пользователь не существует), чтобы нейтрализовать атаки по времени. Ответ занимает одинаковое время, существует пользователь или нет.

POST /api/auth/refresh

Обновляет JWT без повторного ввода пароля.

Заголовки: Authorization: Bearer <current_token>. Без тела.

Response 200: идентичен /login с истечением, отодвинутым на 30 дней, и новым токеном.

Ошибки:

  • 401: текущий токен недействителен, истёк или несоответствие token_version

POST /api/auth/logout

Аннулирует все токены текущего пользователя (на всех его машинах), увеличивая users.token_version. Пользователю придётся снова войти везде.

Response 200: { "success": true }.

POST /api/auth/validate

Проверяет, что токен ещё действителен. Для токенов, до истечения которых осталось менее 7 дней, сервер включает refreshed_token в ответ (авто-продление). Клиент должен сохранить его вместо старого.

Response 200:

{
  "success": true,
  "data": {
    "valid": true,
    "user_id": 12,
    "expires_at": "2026-06-13T14:30:00Z",
    "refreshed_token": "eyJhbGciOiJIUzI1NiIs..."
  }
}

POST /api/auth/register

Создаёт учётную запись пользователя (публичный маршрут, без аутентификации).

POST /api/auth/change-password

Меняет пароль текущего пользователя. Увеличивает token_version, что аннулирует все старые токены.

Репозитории

GET /api/repositories

Перечисляет репозитории, доступные текущему пользователю (отфильтрованные через таблицу прав). Пользователь, у которого нет ни одного паттерна прав на репозиторий, его не видит.

Query-параметры: limit, offset.

Response 200:

{
  "success": true,
  "data": [
    {
      "id": 1,
      "name": "hero-rpg",
      "owner": "acme",
      "description": "Main RPG project",
      "current_revision": "7f3a9b1c2d...",
      "file_count": 8432,
      "size_bytes": 14211938405,
      "created_at": "2026-01-15T10:00:00Z"
    },
    {
      "id": 2,
      "name": "shared-assets",
      "owner": "acme",
      "description": "Shared asset library",
      "current_revision": "3a4b5c6d...",
      "file_count": 412,
      "size_bytes": 980000000,
      "created_at": "2026-02-01T09:00:00Z"
    }
  ]
}

GET /api/repositories/{repo_id}

Детали репозитория, включая агрегированную статистику.

Response 200:

{
  "success": true,
  "data": {
    "id": 1,
    "name": "hero-rpg",
    "owner": "acme",
    "description": "Main RPG project",
    "current_revision": "7f3a9b1c2d...",
    "file_count": 8432,
    "size_bytes": 14211938405,
    "dedup_ratio": 4.2,
    "created_at": "2026-01-15T10:00:00Z",
    "last_commit_at": "2026-05-15T08:30:00Z"
  }
}

Ошибки:

  • 403: нет доступа к этому репозиторию
  • 404: репозиторий не существует

POST /api/repositories

Создаёт новый репозиторий. Требуется capability create_repos (по умолчанию admin).

Body:

{
  "name": "new-project",
  "description": "Optional description"
}

Валидация:

  • name: от 1 до 64 символов, буквенно-цифровые + дефисы + подчёркивания. Уникально в пределах owner.
  • description: от 0 до 1024 символов. Необязательно.

Ошибки:

  • 400: недопустимое или слишком длинное имя
  • 403: отсутствует capability create_repos
  • 409: репозиторий с таким именем уже существует для этого owner

Файлы

POST /api/files/{repo_id}/upload-chunks

Загружает пакет бинарных chunk. Идентичные chunk (по SHA-256) дедуплицируются автоматически. Сервер не сохраняет повторно chunk, который уже присутствует. Ответ возвращает для каждого chunk его хеш + размер + сжатый размер, для использования в последующем POST /commit.

Body:

{
  "chunks": [
    { "data": "<base64-encoded chunk bytes>" },
    { "data": "<base64-encoded chunk bytes>" }
  ]
}

Response 200:

{
  "success": true,
  "data": {
    "chunks": [
      { "hash": "abc123...", "size": 1048576, "compressed_size": 423152 },
      { "hash": "def456...", "size": 2097152, "compressed_size": 891204 }
    ]
  }
}

Ограничения: тело макс. 1 GB (настраивается через security.max_body_size_files). Для более крупных файлов разбивайте на несколько пакетов.

POST /api/files/{repo_id}/commit

Создаёт атомарный commit со списком файлов и их chunk. Либо все файлы проходят, либо ни один.

Body:

{
  "message": "Updated main level + hero pose pass",
  "files": [
    {
      "path": "Content/Maps/MainLevel.umap",
      "action": "modified",
      "chunks": ["abc123...", "def456..."]
    },
    {
      "path": "Content/Characters/NewVillain.uasset",
      "action": "added",
      "chunks": ["fed789..."]
    },
    {
      "path": "Content/OldAsset.uasset",
      "action": "deleted"
    }
  ]
}

Возможные действия: added, modified, deleted. Для added и modified массив chunks содержит хеши, возвращённые /upload-chunks. Для deleted опустите chunks.

Response 200:

{
  "success": true,
  "data": {
    "commit_hash": "7f3a9b1c2d3e4f...",
    "files_changed": 3,
    "bytes_uploaded": 88080384,
    "bytes_deduped": 4194304,
    "revision": 47
  }
}

Ошибки:

  • 400: пустое сообщение, файл без действия, ссылки на несуществующие chunk
  • 403: нет capability checkin или нет права write на один из файлов
  • 409: блокировка уже удерживается другим пользователем на изменённом файле, или конкурентный commit (race condition на том же файле)

GET /api/files/{repo_id}/snapshot

Возвращает полное состояние репозитория на текущей ревизии: все файлы, их ревизии и их chunk. Используется клиентом для операций clone и принудительного sync.

Query-параметры:

  • revision (необязательно): snapshot на прошлой ревизии. По умолчанию: HEAD.

Response 200:

{
  "success": true,
  "data": {
    "revision": 47,
    "files": [
      {
        "path": "Content/Maps/MainLevel.umap",
        "revision": 12,
        "size_bytes": 84934656,
        "chunks": ["abc123...", "def456..."]
      }
    ]
  }
}

GET /api/files/{repo_id}/content

Скачивает содержимое файла на заданной ревизии (сервер пересобирает chunk). Используется clone и sync.

Query-параметры:

  • path: путь к файлу (относительно корня репозитория)
  • revision (необязательно): номер ревизии. По умолчанию: последняя.

Response 200: бинарное содержимое файла.

GET /api/files/{repo_id}/history

Пагинированная история commit репозитория.

Query-параметры:

  • limit (от 1 до 100, по умолчанию 20)
  • offset (по умолчанию 0)
  • path (необязательно): фильтр по пути файла (commit, изменившие этот файл)
  • author (необязательно): фильтр по username
  • since (необязательно): ISO 8601 timestamp

Response 200:

{
  "success": true,
  "data": {
    "commits": [
      {
        "hash": "7f3a9b1c...",
        "author": "alice",
        "author_id": 12,
        "date": "2026-05-15T08:30:00Z",
        "message": "Fixed lighting in main level",
        "files_changed": 1,
        "bytes_uploaded": 84934656
      }
    ],
    "total": 423,
    "limit": 20,
    "offset": 0,
    "has_more": true
  }
}

Инкрементальный sync

Эндпоинты, используемые десктопным клиентом для эффективной синхронизации:

  • GET /api/files/{repo_id}/sync: файлы, изменённые с некоторой ревизии, для дельта-sync.
  • GET /api/files/{repo_id}/deletions: файлы, удалённые на стороне сервера, для распространения удалений локально.
  • GET /api/files/{repo_id}/list: список файлов репозитория.
  • POST /api/files/{repo_id}/checkout: получает блокировки и подготавливает редактирование.

Блокировки

POST /api/locks/{repo_id}/acquire

Получает блокировки на список путей. Получения независимы: список acquired содержит успехи, список failed содержит неудачи с их причиной.

Body:

{
  "paths": [
    "Content/Maps/MainLevel.umap",
    "Content/Characters/Hero.uasset"
  ]
}

Response 200:

{
  "success": true,
  "data": {
    "acquired": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "file_id": 12345,
        "file_path": "Content/Maps/MainLevel.umap",
        "user_id": 12,
        "username": "alice",
        "acquired_at": "2026-05-15T14:30:00Z",
        "last_heartbeat_at": "2026-05-15T14:30:00Z",
        "expires_at": "2026-05-15T15:30:00Z"
      }
    ],
    "failed": [
      {
        "path": "Content/Characters/Hero.uasset",
        "reason": "ALREADY_LOCKED",
        "lock_holder": "bob",
        "lock_acquired_at": "2026-05-15T13:00:00Z"
      }
    ]
  }
}

Блокировки истекают через 60 минут без heartbeat. Поле expires_at отражает этот срок. Вызывайте /heartbeat периодически, чтобы продлить блокировку, или /release, чтобы освободить её.

Причины failed:

  • ALREADY_LOCKED: другой пользователь удерживает блокировку (поля lock_holder и lock_acquired_at заполнены)
  • PERMISSION_DENIED: нет права write на этот путь
  • INVALID_PATH: некорректный путь (абсолютный, содержит .. и т. д.)

POST /api/locks/{repo_id}/release

Освобождает блокировки, которыми вы владеете. Body:

{
  "paths": ["Content/Maps/MainLevel.umap"],
  "force": false
}

force: true требует capability force_unlock. Действие аудируется.

POST /api/locks/{repo_id}/heartbeat

Обновляет last_heartbeat_at на ваших блокировках. Рекомендуется каждые 5 минут для долго выполняющихся задач CI, которые хотят, чтобы админы видели активность.

GET /api/locks/{repo_id}/status

Перечисляет все активные блокировки репозитория, с join по users и files для прямого возврата имён.

Query-параметры: user (фильтр по username), limit, offset.

Response 200:

{
  "success": true,
  "data": {
    "locks": [
      {
        "file_path": "Content/Maps/MainLevel.umap",
        "user_id": 12,
        "username": "alice",
        "acquired_at": "2026-05-15T14:30:00Z",
        "last_heartbeat_at": "2026-05-15T16:00:00Z"
      }
    ],
    "total": 1
  }
}

Комментарии и ревью

GET /api/comments/{repo_id}/comments?commit_hash=<hash>

Перечисляет комментарии commit, в виде тредов (родитель → потомки).

Response 200:

{
  "success": true,
  "data": [
    {
      "id": 42,
      "commit_hash": "7f3a9b1c...",
      "file_path": "Content/Maps/MainLevel.umap",
      "author_id": 12,
      "author_username": "alice",
      "body": "LGTM, ship it",
      "parent_id": null,
      "created_at": "2026-05-15T15:00:00Z",
      "replies": [
        {
          "id": 43,
          "author_username": "bob",
          "body": "Thanks!",
          "parent_id": 42,
          "created_at": "2026-05-15T15:05:00Z"
        }
      ]
    }
  ]
}

POST /api/comments/{repo_id}/comments

Создаёт комментарий. file_path необязателен (комментарий к commit vs. к файлу). parent_id для ответа на существующий тред.

Body:

{
  "commit_hash": "7f3a9b1c...",
  "file_path": "Content/Maps/MainLevel.umap",
  "body": "LGTM, ship it",
  "parent_id": null
}

POST /api/comments/{repo_id}/reviews

Отправляет ревью по commit. Требуется capability approve_changes.

Body:

{
  "commit_hash": "7f3a9b1c...",
  "status": "approved",
  "comment": "Lighting looks great, approving"
}

Принимаемый status: approved, changes_requested, pending.

Список наблюдения

GET /api/watchlist/{repo_id}/watchlist

Перечисляет паттерны watch текущего пользователя на этом репозитории.

Response 200:

{
  "success": true,
  "data": [
    {
      "id": 1,
      "user_id": 12,
      "pattern": "Content/Characters/Hero/**",
      "notify_on": ["commit", "lock"],
      "created_at": "2026-04-10T10:00:00Z"
    }
  ]
}

POST /api/watchlist/{repo_id}/watchlist

Добавляет паттерн watch. Body:

{
  "pattern": "Content/Characters/Hero/**",
  "notify_on": ["commit", "lock"]
}

Возможные события: commit (commit изменил соответствующий файл), lock (была получена блокировка), review (было опубликовано ревью).

DELETE /api/watchlist/{repo_id}/watchlist/{watch_id}

Удаляет паттерн watch. Возвращает { "success": true }.

Доска производства (tasks)

Старый API modification-requests был удалён: доска производства его поглощает (запросы становятся карточками, origin='request'). Эндпоинты вложены под /api/tasks/{repo_id}.

МаршрутОписание
GET /api/tasks/{repo_id}/boardПолная доска: колонки + карточки
POST /api/tasks/{repo_id}/tasksСоздаёт карточку
PUT/DELETE /api/tasks/{repo_id}/tasks/{task_id}Обновляет / удаляет карточку
PUT /api/tasks/{repo_id}/tasks/{task_id}/assigneesНазначает пользователей на карточку
GET /api/tasks/{repo_id}/assignable-usersСписок назначаемых пользователей
POST /api/tasks/{repo_id}/columnsНастраивает колонки доски

Также доступны подмаршруты: комментарии карточек, ссылки на assets и commit, и вложения (с обложкой). Настройка колонок требует capability manage_board (admin / lead).

Сборки

POST /api/builds/{repo_id}/upload?hash=<sha256>

Загружает файл сборки в <storage_path>/builds/<hash>. Сервер проверяет SHA-256, переданный в query-параметре, по полученному содержимому. Если хеш уже существует на стороне сервера, сразу возвращает 200 без повторного копирования (dedup на стороне build storage).

Заголовки: Content-Type: application/octet-stream.

Body: сырой бинарный (без JSON-обёртки).

Ограничения: 8 GB на файл.

Ошибки:

  • 400: query-параметр hash отсутствует или некорректен, или вычисленный SHA-256 != переданному хешу
  • 413: файл > 8 GB

POST /api/builds/{repo_id}/publish

Регистрирует manifest сборки после загрузки всех её файлов. Требуется capability publish_builds.

Body:

{
  "version": "0.1.5-nightly",
  "config": "Development",
  "platform": "Win64",
  "executable_path": "HeroRPG/Binaries/Win64/HeroRPG.exe",
  "release_notes": "Nightly build of main branch, 2026-05-15",
  "files": [
    { "path": "HeroRPG.exe",                "hash": "abc123...", "size_bytes": 12345678 },
    { "path": "HeroRPG/Content/Paks/pak0.pak", "hash": "def456...", "size_bytes": 2147483648 }
  ]
}

GET /api/builds/{repo_id}

Перечисляет опубликованные сборки. Требуется capability download_builds, чтобы видеть ссылки на скачивание.

Response 200:

{
  "success": true,
  "data": [
    {
      "id": 5,
      "version": "0.1.5-nightly",
      "config": "Development",
      "platform": "Win64",
      "published_by": "ci-nightly",
      "published_at": "2026-05-15T03:00:00Z",
      "size_bytes": 2159829326,
      "file_count": 142,
      "release_notes": "Nightly build of main branch, 2026-05-15"
    }
  ]
}

GET /api/builds/{repo_id}/{build_id}/file

Скачивает файл сборки. Требуется capability download_builds. Manifest сборки доступен через GET /api/builds/{repo_id}/{build_id}/manifest.

Администрирование

Все маршруты /api/admin/* требуют, чтобы у пользователя была хотя бы одна admin-capability (в зависимости от эндпоинта: manage_users, manage_permissions, manage_rules и т. д.). Краткая документация ниже. Большинство следуют стандартному паттерну REST (GET/POST/PUT/DELETE).

МаршрутCapabilityОписание
GET/POST /api/admin/usersmanage_usersПеречисляет / создаёт пользователей
PUT/DELETE /api/admin/users/{id}manage_usersОбновляет / удаляет
POST /api/admin/users/{id}/reset-passwordmanage_usersСбрасывает пароль, увеличивает token_version
GET/POST /api/admin/groupsmanage_usersУправление группами
GET/POST /api/admin/permissions/{repo_id}manage_permissionsGlob-правила прав по пути
GET/POST /api/admin/repositories/{repo_id}/rulesmanage_rulesПравила валидации перед checkin
GET/POST /api/admin/repositories/{repo_id}/webhooksmanage_rulesDiscord / Slack / Teams / custom
GET /api/admin/auditview_all_activityОтфильтрованный и пагинированный audit log
GET /api/admin/statsview_all_activityStorage metrics, dedup ratio, рост
GET /api/admin/repositories/{repo_id}/gc/previewadminПредпросмотр GC без выполнения
POST /api/admin/repositories/{repo_id}/gcadminЗапускает garbage collection
POST /api/admin/locks/{id}/force-releaseforce_unlockПринудительно снимает чужую блокировку, аудируется

Здоровье

GET /health

Не аутентифицированный эндпоинт, возвращающий 200 OK, если сервер может обратиться к базе данных. Используется для health-check балансировщиков нагрузки или мониторинга (Prometheus blackbox, Datadog synthetic и т. д.).

Response 200: OK (text/plain).

Response 503: если база данных недоступна.

GET /api/server-info

Публичный, не аутентифицированный эндпоинт, возвращающий метаданные сервера (версия и т. д.). Как и /health, он не защищён middleware аутентификации.

Формат ошибок

В случае ошибки ответ: { "success": false, "error": "..." } с подходящим HTTP-статусом. Поле error: сообщение, читаемое человеком. Для клиентов, которым нужны стабильные машиночитаемые коды, парсите префикс (напр.: "Permission denied: ..." всегда начинается с "Permission denied").

HTTPЗначениеКогда
400Bad requestНедопустимый параметр, некорректный JSON, слишком большое тело (до жёсткого лимита 413)
401Not authenticatedТокен отсутствует, истёк, недействительная подпись, или несоответствие token_version
403Permission deniedОтсутствует capability, или нет прав на путь / репозиторий
404Resource not foundРепозиторий / файл / commit / пользователь не существует или недоступен
409ConflictБлокировка уже удерживается, конкурентный commit, нарушено ограничение unique
413Payload too largeТело сверх лимита (1 GB на /files, 8 GB на /builds/upload)
429Too many requestsТолько на /auth/login (ограничение частоты по пользователю)
500Internal server errorОшибка DB, ошибка IO. Логируется на стороне сервера; приложите timestamp, если сообщаете о баге.
503Service unavailableБаза данных недоступна (health check) или выполняется миграция