feat: finish the upload, playback, and test gaps from the docs
ci / api / api (push) Successful in 40s
ci / db / db (push) Successful in 1m3s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Failing after 1m48s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 46s
ci / ts / ts (packages + web) (push) Failing after 4m57s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 5m7s
ci / api / api (push) Successful in 40s
ci / db / db (push) Successful in 1m3s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Failing after 1m48s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 46s
ci / ts / ts (packages + web) (push) Failing after 4m57s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 5m7s
Quality upgrades, batch covers, staging preview, and resume after reload are in the queue. Loudness and crossfade work on web and Android, and the documented checks now have suites. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
2b76060ea3
commit
066ba24834
61 files changed
+2818
-463
No files matched your search
+10
-2
@@ -136,6 +136,7 @@ CREATE TABLE upload_batches ( -- одна «пачка» загруз
|
||||
id uuid PRIMARY KEY,
|
||||
author_id uuid NOT NULL REFERENCES users(id),
|
||||
comment text, -- комментарий автора для админа
|
||||
cover_path text, -- обложка пачки (JPEG/PNG/WebP), вшивается при импорте
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
@@ -160,6 +161,7 @@ CREATE TABLE upload_files (
|
||||
duplicate_of_track_id uuid REFERENCES tracks(id),
|
||||
duplicate_level text, -- sha256 | metadata | fingerprint
|
||||
possible_duplicate boolean NOT NULL DEFAULT false,
|
||||
quality_upgrade boolean NOT NULL DEFAULT false, -- тот же трек, но лучше по качеству
|
||||
target_source_id uuid REFERENCES library_sources(id),
|
||||
target_rel_path text,
|
||||
status_reason text, -- код причины: uploads.duplicate, uploads.invalid_audio, текст отклонения
|
||||
@@ -173,12 +175,18 @@ CREATE INDEX ON upload_files (status);
|
||||
CREATE INDEX ON upload_files (author_id, created_at DESC);
|
||||
CREATE INDEX ON upload_files (sha256);
|
||||
|
||||
-- Счётчик квоты (одна строка). Обновляется в той же транзакции, что и смена статуса;
|
||||
-- ночная задача сверяет его с SUM(size_bytes) по «занимающим» статусам.
|
||||
-- Счётчик квоты (одна строка). Обновляется в той же транзакции, что и смена статуса.
|
||||
-- При старте api и каждые 10 минут сверяется с SUM(size_bytes) по «занимающим» статусам.
|
||||
CREATE TABLE upload_quota (
|
||||
id boolean PRIMARY KEY DEFAULT true CHECK (id),
|
||||
used_bytes bigint NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
-- Когда автор последний раз открыл «Мои предложения» (бейдж новых решений).
|
||||
CREATE TABLE upload_visits (
|
||||
user_id uuid PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
|
||||
seen_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
```
|
||||
|
||||
## Каталог
|
||||
|
||||
+5
-7
@@ -157,10 +157,8 @@ interface WaveButtonProps {
|
||||
- Лист обновления: «Что нового» → разрешение на установку (один раз) → загрузка → проверка SHA-256 → «Установить».
|
||||
- Офлайн-баннер при отсутствии сети.
|
||||
|
||||
## 5. Пробелы дизайна (дорабатываем при реализации)
|
||||
- **Адаптивность веба**: прототип рассчитан на десктоп (≥ 1200 px). Для < 1024 px сайдбар сворачивается в иконки,
|
||||
для < 768 px — нижняя навигация как в Android. Макетов нет — делаем по образцу Android-версии.
|
||||
- **Пустые состояния и ошибки** есть не везде (например, хранилище недоступно, нет сети в вебе) — по образцу существующих
|
||||
(иконка + короткий текст + действие).
|
||||
- **Онбординг вкуса** (выбор жанров / исполнителей при первом входе) в прототипе отсутствует — макет нужен до M6.
|
||||
- **Английская локализация**: в прототипе только русские строки; при вёрстке проверять длинные строки на обоих языках.
|
||||
## 5. Адаптив и онбординг
|
||||
- **Адаптивность веба**: при ширине < 1024 px сайдбар сворачивается в иконки, при < 768 px — нижняя навигация.
|
||||
- **Онбординг вкуса** показывается на главной, пока `GET /onboarding` отвечает `required`.
|
||||
- **Пустые состояния** — иконка, короткий текст и действие там, где список может быть пустым (поиск, коллекция, очередь).
|
||||
- Строки интерфейса есть на русском и английском; длинные подписи проверяются на обоих языках.
|
||||
+2
-1
@@ -37,7 +37,8 @@ location /_internal/cache/ { internal; alias /cache/transcode/; sendfile on;
|
||||
- Транскод трека 4 мин в Opus занимает ~1–2 с на одном ядре, поэтому делаем синхронно при первом запросе.
|
||||
Клиенты **предзагружают следующий трек очереди** (запрос `HEAD`/`Range: bytes=0-0` за 20 с до конца текущего) —
|
||||
на сервере это прогревает транскод.
|
||||
- Фоновая задача `pretranscode` для треков из выданных пачек волны (они почти наверняка будут сыграны).
|
||||
- Фоновая подготовка: выдача пачки волны вызывает `playback.Prewarm` — для качества `high` транскод
|
||||
строится сразу, не дожидаясь первого `Range` клиента.
|
||||
- Кэш транскодов — LRU с лимитом `TRANSCODE_CACHE_LIMIT_GB` (по умолчанию 50 ГБ), чистка по `atime` раз в час.
|
||||
- Пул ffmpeg ограничен (`TRANSCODE_CONCURRENCY`, по умолчанию = число ядер / 2).
|
||||
|
||||
|
||||
+9
-6
@@ -7,13 +7,13 @@
|
||||
| Unit | Go | `testing`, `testify`, табличные тесты | Политики прав (вся матрица из auth-and-roles.md), парсинг шаблонов путей и слияние с тегами, нормализация имён, подпись ссылок, расчёт `implicit_score`, рендер `write_template` и очистка имён, обратимость шаблон ↔ регэксп, учёт квоты при конкурентных резервах, переходы статусов загрузки |
|
||||
| Unit | Python | `pytest`, `hypothesis` | Формулы скоринга, context_fit, MMR, бандит, обновление сессионного вектора, правила переранжирования |
|
||||
| Unit | TS | `vitest` | `packages/core`: очередь, события (подсчёт `played_ms`, засчитанное прослушивание), single-flight refresh, офлайн-очередь |
|
||||
| Компоненты | Web | `vitest` + Testing Library | Поиск (клавиатура, Enter / выбор), плеер, MoodPicker, формы админки |
|
||||
| Компоненты | Mobile | `jest` + RNTL | Экраны входа, коллекции, диалог обновления |
|
||||
| Интеграция | Go | `testcontainers-go` (Postgres + pgvector, Redis, Meilisearch) | Репозитории, миграции, сканер на тестовой библиотеке, API-хендлеры через `httptest` |
|
||||
| Компоненты | Web | `vitest` + Testing Library + axe-core | Поиск, плеер, волна, формы админки, вход; серьёзные нарушения axe на главной и входе |
|
||||
| Компоненты | Mobile | `vitest` (`apps/mobile/test`) | Офлайн-волна и решение диалога обновления |
|
||||
| Интеграция | Go | `testcontainers-go` (Postgres + pgvector, Redis, Meilisearch) | Репозитории, миграции, сканер, API через `httptest`; `TestUserJourney` — сценарии 1–2 и скан |
|
||||
| Интеграция | Python | `testcontainers` | Чтение событий из Redis Stream, kNN в pgvector, ночной пайплайн на синтетических данных |
|
||||
| Контракт | — | `schemathesis` против запущенного `api` | Ответы соответствуют `openapi.yaml`, нет 500 на фаззинге |
|
||||
| E2E | Web | Playwright (Chromium + Firefox) | Сценарии ниже, на `deploy/compose.test.yaml` |
|
||||
| E2E | Mobile | Maestro (эмулятор) | Вход, волна, фоновое воспроизведение, скачивание, офлайн — ночной прогон, не на каждый PR |
|
||||
| Контракт | — | `tests/contract/schemathesis.sh` | Ответы соответствуют `openapi.yaml`; нужен запущенный `api` |
|
||||
| E2E | Web | Playwright (`apps/web/e2e`, Chromium, `pnpm --filter @pipu/web test:e2e`) | Сценарии 1, 4 и 9 на фейковом API |
|
||||
| E2E | Mobile | Maestro (`apps/mobile/.maestro`) | Вход, коллекция, проверка обновления. Ночной прогон (`PIPU_E2E_SERVER`, `PIPU_E2E_USER`, `PIPU_E2E_PASSWORD`), не на каждый PR |
|
||||
| Качество рекомендаций | Python | офлайн-оценка | NDCG / Recall на синтетическом датасете с известными «вкусами»; регрессия качества ломает тест |
|
||||
|
||||
## Тестовая музыка
|
||||
@@ -27,6 +27,9 @@
|
||||
|
||||
## E2E-сценарии (обязательные)
|
||||
|
||||
Сценарии 1, 4 и 9 гоняет Playwright. Сценарии входа, ролей, поиска, плеера, волны и языка — `apps/web/src/App.test.tsx`.
|
||||
Сценарии источника и скана — `TestUserJourney` в `services/api` (нужен `TEST_DATABASE_URL`).
|
||||
|
||||
1. Первый запуск: вход bootstrap-админа → принудительная смена логина / пароля → главная.
|
||||
2. Админ создаёт модератора; модератор создаёт пользователя; модератор **не может** создать модератора (UI и API).
|
||||
3. Админ добавляет `local`-источник, задаёт два шаблона, предпросмотр, скан → треки появились в поиске.
|
||||
|
||||
+12
-10
@@ -197,17 +197,19 @@
|
||||
- Настройки: `GET` / `PATCH /admin/settings/uploads` (ключ `uploads.settings` в `app_settings`).
|
||||
- Веб (`apps/web/src/features/uploads`, `pages/uploads`, `pages/admin/UploadQueuePage.tsx`):
|
||||
- «Предложить треки» (`/uploads/new`): drag-and-drop файлов и папок (FileSystemEntry API, относительные пути),
|
||||
выбор файлов / папки; SHA-256 потоком в Web Worker (`hash-wasm`); предпроверка; `POST /uploads`; `tus-js-client`
|
||||
с готовым `uploadUrl`, куски по 8 МБ, 4 файла параллельно, 401 → обновление токена и повтор. Загрузчик — синглтон
|
||||
модуля: переходы по приложению не прерывают загрузку; оборвавшиеся файлы продолжаются кнопкой
|
||||
«Продолжить загрузку» (место уже зарезервировано).
|
||||
- «Мои предложения» (`/uploads/mine`): статусы, причины отказа, отзыв до решения, квота.
|
||||
выбор файлов / папки; SHA-256 потоком в Web Worker (`hash-wasm`); теги читает `music-metadata` и отдаёт их
|
||||
в предпроверку; `POST /uploads`; `tus-js-client` с готовым `uploadUrl`, куски по 8 МБ, 4 файла параллельно,
|
||||
401 → обновление токена и повтор. Загрузчик — синглтон модуля: переходы по приложению не прерывают загрузку.
|
||||
Незавершённые tus-ссылки пишутся в `localStorage`; после перезагрузки пользователь выбирает те же файлы,
|
||||
клиент сверяет SHA-256 и продолжает с принятого смещения.
|
||||
- «Мои предложения» (`/uploads/mine`): статусы, причины отказа, отзыв до решения, квота. Бейдж — число решений
|
||||
новее `upload_visits`; открытие страницы вызывает `POST /uploads/seen`.
|
||||
- «Очередь на добавление» (`/admin/uploads`, admin): вкладки по статусам, фильтр возможных дублей, группировка
|
||||
по пачкам, правка метаданных и хранилища, массовое одобрение / отклонение с причиной, повтор импорта;
|
||||
настройки очереди — внизу страницы.
|
||||
- Пока не сделано: `allow_quality_upgrade`, правка «на всю пачку» (`PATCH /admin/uploads/batches/{id}`),
|
||||
прослушивание из staging, отдельная обложка пачки, ночная сверка счётчика квоты, чтение тегов на клиенте
|
||||
(`music-metadata`) для предпроверки метаданных, продолжение загрузки после перезагрузки страницы.
|
||||
по пачкам, правка метаданных и хранилища, правка исполнителя / альбома / года / обложки на всю пачку,
|
||||
прослушивание staging по подписанной ссылке, флаг улучшения качества, массовое одобрение / отклонение с причиной,
|
||||
повтор импорта. Настройки очереди — лимиты, хранилище и `allow_quality_upgrade`.
|
||||
- Сверка квоты: `used_bytes` обновляется в транзакции смены статуса и дополнительно пересчитывается по
|
||||
`SUM(size_bytes)` занимающих статусов при старте `api` и в цикле очистки (раз в 10 минут).
|
||||
|
||||
## 11. Очистка
|
||||
- `uploading` старше 24 ч → `expired`, файлы удаляются.
|
||||
|
||||
Reference in new issue
Block a user