Files
retivo/README.md
2026-10-05 20:38:28 +02:00

199 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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` и пароль первого админа перед публикацией.