2026-10-05 20:38:28 +02:00
2026-10-05 20:38:28 +02:00
2026-10-05 20:38:28 +02:00
2026-10-05 20:38:28 +02:00
2026-10-05 20:38:28 +02:00
2026-10-05 20:38:28 +02:00
2026-10-05 20:38:28 +02:00
2026-10-05 20:38:28 +02:00
2026-10-05 20:38:28 +02:00
2026-10-05 20:38:28 +02:00
2026-10-05 20:38:28 +02:00
2026-10-05 20:38:28 +02:00

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)

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/порты точно подхватились, передавайте файл явно, как выше.

Первый админ: admin@adc.retivo / admin123 (задаётся в .env.dev, смените после входа).

Быстрый старт (Docker, prod)

cp .env.example .env
# обязательно смените SESSION_SECRET, пароли и ADMIN_PASSWORD
docker compose up --build -d

Приложение (API + собранный SPA) будет доступно на http://localhost:3000.

Локальная разработка без Docker

Нужны локальные PostgreSQL, Redis и S3-совместимое хранилище (MinIO).

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)

Полезные команды

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 так:

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 и пароль первого админа перед публикацией.
Description
No description provided
Readme 200 KiB
Languages
TypeScript 98.3%
CSS 0.7%
Dockerfile 0.5%
JavaScript 0.3%
HTML 0.2%