ci / api / api (push) Successful in 55s
release / release (версия и заметки) (push) Successful in 5s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m21s
ci / ts / ts (packages + web) (push) Successful in 2m19s
release / android (APK в релиз) (push) Successful in 4m45s
release / desktop (Linux и Windows в релиз) (push) Successful in 20m58s
release / prune (старые файлы и прогоны) (push) Successful in 9s
ci / module boundaries / boundaries (push) Successful in 7s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m35s
ci / db / db (push) Successful in 59s
48 lines
7.0 KiB
Markdown
48 lines
7.0 KiB
Markdown
# Личные плейлисты
|
||
|
||
Плейлисты хранятся в PostgreSQL. Владелец управляет ими в вебе, desktop и React Native-клиенте; по желанию может поделиться ссылкой для прослушивания. В вебе раздел находится в коллекции; на узком экране — в меню «Ещё». В мобильном клиенте — кнопка «Плейлисты» в коллекции.
|
||
|
||
Можно создать плейлист, изменить название и описание, добавить трек из поиска, альбома или коллекции, убрать трек и переместить его выше/ниже. В вебе можно добавить целый альбом. Один трек встречается в плейлисте один раз: повторное добавление не создаёт дубль. Воспроизведение доступно в сохранённом или случайном порядке; недоступные треки остаются видимыми, но пропускаются при запуске всего плейлиста. Удаление плейлиста требует подтверждения и не удаляет музыку из каталога.
|
||
|
||
## API
|
||
|
||
- `GET /api/v1/me/playlists` — сводки личных плейлистов, последние изменённые первыми.
|
||
- `POST /api/v1/me/playlists` — создание: `name`, `description`, `track_ids`.
|
||
- `GET /api/v1/me/playlists/{id}` — метаданные и упорядоченные треки с персональными реакциями и подписанными ссылками.
|
||
- `PUT /api/v1/me/playlists/{id}` — атомарная замена метаданных и порядка треков; дополнительно требуется `revision` из прочитанного плейлиста.
|
||
- `DELETE /api/v1/me/playlists/{id}` — удаление.
|
||
- `POST /api/v1/me/playlists/{id}/share` — создать или вернуть действующий `share_id` (только владелец).
|
||
- `DELETE /api/v1/me/playlists/{id}/share` — отключить доступ по ссылке (только владелец).
|
||
- `GET /api/v1/shared/playlists/{shareId}` — прочитать плейлист по действующей ссылке; требуется вход на этот сервер.
|
||
|
||
Все операции требуют авторизации. Чужой и отсутствующий плейлист возвращают одинаковый `404`. Изменения блокируют строку плейлиста в транзакции; устаревшая версия возвращает `409 playlist.conflict`. Редактор сохраняет исходную версию до закрытия, даже если фоновый запрос получил новую. Ошибка трека откатывает изменение целиком. Ввод ограничен 120 символами названия, 2000 символами описания и 2000 уникальными треками.
|
||
|
||
Миграция `00013_playlists.sql` создаёт таблицы `playlists`, `playlist_tracks` и индексы. Удаление пользователя очищает его плейлисты; удаление трека из каталога очищает его членство. Таблицы не затрагивают музыкальные файлы.
|
||
|
||
Изначально плейлисты личные. Кнопка «Поделиться плейлистом» включает доступ по ссылке
|
||
`/shared/playlist/{shareId}`. В вебе/desktop ссылка копируется, в мобильном браузере и Android открывается
|
||
системное меню «Поделиться». Desktop использует адрес музыкального сервера, а не локального WebView.
|
||
Как и ссылки на треки, ссылка требует входа на тот же сервер. После входа открывается запрошенный плейлист,
|
||
без автоматического запуска музыки. Получатель может слушать в обычном или случайном порядке и переслать
|
||
ту же ссылку, но не может редактировать или удалять исходный плейлист.
|
||
|
||
Ссылка содержит отдельный случайный UUID и не раскрывает токены авторизации или подписанные URL аудио.
|
||
Список `/me/playlists` и обычное чтение по UUID плейлиста остаются доступны только владельцу. Реакции
|
||
на треки и ссылки стрима в общем плейлисте принадлежат текущему слушателю. Данные общих страниц в веб-кэше
|
||
разделяются по пользователям.
|
||
|
||
«Отключить доступ по ссылке» отзывает прежний `share_id`; следующая публикация создаёт новый.
|
||
Повторное нажатие «Поделиться» без отзыва сохраняет ссылку. Ссылка показывает актуальный порядок и метаданные;
|
||
изменение доступа не меняет `revision` и не вызывает конфликт редактора. При удалении плейлиста или блокировке
|
||
владельца ссылка возвращает `404`. Миграция `00016_playlist_sharing.sql` добавляет `share_id` и уникальный индекс.
|
||
Совместное редактирование и загрузка пользовательских обложек не входят в эту реализацию. Карточки имеют градиентную обложку, страница использует обложку первого трека. Контекст воспроизведения — `collection` с UUID и названием плейлиста.
|
||
|
||
## Проверки
|
||
|
||
- `pnpm --filter @pipu/web test src/App.test.tsx -t playlists`
|
||
- `pnpm --filter @pipu/web test:e2e e2e/playlists.spec.ts`
|
||
- `pnpm --filter @pipu/web build`
|
||
- `pnpm --filter @pipu/mobile typecheck`
|
||
- В `services/api`: `go test ./internal/playback ./internal/server`
|
||
- С отдельной тестовой PostgreSQL/pgvector: `TEST_DATABASE_URL=… go test -tags integration ./internal/playback -run TestPlaylistPersistenceAndIsolation -v`. Проверяет сохранение порядка, изоляцию владельцев, откат при несуществующем треке, конфликт конкурентных записей и каскадное удаление членства.
|