Files
pipu-music/api/openapi.yaml
T
mrleo1nidandClaude Opus 5.5 c583991d14
ci / api / api (push) Successful in 26s
ci / mobile / mobile (Metro-бандл Android) (push) Failing after 22s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 37s
ci / ts / ts (packages + web) (push) Successful in 51s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 53s
feat(api): library sources, scanner and catalog (M2)
- schema for sources, path patterns, media files, artists, albums,
  tracks, genres (migration 00003)
- path patterns (RE2 with named groups, skip), merge with tags by
  source priority, fallbacks from the file name and folder, name
  normalization and multi-artist split
- scanner: walk, skip unchanged files by size and mtime, ffprobe in 8
  workers with a single catalog writer, renames by content hash keep
  the track, duplicates fold into one track with the best file as
  primary, missing files are kept 30 days
- storage-agent mounts NFS with validated server, export and an option
  whitelist, no shell; test mount counts audio files
- admin API: sources CRUD, connection test, patterns with preview,
  scan, library status, file errors, host folder picker; scheduled
  scans and health checks; mounts are restored on start
- catalog API: artist, artist albums, album with tracks, track
- api image is alpine with ffmpeg for ffprobe

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 06:48:37 +03:00

1525 lines
45 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
openapi: 3.0.3
info:
title: Pipu Music API
version: 0.1.0
description: |
Публичный REST API Pipu Music. Источник истины для сервера (oapi-codegen)
и клиентов (openapi-typescript). Соглашения — docs/api.md.
servers:
- url: /
tags:
- name: system
- name: auth
- name: me
- name: admin
- name: library
- name: catalog
security:
- bearerAuth: []
paths:
/healthz:
get:
operationId: getHealthz
tags: [system]
security: []
summary: Живость процесса
responses:
"200":
description: Процесс жив
content:
application/json:
schema:
$ref: "#/components/schemas/HealthStatus"
/readyz:
get:
operationId: getReadyz
tags: [system]
security: []
summary: Готовность обслуживать запросы (зависимости доступны)
responses:
"200":
description: Все зависимости доступны
content:
application/json:
schema:
$ref: "#/components/schemas/ReadyStatus"
"503":
description: Одна или несколько зависимостей недоступны
content:
application/json:
schema:
$ref: "#/components/schemas/ReadyStatus"
/api/v1/server-info:
get:
operationId: getServerInfo
tags: [system]
security: []
summary: Сведения о сервере для клиентов
description: |
Вызывается без аутентификации: веб и Android проверяют версию сервера,
совместимость клиента и поддерживаемые языки (экран подключения).
responses:
"200":
description: Сведения о сервере
content:
application/json:
schema:
$ref: "#/components/schemas/ServerInfo"
default:
$ref: "#/components/responses/Problem"
/api/v1/auth/login:
post:
operationId: login
tags: [auth]
security: []
summary: Вход по логину и паролю
description: |
Веб (`client_type: web`) получает refresh-токен в cookie `pipu_rt`
(`HttpOnly; Secure; SameSite=Strict; Path=/api/v1/auth`), Android — в поле `refresh_token`.
Неверный логин или пароль, а также временно заблокированный перебором вход — всегда
`401 auth.invalid_credentials`. Превышен лимит попыток с адреса — `429 auth.too_many_attempts`.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/LoginRequest"
responses:
"200":
description: Успешный вход
content:
application/json:
schema:
$ref: "#/components/schemas/AuthTokens"
default:
$ref: "#/components/responses/Problem"
/api/v1/auth/refresh:
post:
operationId: refreshTokens
tags: [auth]
security: []
summary: Ротация refresh-токена
description: |
Refresh-токен берётся из cookie `pipu_rt` (веб) или из тела (Android). Старый токен
становится использованным; его повторное предъявление отзывает всю семью токенов.
parameters:
- name: pipu_rt
in: cookie
required: false
schema:
type: string
requestBody:
required: false
content:
application/json:
schema:
$ref: "#/components/schemas/RefreshRequest"
responses:
"200":
description: Новая пара токенов
content:
application/json:
schema:
$ref: "#/components/schemas/AuthTokens"
default:
$ref: "#/components/responses/Problem"
/api/v1/auth/logout:
post:
operationId: logout
tags: [auth]
summary: Выход — отзыв текущей сессии
responses:
"204":
description: Сессия завершена, cookie `pipu_rt` очищена
default:
$ref: "#/components/responses/Problem"
/api/v1/auth/logout-all:
post:
operationId: logoutAll
tags: [auth]
summary: Выход на всех устройствах
responses:
"204":
description: Все сессии пользователя завершены
default:
$ref: "#/components/responses/Problem"
/api/v1/me:
get:
operationId: getMe
tags: [me]
summary: Текущий пользователь
responses:
"200":
description: Профиль
content:
application/json:
schema:
$ref: "#/components/schemas/Me"
default:
$ref: "#/components/responses/Problem"
patch:
operationId: updateMe
tags: [me]
summary: Изменить имя, язык, настройки интерфейса
description: Поля `ui_prefs` сливаются с сохранёнными; не переданные поля не меняются.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateMeRequest"
responses:
"200":
description: Обновлённый профиль
content:
application/json:
schema:
$ref: "#/components/schemas/Me"
default:
$ref: "#/components/responses/Problem"
/api/v1/me/password:
post:
operationId: changePassword
tags: [me]
summary: Сменить пароль
description: |
`current_password` обязателен, кроме случая `must_change_password` (вход по временному паролю).
Все сессии отзываются; текущему устройству выдаётся новая пара токенов.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ChangePasswordRequest"
responses:
"200":
description: Пароль изменён, новая пара токенов
content:
application/json:
schema:
$ref: "#/components/schemas/AuthTokens"
default:
$ref: "#/components/responses/Problem"
/api/v1/me/bootstrap-credentials:
post:
operationId: setBootstrapCredentials
tags: [me]
summary: Первый запуск — новые логин и пароль администратора
description: |
Только при `must_change_credentials`. Пароль должен отличаться от текущего.
Все прочие сессии отзываются, выдаётся новая пара токенов.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/BootstrapCredentialsRequest"
responses:
"200":
description: Учётные данные заменены, новая пара токенов
content:
application/json:
schema:
$ref: "#/components/schemas/AuthTokens"
default:
$ref: "#/components/responses/Problem"
/api/v1/me/sessions:
get:
operationId: listMySessions
tags: [me]
summary: Активные сессии (устройства)
responses:
"200":
description: Список сессий, новые сверху
content:
application/json:
schema:
$ref: "#/components/schemas/SessionList"
default:
$ref: "#/components/responses/Problem"
/api/v1/me/sessions/{id}:
delete:
operationId: revokeMySession
tags: [me]
summary: Завершить сессию на устройстве
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"204":
description: Сессия завершена
default:
$ref: "#/components/responses/Problem"
/api/v1/admin/users:
get:
operationId: listUsers
tags: [admin]
summary: Пользователи (admin, moderator)
description: Сортировка по логину. `q` ищет по логину и имени без учёта регистра.
parameters:
- name: q
in: query
schema:
type: string
maxLength: 64
- name: role
in: query
schema:
$ref: "#/components/schemas/Role"
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Cursor"
responses:
"200":
description: Страница пользователей
content:
application/json:
schema:
$ref: "#/components/schemas/AdminUserPage"
default:
$ref: "#/components/responses/Problem"
post:
operationId: createUser
tags: [admin]
summary: Создать пользователя
description: |
Пароль генерирует сервер и возвращает **один раз**; при первом входе его нужно сменить.
Модератор создаёт только `user`, админ — любые роли.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateUserRequest"
responses:
"201":
description: Пользователь создан
content:
application/json:
schema:
$ref: "#/components/schemas/CreatedUser"
default:
$ref: "#/components/responses/Problem"
/api/v1/admin/users/{id}:
parameters:
- $ref: "#/components/parameters/UserId"
patch:
operationId: updateUser
tags: [admin]
summary: Изменить имя, роль, блокировку
description: |
Роль меняет только админ. Смена роли и блокировка мгновенно отзывают токены пользователя.
Последнего активного админа нельзя понизить или заблокировать — `409 users.last_admin`.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateUserRequest"
responses:
"200":
description: Обновлённый пользователь
content:
application/json:
schema:
$ref: "#/components/schemas/AdminUser"
default:
$ref: "#/components/responses/Problem"
delete:
operationId: deleteUser
tags: [admin]
summary: Удалить пользователя (admin)
responses:
"204":
description: Удалён
default:
$ref: "#/components/responses/Problem"
/api/v1/admin/users/{id}/reset-password:
parameters:
- $ref: "#/components/parameters/UserId"
post:
operationId: resetUserPassword
tags: [admin]
summary: Сбросить пароль
description: Новый временный пароль возвращается один раз; все сессии пользователя завершаются.
responses:
"200":
description: Временный пароль
content:
application/json:
schema:
$ref: "#/components/schemas/TemporaryPassword"
default:
$ref: "#/components/responses/Problem"
/api/v1/admin/audit:
get:
operationId: listAudit
tags: [admin]
summary: Журнал действий (admin), новые сверху
parameters:
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Cursor"
responses:
"200":
description: Страница журнала
content:
application/json:
schema:
$ref: "#/components/schemas/AuditPage"
default:
$ref: "#/components/responses/Problem"
/api/v1/admin/sources:
get:
operationId: listSources
tags: [library]
summary: Хранилища (admin)
responses:
"200":
description: Список хранилищ
content:
application/json:
schema:
$ref: "#/components/schemas/SourceList"
default:
$ref: "#/components/responses/Problem"
post:
operationId: createSource
tags: [library]
summary: Подключить хранилище (admin)
description: |
`local` — подпапка в каталоге LIBRARY_HOST сервера; `nfs` — монтирует storage-agent.
Хранилище сохраняется, даже если подключиться не удалось: статус и сообщение — в ответе.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/SourceInput"
responses:
"201":
description: Хранилище создано
content:
application/json:
schema:
$ref: "#/components/schemas/Source"
default:
$ref: "#/components/responses/Problem"
/api/v1/admin/sources/{id}:
parameters:
- $ref: "#/components/parameters/SourceId"
get:
operationId: getSource
tags: [library]
summary: Хранилище (admin)
responses:
"200":
description: Хранилище
content:
application/json:
schema:
$ref: "#/components/schemas/Source"
default:
$ref: "#/components/responses/Problem"
patch:
operationId: updateSource
tags: [library]
summary: Изменить хранилище (admin)
description: Изменение адреса NFS или папки переподключает хранилище.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/SourcePatch"
responses:
"200":
description: Хранилище
content:
application/json:
schema:
$ref: "#/components/schemas/Source"
default:
$ref: "#/components/responses/Problem"
delete:
operationId: deleteSource
tags: [library]
summary: Отключить и удалить хранилище (admin)
description: Треки, файлы которых были только в этом хранилище, удаляются из каталога.
responses:
"204":
description: Удалено
default:
$ref: "#/components/responses/Problem"
/api/v1/admin/sources/{id}/test:
parameters:
- $ref: "#/components/parameters/SourceId"
post:
operationId: testSource
tags: [library]
summary: Проверить подключение (admin)
responses:
"200":
description: Результат проверки
content:
application/json:
schema:
$ref: "#/components/schemas/SourceTestResult"
default:
$ref: "#/components/responses/Problem"
/api/v1/admin/sources/{id}/patterns:
parameters:
- $ref: "#/components/parameters/SourceId"
get:
operationId: getSourcePatterns
tags: [library]
summary: Шаблоны путей (admin)
responses:
"200":
description: Шаблоны по порядку
content:
application/json:
schema:
$ref: "#/components/schemas/PatternList"
default:
$ref: "#/components/responses/Problem"
put:
operationId: putSourcePatterns
tags: [library]
summary: Заменить шаблоны путей (admin)
description: Ошибка компиляции — `422 validation.pattern_invalid` с `params.index` и `params.reason`.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PatternList"
responses:
"200":
description: Сохранённые шаблоны
content:
application/json:
schema:
$ref: "#/components/schemas/PatternList"
default:
$ref: "#/components/responses/Problem"
/api/v1/admin/sources/{id}/patterns/preview:
parameters:
- $ref: "#/components/parameters/SourceId"
post:
operationId: previewSourcePatterns
tags: [library]
summary: Предпросмотр разбора путей без сохранения (admin)
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PatternPreviewRequest"
responses:
"200":
description: Разбор выборки файлов
content:
application/json:
schema:
$ref: "#/components/schemas/PatternPreview"
default:
$ref: "#/components/responses/Problem"
/api/v1/admin/sources/{id}/scan:
parameters:
- $ref: "#/components/parameters/SourceId"
post:
operationId: scanSource
tags: [library]
summary: Запустить скан (admin)
description: Скан идёт в фоне; прогресс — в `GET /admin/library/status`. Уже идёт — `409 library.scan_running`.
requestBody:
required: false
content:
application/json:
schema:
$ref: "#/components/schemas/ScanRequest"
responses:
"202":
description: Скан запущен
content:
application/json:
schema:
$ref: "#/components/schemas/ScanProgress"
default:
$ref: "#/components/responses/Problem"
/api/v1/admin/library/status:
get:
operationId: getLibraryStatus
tags: [library]
summary: Состояние библиотеки (admin, moderator)
responses:
"200":
description: Счётчики и сканы
content:
application/json:
schema:
$ref: "#/components/schemas/LibraryStatus"
default:
$ref: "#/components/responses/Problem"
/api/v1/admin/library/errors:
get:
operationId: listLibraryErrors
tags: [library]
summary: Файлы, которые не удалось прочитать (admin)
parameters:
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Cursor"
responses:
"200":
description: Страница ошибок
content:
application/json:
schema:
$ref: "#/components/schemas/LibraryErrorPage"
default:
$ref: "#/components/responses/Problem"
/api/v1/admin/library/host-dirs:
get:
operationId: listHostDirs
tags: [library]
summary: Подпапки каталога LIBRARY_HOST — выбор папки для local-хранилища (admin)
parameters:
- name: path
in: query
description: Путь относительно корня, пусто — корень
schema:
type: string
responses:
"200":
description: Подпапки
content:
application/json:
schema:
$ref: "#/components/schemas/HostDirList"
default:
$ref: "#/components/responses/Problem"
/api/v1/artists/{id}:
parameters:
- $ref: "#/components/parameters/EntityId"
get:
operationId: getArtist
tags: [catalog]
summary: Исполнитель
responses:
"200":
description: Исполнитель
content:
application/json:
schema:
$ref: "#/components/schemas/Artist"
default:
$ref: "#/components/responses/Problem"
/api/v1/artists/{id}/albums:
parameters:
- $ref: "#/components/parameters/EntityId"
get:
operationId: listArtistAlbums
tags: [catalog]
summary: Альбомы исполнителя (свои и сборники с его треками)
responses:
"200":
description: Альбомы исполнителя (свои и сборники с его треками)
content:
application/json:
schema:
$ref: "#/components/schemas/AlbumSummaryList"
default:
$ref: "#/components/responses/Problem"
/api/v1/albums/{id}:
parameters:
- $ref: "#/components/parameters/EntityId"
get:
operationId: getAlbum
tags: [catalog]
summary: Альбом с треками
responses:
"200":
description: Альбом с треками
content:
application/json:
schema:
$ref: "#/components/schemas/Album"
default:
$ref: "#/components/responses/Problem"
/api/v1/tracks/{id}:
parameters:
- $ref: "#/components/parameters/EntityId"
get:
operationId: getTrack
tags: [catalog]
summary: Трек
responses:
"200":
description: Трек
content:
application/json:
schema:
$ref: "#/components/schemas/Track"
default:
$ref: "#/components/responses/Problem"
components:
parameters:
SourceId:
name: id
in: path
required: true
schema:
type: string
format: uuid
EntityId:
name: id
in: path
required: true
schema:
type: string
format: uuid
Limit:
name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 200
default: 50
Cursor:
name: cursor
in: query
description: Непрозрачный курсор из `next_cursor` предыдущей страницы
schema:
type: string
UserId:
name: id
in: path
required: true
schema:
type: string
format: uuid
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
responses:
Problem:
description: Ошибка (RFC 9457)
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
schemas:
HealthStatus:
type: object
required: [status]
properties:
status:
type: string
enum: [ok]
ReadyStatus:
type: object
required: [status, checks]
properties:
status:
type: string
enum: [ok, degraded]
checks:
type: object
description: Результат проверки каждой зависимости
additionalProperties:
type: string
description: "`ok` или `fail` (причина — только в логе сервиса)"
ServerInfo:
type: object
required: [name, version, api_version, supported_locales, min_android_version_code, bootstrap_required]
properties:
name:
type: string
example: pipu-music
version:
type: string
description: Версия сервера (SemVer)
example: 0.1.0
api_version:
type: string
example: v1
supported_locales:
type: array
items:
type: string
example: [ru, en]
min_android_version_code:
type: integer
description: Минимальный versionCode Android-приложения, которое поддерживает сервер
example: 10099
bootstrap_required:
type: boolean
description: Первый вход администратора ещё не завершён — клиент может показать подсказку
Role:
type: string
enum: [admin, moderator, user]
ClientType:
type: string
enum: [web, android]
LoginRequest:
type: object
required: [username, password, client_type]
properties:
username:
type: string
password:
type: string
client_type:
$ref: "#/components/schemas/ClientType"
device_name:
type: string
maxLength: 100
description: Например «Chrome / Windows», «Pixel 8»
RefreshRequest:
type: object
properties:
refresh_token:
type: string
description: Только Android; веб передаёт токен в cookie
AuthTokens:
type: object
required: [access_token, token_type, expires_in, user]
properties:
access_token:
type: string
token_type:
type: string
enum: [Bearer]
expires_in:
type: integer
description: Срок жизни access-токена, секунды
refresh_token:
type: string
description: "Только для `client_type: android`"
user:
$ref: "#/components/schemas/Me"
UiPrefs:
type: object
properties:
theme:
type: string
enum: [dark, light, system]
wave_variant:
type: string
enum: [orb, liquid]
web_player:
type: string
enum: [bar, island]
mobile_player:
type: string
enum: [cover, wave]
ambient:
type: boolean
crossfade_s:
type: integer
minimum: 0
maximum: 6
normalize:
type: boolean
Me:
type: object
required: [id, username, display_name, role, locale, ui_prefs, must_change_password, must_change_credentials, created_at]
properties:
id:
type: string
format: uuid
username:
type: string
display_name:
type: string
nullable: true
role:
$ref: "#/components/schemas/Role"
locale:
type: string
ui_prefs:
$ref: "#/components/schemas/UiPrefs"
must_change_password:
type: boolean
must_change_credentials:
type: boolean
created_at:
type: string
format: date-time
UpdateMeRequest:
type: object
properties:
display_name:
type: string
maxLength: 64
description: Пустая строка — сбросить имя
locale:
type: string
ui_prefs:
$ref: "#/components/schemas/UiPrefs"
ChangePasswordRequest:
type: object
required: [new_password]
properties:
current_password:
type: string
new_password:
type: string
BootstrapCredentialsRequest:
type: object
required: [new_username, new_password]
properties:
new_username:
type: string
new_password:
type: string
AdminUser:
type: object
required: [id, username, display_name, role, locale, is_blocked, must_change_password, must_change_credentials, created_at, last_login_at]
properties:
id:
type: string
format: uuid
username:
type: string
display_name:
type: string
nullable: true
role:
$ref: "#/components/schemas/Role"
locale:
type: string
is_blocked:
type: boolean
must_change_password:
type: boolean
must_change_credentials:
type: boolean
created_at:
type: string
format: date-time
last_login_at:
type: string
format: date-time
nullable: true
AdminUserPage:
type: object
required: [items, next_cursor]
properties:
items:
type: array
items:
$ref: "#/components/schemas/AdminUser"
next_cursor:
type: string
nullable: true
CreateUserRequest:
type: object
required: [username, role]
properties:
username:
type: string
display_name:
type: string
maxLength: 64
role:
$ref: "#/components/schemas/Role"
CreatedUser:
type: object
required: [user, temporary_password]
properties:
user:
$ref: "#/components/schemas/AdminUser"
temporary_password:
type: string
description: Показывается один раз
UpdateUserRequest:
type: object
properties:
display_name:
type: string
maxLength: 64
description: Пустая строка — сбросить имя
role:
$ref: "#/components/schemas/Role"
is_blocked:
type: boolean
TemporaryPassword:
type: object
required: [temporary_password]
properties:
temporary_password:
type: string
AuditActor:
type: object
required: [id, username]
properties:
id:
type: string
format: uuid
username:
type: string
AuditEntry:
type: object
required: [id, actor, action, target_type, target_id, payload, created_at]
properties:
id:
type: string
format: uuid
actor:
description: null — система или удалённый пользователь
nullable: true
allOf:
- $ref: "#/components/schemas/AuditActor"
action:
type: string
example: user.create
target_type:
type: string
nullable: true
target_id:
type: string
format: uuid
nullable: true
payload:
type: object
nullable: true
additionalProperties: true
created_at:
type: string
format: date-time
AuditPage:
type: object
required: [items, next_cursor]
properties:
items:
type: array
items:
$ref: "#/components/schemas/AuditEntry"
next_cursor:
type: string
nullable: true
SourceType:
type: string
enum: [nfs, local]
SourceStatus:
type: string
enum: [pending, mounted, error, disabled]
TagPriority:
type: string
enum: [tags_first, path_first]
Source:
type: object
required: [id, name, type, nfs_server, nfs_export, nfs_options, local_path, tag_priority, scan_interval_minutes, status, status_message, last_scan_at, created_at, scan]
properties:
id:
type: string
format: uuid
name:
type: string
type:
$ref: "#/components/schemas/SourceType"
nfs_server:
type: string
nullable: true
nfs_export:
type: string
nullable: true
nfs_options:
type: string
local_path:
type: string
nullable: true
description: Подпапка относительно LIBRARY_HOST, пусто — весь каталог
tag_priority:
$ref: "#/components/schemas/TagPriority"
scan_interval_minutes:
type: integer
nullable: true
description: Автоматический скан, null — только вручную
status:
$ref: "#/components/schemas/SourceStatus"
status_message:
type: string
nullable: true
last_scan_at:
type: string
format: date-time
nullable: true
created_at:
type: string
format: date-time
scan:
nullable: true
allOf:
- $ref: "#/components/schemas/ScanProgress"
SourceList:
type: object
required: [items]
properties:
items:
type: array
items:
$ref: "#/components/schemas/Source"
SourceInput:
type: object
required: [name, type]
properties:
name:
type: string
maxLength: 100
type:
$ref: "#/components/schemas/SourceType"
nfs_server:
type: string
nfs_export:
type: string
nfs_options:
type: string
local_path:
type: string
tag_priority:
$ref: "#/components/schemas/TagPriority"
scan_interval_minutes:
type: integer
minimum: 5
nullable: true
SourcePatch:
type: object
properties:
name:
type: string
maxLength: 100
nfs_server:
type: string
nfs_export:
type: string
nfs_options:
type: string
local_path:
type: string
tag_priority:
$ref: "#/components/schemas/TagPriority"
scan_interval_minutes:
type: integer
description: 0 — отключить автоматический скан
enabled:
type: boolean
description: false — отключить хранилище (треки становятся недоступны), true — подключить снова
SourceTestResult:
type: object
required: [ok, message, audio_files]
properties:
ok:
type: boolean
message:
type: string
audio_files:
type: integer
description: Сколько аудиофайлов найдено (проверка останавливается на 1000)
PathPattern:
type: object
required: [regex, is_enabled]
properties:
regex:
type: string
maxLength: 1000
is_enabled:
type: boolean
PatternList:
type: object
required: [patterns]
properties:
patterns:
type: array
maxItems: 50
items:
$ref: "#/components/schemas/PathPattern"
PatternPreviewRequest:
type: object
required: [patterns]
properties:
patterns:
type: array
maxItems: 50
items:
$ref: "#/components/schemas/PathPattern"
sample:
type: integer
minimum: 1
maximum: 200
default: 50
TrackMeta:
type: object
required: [artist, album_artist, album, title, genres, year, track, disc]
properties:
artist:
type: string
album_artist:
type: string
album:
type: string
title:
type: string
genres:
type: array
items:
type: string
year:
type: integer
nullable: true
track:
type: integer
nullable: true
disc:
type: integer
nullable: true
PreviewItem:
type: object
required: [rel_path, pattern_index, skipped, path_fields, tags, result, error]
properties:
rel_path:
type: string
pattern_index:
type: integer
nullable: true
description: Номер совпавшего шаблона, null — ни один
skipped:
type: boolean
path_fields:
type: object
additionalProperties:
type: string
tags:
type: object
additionalProperties:
type: string
result:
$ref: "#/components/schemas/TrackMeta"
error:
type: string
nullable: true
PatternPreview:
type: object
required: [items, unmatched]
properties:
items:
type: array
items:
$ref: "#/components/schemas/PreviewItem"
unmatched:
type: integer
ScanRequest:
type: object
properties:
full:
type: boolean
description: Перечитать все файлы, а не только новые и изменённые
ScanProgress:
type: object
required: [running, full, started_at, finished_at, found, to_process, processed, added, updated, moved, missing, errors, error]
properties:
running:
type: boolean
full:
type: boolean
started_at:
type: string
format: date-time
finished_at:
type: string
format: date-time
nullable: true
found:
type: integer
to_process:
type: integer
processed:
type: integer
added:
type: integer
updated:
type: integer
moved:
type: integer
missing:
type: integer
errors:
type: integer
error:
type: string
nullable: true
LibraryStatus:
type: object
required: [artists, albums, tracks, files, missing_files, file_errors, sources]
properties:
artists:
type: integer
albums:
type: integer
tracks:
type: integer
files:
type: integer
missing_files:
type: integer
file_errors:
type: integer
sources:
type: array
items:
$ref: "#/components/schemas/SourceStatusItem"
SourceStatusItem:
type: object
required: [id, name, status, status_message, last_scan_at, scan]
properties:
id:
type: string
format: uuid
name:
type: string
status:
$ref: "#/components/schemas/SourceStatus"
status_message:
type: string
nullable: true
last_scan_at:
type: string
format: date-time
nullable: true
scan:
nullable: true
allOf:
- $ref: "#/components/schemas/ScanProgress"
LibraryError:
type: object
required: [file_id, source_id, rel_path, error]
properties:
file_id:
type: string
format: uuid
source_id:
type: string
format: uuid
rel_path:
type: string
error:
type: string
LibraryErrorPage:
type: object
required: [items, next_cursor]
properties:
items:
type: array
items:
$ref: "#/components/schemas/LibraryError"
next_cursor:
type: string
nullable: true
HostDirList:
type: object
required: [path, dirs]
properties:
path:
type: string
dirs:
type: array
items:
type: string
ArtistRef:
type: object
required: [id, name]
properties:
id:
type: string
format: uuid
name:
type: string
AlbumRef:
type: object
required: [id, title, year, cover_url, cover_colors]
properties:
id:
type: string
format: uuid
title:
type: string
year:
type: integer
nullable: true
cover_url:
type: string
nullable: true
cover_colors:
type: array
items:
type: string
Track:
type: object
required: [id, title, duration_ms, artists, album, genres, disc_no, track_no, available]
properties:
id:
type: string
format: uuid
title:
type: string
duration_ms:
type: integer
artists:
type: array
items:
$ref: "#/components/schemas/ArtistRef"
album:
$ref: "#/components/schemas/AlbumRef"
genres:
type: array
items:
type: string
disc_no:
type: integer
nullable: true
track_no:
type: integer
nullable: true
available:
type: boolean
description: Есть доступный файл (хранилище подключено, файл на месте)
Artist:
type: object
required: [id, name, album_count, track_count]
properties:
id:
type: string
format: uuid
name:
type: string
album_count:
type: integer
track_count:
type: integer
AlbumSummary:
type: object
required: [id, title, year, cover_url, cover_colors, artist, track_count]
properties:
id:
type: string
format: uuid
title:
type: string
year:
type: integer
nullable: true
cover_url:
type: string
nullable: true
cover_colors:
type: array
items:
type: string
artist:
$ref: "#/components/schemas/ArtistRef"
track_count:
type: integer
AlbumSummaryList:
type: object
required: [items]
properties:
items:
type: array
items:
$ref: "#/components/schemas/AlbumSummary"
Album:
type: object
required: [id, title, year, cover_url, cover_colors, artist, duration_ms, tracks]
properties:
id:
type: string
format: uuid
title:
type: string
year:
type: integer
nullable: true
cover_url:
type: string
nullable: true
cover_colors:
type: array
items:
type: string
artist:
$ref: "#/components/schemas/ArtistRef"
duration_ms:
type: integer
tracks:
type: array
items:
$ref: "#/components/schemas/Track"
Session:
type: object
required: [id, device_name, client_type, created_at, last_used_at, current]
properties:
id:
type: string
format: uuid
device_name:
type: string
nullable: true
client_type:
$ref: "#/components/schemas/ClientType"
created_at:
type: string
format: date-time
description: Время входа
last_used_at:
type: string
format: date-time
current:
type: boolean
description: Сессия, из которой сделан запрос
SessionList:
type: object
required: [items]
properties:
items:
type: array
items:
$ref: "#/components/schemas/Session"
FieldError:
type: object
required: [field, code]
properties:
field:
type: string
code:
type: string
Problem:
type: object
required: [status, code]
properties:
type:
type: string
default: about:blank
status:
type: integer
code:
type: string
description: Машинный код ошибки для i18n, например `auth.invalid_credentials`
detail:
type: string
description: Пояснение для логов (не для показа пользователю)
params:
type: object
additionalProperties: true
errors:
type: array
description: Ошибки отдельных полей запроса
items:
$ref: "#/components/schemas/FieldError"