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