feat: storage writes via storage-agent, accepts_uploads and write_template (M5.5)
ci / api / api (push) Successful in 33s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 43s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m11s
ci / ts / ts (packages + web) (push) Successful in 1m10s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 51s

- storage-agent: POST /internal/files copies a staged file into a source
  (temp file, fsync, SHA-256 check, hard link so nothing is overwritten,
  " (2)" suffix on conflict, owner PIPU_FILE_UID/GID) and
  POST /internal/files/test. NFS sources that accept uploads are mounted
  rw in an agent-only /library-rw and bind-mounted read-only for others;
  local sources are written through /library-host-rw.
- meta: write templates ({track:02}, {disc:1-}, [optional]) with name
  sanitising and length limits, suggestion from the source's regexes
  and a reversibility check against 50 catalogue tracks.
- API and admin UI: accepts_uploads / write_template on sources,
  write-template suggest and check endpoints, write test result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Opus 5.5 committed 2026-10-20 13:46:56 +03:00
1 parent 9b50038bb4
commit f66be2ba0a
27 files changed
+2422 -213

No files matched your search

+121 -1
View File
@@ -532,6 +532,48 @@ paths:
$ref: "#/components/schemas/PatternPreview"
default:
$ref: "#/components/responses/Problem"
/api/v1/admin/sources/{id}/write-template/suggest:
parameters:
- $ref: "#/components/parameters/SourceId"
post:
operationId: suggestWriteTemplate
tags: [library]
summary: Подобрать шаблон пути по существующей структуре (admin)
description: |
Выражение хранилища, совпавшее с наибольшим числом путей выборки, превращается в шаблон.
Не удалось (нет файлов или выражения не переводятся) — `422 library.template_unavailable`.
responses:
"200":
description: Предложенный шаблон
content:
application/json:
schema:
$ref: "#/components/schemas/WriteTemplateSuggestion"
default:
$ref: "#/components/responses/Problem"
/api/v1/admin/sources/{id}/write-template/check:
parameters:
- $ref: "#/components/parameters/SourceId"
post:
operationId: checkWriteTemplate
tags: [library]
summary: Проверить шаблон на обратимость (admin)
description: Шаблон применяется к 50 трекам хранилища, пути разбираются его выражениями обратно.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/WriteTemplateCheckRequest"
responses:
"200":
description: Итог проверки
content:
application/json:
schema:
$ref: "#/components/schemas/WriteTemplateCheck"
default:
$ref: "#/components/responses/Problem"
/api/v1/admin/sources/{id}/scan:
parameters:
- $ref: "#/components/parameters/SourceId"
@@ -1453,7 +1495,7 @@ components:
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]
required: [id, name, type, nfs_server, nfs_export, nfs_options, local_path, tag_priority, scan_interval_minutes, accepts_uploads, write_template, status, status_message, last_scan_at, created_at, scan]
properties:
id:
type: string
@@ -1480,6 +1522,13 @@ components:
type: integer
nullable: true
description: Автоматический скан, null — только вручную
accepts_uploads:
type: boolean
description: Принимает импорт из очереди на добавление (NFS монтируется агентом ещё и на запись)
write_template:
type: string
nullable: true
description: Шаблон пути новых файлов (docs/uploads.md#7), null — не задан
status:
$ref: "#/components/schemas/SourceStatus"
status_message:
@@ -1527,6 +1576,11 @@ components:
type: integer
minimum: 5
nullable: true
accepts_uploads:
type: boolean
write_template:
type: string
maxLength: 500
SourcePatch:
type: object
properties:
@@ -1549,6 +1603,15 @@ components:
enabled:
type: boolean
description: false — отключить хранилище (треки становятся недоступны), true — подключить снова
accepts_uploads:
type: boolean
description: Смена переподключает NFS-хранилище (rw-точка агента)
write_template:
type: string
maxLength: 500
description: |
Шаблон пути новых файлов; пусто — убрать. Неверный синтаксис — `422 validation.template_invalid`
с `params.reason`. Проверка обратимости не обязательна для сохранения — её показывает `write-template/check`.
SourceTestResult:
type: object
required: [ok, message, audio_files]
@@ -1560,6 +1623,63 @@ components:
audio_files:
type: integer
description: Сколько аудиофайлов найдено (проверка останавливается на 1000)
write_ok:
type: boolean
nullable: true
description: Удалось создать и удалить `.pipu-write-test` (только для accepts_uploads; иначе null)
write_message:
type: string
nullable: true
WriteTemplateSuggestion:
type: object
required: [template]
properties:
template:
type: string
WriteTemplateCheckRequest:
type: object
required: [template]
properties:
template:
type: string
maxLength: 500
WriteTemplateMismatch:
type: object
required: [rel_path, rendered, fields, error]
properties:
rel_path:
type: string
description: Реальный путь файла
rendered:
type: string
nullable: true
description: Путь по шаблону, null — не построился
fields:
type: array
items:
type: string
description: Поля, которые регэкспы хранилища распознали не так; `pattern` — не совпало ни одно выражение
error:
type: string
nullable: true
WriteTemplateCheck:
type: object
required: [ok, total, round_trip, same_path, mismatches]
properties:
ok:
type: boolean
description: Все пути разобраны обратно без изменений и ≥ 90% совпали с реальными
total:
type: integer
round_trip:
type: integer
same_path:
type: integer
mismatches:
type: array
maxItems: 10
items:
$ref: "#/components/schemas/WriteTemplateMismatch"
PathPattern:
type: object
required: [regex, is_enabled]