199 lines
12 KiB
Markdown
199 lines
12 KiB
Markdown
# ADC Retivo
|
||
|
||
Самохостинговая альтернатива Spotify: загрузка своей музыки, автоматическое обогащение
|
||
метаданных из открытых источников, модерация, публичные/приватные плейлисты, подписки,
|
||
лайки, каскадные комментарии, общий поиск и постоянный плеер с очередью и текстами песен.
|
||
|
||
Стек:
|
||
|
||
- **Backend:** Node.js + Express + TypeScript, Drizzle ORM (PostgreSQL)
|
||
- **Frontend:** React + Vite + TypeScript + Tailwind (раздаётся Express-ом в проде)
|
||
- **Инфраструктура:** PostgreSQL, Redis (сессии + очередь BullMQ), MinIO (S3)
|
||
- **Метаданные:** MusicBrainz, AcoustID (Chromaprint/fpcalc), Cover Art Archive, LRCLIB, Wikipedia
|
||
|
||
---
|
||
|
||
## Возможности
|
||
|
||
- Вход только по аккаунту (экран логина для гостей). **Регистрация — из админ-панели.**
|
||
- Первый супер-админ создаётся автоматически из переменных окружения.
|
||
- Гибкие разрешения на пользователя: создание плейлистов, загрузка треков, автопринятие,
|
||
модерация, управление пользователями/библиотекой, просмотр всех плейлистов.
|
||
- Загрузка треков: определение дубликатов (SHA-256), сохранение в S3, асинхронный сбор
|
||
метаданных (название, исполнитель, альбом, обложка, дата, текст с таймкодами), модерация.
|
||
- Несколько исполнителей на трек: `A, B`, `A feat. B`, `A & B` разбираются в отдельные кредиты
|
||
(`main`/`featured`) со ссылками на страницы каждого артиста; редактируется в админке.
|
||
- Плейлисты: публичные / по ссылке / приватные; добавление в библиотеку **ссылкой или копией**;
|
||
соавторы; настройка «разрешить редактирование из библиотеки».
|
||
- Подписки на артистов, лайки треков, каскадные комментарии с ответами.
|
||
- Общий поиск: треки, артисты, альбомы, публичные плейлисты, пользователи.
|
||
- Плеер не прерывается при переходах: мини-бар и развёрнутый вид, очередь с
|
||
редактированием и перемешиванием, повтор, текст песни (синхронизированный).
|
||
- Системные медиа-контролы (Media Session): обложка, название, исполнители и переключение
|
||
треков с локскрина/OS-плеера.
|
||
- Публичные страницы артиста/альбома/трека/пользователя/плейлиста + кнопка «Поделиться».
|
||
- Адаптивный минималистичный интерфейс (blur/glass) на русском.
|
||
|
||
---
|
||
|
||
## Быстрый старт (Docker, dev)
|
||
|
||
```bash
|
||
cp .env.dev.example .env.dev
|
||
docker compose --env-file .env.dev -f docker-compose.dev.yml up --build
|
||
```
|
||
|
||
> Почему `--env-file`: Docker Compose использует `.env.dev` двумя разными путями.
|
||
> `env_file` в сервисах кладёт переменные **внутрь контейнеров**, но подстановка
|
||
> `${VAR}` в самом compose-файле читается только из `.env` (или из файла,
|
||
> указанного через `--env-file`). Чтобы `MINIO_IMAGE`/`MINIO_COMMAND`/порты точно
|
||
> подхватились, передавайте файл явно, как выше.
|
||
|
||
- Frontend (Vite, hot reload): http://localhost:5173
|
||
- Backend API: http://localhost:3001/api
|
||
- MinIO Console: http://localhost:9001
|
||
|
||
Первый админ: `admin@adc.retivo` / `admin123` (задаётся в `.env.dev`, смените после входа).
|
||
|
||
## Быстрый старт (Docker, prod)
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
# обязательно смените SESSION_SECRET, пароли и ADMIN_PASSWORD
|
||
docker compose up --build -d
|
||
```
|
||
|
||
Приложение (API + собранный SPA) будет доступно на http://localhost:3000.
|
||
|
||
## Локальная разработка без Docker
|
||
|
||
Нужны локальные PostgreSQL, Redis и S3-совместимое хранилище (MinIO).
|
||
|
||
```bash
|
||
npm install
|
||
cp .env.example .env # укажите localhost-хосты и порты
|
||
npm run db:migrate -w @adc-retivo/server
|
||
npm run db:seed -w @adc-retivo/server
|
||
npm run dev # server (3001) + web (5173)
|
||
```
|
||
|
||
> Перед сборкой прод-образов нужно сгенерировать миграции: `npm run db:generate -w @adc-retivo/server`.
|
||
|
||
---
|
||
|
||
## Переменные окружения
|
||
|
||
См. `.env.example` (prod) и `.env.dev.example` (dev). Ключевые:
|
||
|
||
| Переменная | Назначение |
|
||
| --- | --- |
|
||
| `DATABASE_URL` | строка подключения к PostgreSQL |
|
||
| `REDIS_URL` | подключение к Redis (сессии, очередь) |
|
||
| `MINIO_*` | доступ к S3/MinIO (endpoint, ключи, bucket) |
|
||
| `MINIO_IMAGE` | docker-образ объектного хранилища (upstream `minio/minio` удалён из Docker Hub — укажите форк) |
|
||
| `MINIO_COMMAND` | команда контейнера хранилища. Пусто = использовать entrypoint образа (форк). Для stock: `server /data --console-address ":9001"` |
|
||
| `SESSION_SECRET` | секрет для cookie-сессий |
|
||
| `MB_USER_AGENT` | описательный User-Agent для MusicBrainz (с контактом) |
|
||
| `ACOUSTID_API_KEY` | ключ AcoustID (необязательно; без него fingerprint пропускается) |
|
||
| `FPCALC_PATH` | путь к `fpcalc` из Chromaprint (по умолчанию `fpcalc`) |
|
||
| `ADMIN_*` | данные первого супер-админа |
|
||
|
||
---
|
||
|
||
## Как работает конвейер загрузки
|
||
|
||
1. `POST /api/uploads` (multipart) → SHA-256, поиск дубликата по хэшу.
|
||
2. Файл кладётся в S3 (`tracks/...`), создаётся запись трека (`pending` либо `approved`, если
|
||
у автора есть `canAutoApprove`), задача ставится в очередь BullMQ.
|
||
3. Воркер:
|
||
- читает теги (`music-metadata`) и встроенную обложку;
|
||
- считает аудио-отпечаток (`fpcalc`) и спрашивает AcoustID;
|
||
- тянет канонические данные из MusicBrainz, обложку из Cover Art Archive;
|
||
- берёт биографию/фото артиста из Википедии;
|
||
- находит текст и синхронизацию из LRCLIB (LRC → таймкоды);
|
||
- создаёт/обновляет артиста и альбом, сохраняет метаданные в трек.
|
||
4. Если автопринятие выключено — трек ждёт модерации в админ-панели.
|
||
|
||
Сетевые источники необязательны: при ошибке/404 соответствующее поле просто остаётся пустым.
|
||
|
||
---
|
||
|
||
## Основные разделы API
|
||
|
||
- `POST /api/auth/login|logout`, `GET /api/auth/session|me`
|
||
- `GET/PATCH /api/me`, `POST /api/me/password`, `POST /api/me/avatar`
|
||
- `GET /api/tracks`, `/api/tracks/random`, `/api/tracks/:id`, `/api/tracks/:id/stream` (Range),
|
||
`/api/tracks/:id/lyrics`, `/api/tracks/:id/comments`, `/api/tracks/:id/like`
|
||
- `POST /api/uploads`, `GET /api/uploads/mine`, `POST /api/uploads/:id/retry`
|
||
- `GET /api/artists/:id`, `/api/albums/:id`, `/api/playlists/:id`, `/api/library`, `/api/search`
|
||
- `POST/PATCH/DELETE /api/playlists`, `/api/playlists/:id/tracks`, `/order`, `/library`, `/collaborators`
|
||
- `GET /api/admin/stats|users|moderation|tracks|playlists`
|
||
- `POST /api/admin/users`, `PATCH/DELETE /api/admin/users/:id`
|
||
- `POST /api/admin/tracks/:id/approve|reject`
|
||
|
||
---
|
||
|
||
## Структура проекта
|
||
|
||
```
|
||
adc_retivo/
|
||
├─ apps/
|
||
│ ├─ server/ # Express API + воркер метаданных + раздача SPA
|
||
│ │ ├─ src/
|
||
│ │ │ ├─ db/ # схема Drizzle, миграции, seed
|
||
│ │ │ ├─ lib/ # env, redis, session, storage, queue, metadata/*, presenters
|
||
│ │ │ ├─ middleware/ # auth/permissions
|
||
│ │ │ ├─ routes/ # REST-роуты
|
||
│ │ │ ├─ services/ # ingest, доступ к плейлистам
|
||
│ │ │ └─ workers/ # BullMQ-воркер обогащения
|
||
│ │ ├─ drizzle/ # сгенерированные SQL-миграции
|
||
│ │ └─ Dockerfile(.dev)
|
||
│ └─ web/ # React SPA
|
||
│ └─ src/{components,pages,store,lib}
|
||
├─ docker-compose.yml # prod (app раздаёт SPA)
|
||
└─ docker-compose.dev.yml # dev (Vite + watcher)
|
||
```
|
||
|
||
## Полезные команды
|
||
|
||
```bash
|
||
npm run typecheck # проверка типов обоих воркспейсов
|
||
npm run build # собрать web + server
|
||
npm run db:generate -w @adc-retivo/server # сгенерировать миграции из схемы
|
||
npm run db:migrate -w @adc-retivo/server # применить миграции
|
||
npm run db:seed -w @adc-retivo/server # создать первого админа (если база пуста)
|
||
```
|
||
|
||
## Частые проблемы
|
||
|
||
**`exec: "server": executable file not found in $PATH` (MinIO).**
|
||
Upstream-образ `minio/minio` удалён из Docker Hub, поэтому вы используете форк. У многих форков
|
||
свой entrypoint (`start.sh`), который сам запускает сервер, и им НЕ нужно передавать
|
||
`server /data ...`. Решение: задайте `MINIO_IMAGE=<ваш-форк>` и `MINIO_COMMAND=` (пусто) —
|
||
тогда compose отдаёт `command: []` и образ запускается своим entrypoint. Для настоящего
|
||
stock-образа, наоборот, укажите `MINIO_COMMAND=server /data --console-address ":9001"`.
|
||
|
||
**`.env.dev` «не подхватывается».**
|
||
Так и есть: `env_file` в сервисах кладёт переменные в контейнеры, но подстановка `${VAR}`
|
||
в compose-файле читается из `.env` (или из файла, переданного через `--env-file`).
|
||
Запускайте dev так:
|
||
|
||
```bash
|
||
docker compose --env-file .env.dev -f docker-compose.dev.yml up --build
|
||
```
|
||
|
||
А в prod-режиме можно просто скопировать `.env.example` в `.env` — он читается автоматически.
|
||
|
||
**Объектное хранилище недоступно / бакет не создаётся.**
|
||
Bucket создаётся самим приложением при старте (`ensureBucket`), отдельный `minio-init` не нужен.
|
||
Проверьте, что `MINIO_ENDPOINT/PORT/ключи` совпадают с сервисом хранилища.
|
||
|
||
---
|
||
|
||
## Заметки по безопасности
|
||
|
||
- Сессии хранятся в Redis, cookie `httpOnly` + `sameSite=lax` (+ `secure` в проде).
|
||
- Пароли — bcrypt.
|
||
- Стриминг аудио проксируется через API с проверкой доступа и поддержкой HTTP Range.
|
||
- Обязательно смените `SESSION_SECRET` и пароль первого админа перед публикацией.
|