feat(api): library sources, scanner and catalog (M2)
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
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
- 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>
This commit is contained in:
1 parent
ab092def54
commit
c583991d14
24 files changed
+7467
-84
No files matched your search
@@ -12,6 +12,8 @@ tags:
|
||||
- name: auth
|
||||
- name: me
|
||||
- name: admin
|
||||
- name: library
|
||||
- name: catalog
|
||||
security:
|
||||
- bearerAuth: []
|
||||
paths:
|
||||
@@ -369,8 +371,316 @@ paths:
|
||||
$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
|
||||
@@ -695,6 +1005,462 @@ components:
|
||||
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]
|
||||
|
||||
Reference in new issue
Block a user