Initial commit

This commit is contained in:
2026-10-05 20:38:28 +02:00
commit 60a68b420e
107 changed files with 21716 additions and 0 deletions

198
README.md Normal file
View File

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