DaydreamMods

DaydreamMods

Архитектура

Из каких процессов состоит прод и кто за что отвечает.

Репозиторий — один Bun workspace. Два долгоживущих процесса и общие пакеты.

Сайт

apps/web — Next.js 16 (App Router), React 19, TypeScript, Tailwind CSS 3.

Слушает порт 3000. Отдаёт публичные страницы, кабинет, админку и tRPC по адресу /api/trpc. Загрузка картинок и архивов сборки идёт на /api/admin/upload.

Сессия на клиенте лежит в localStorage под ключом ddm_session. Каждый запрос tRPC, если токен есть, шлёт заголовок Authorization: Bearer ….

API

apps/server — Express на Bun, порт 4000.

Две публичные задачи, которые нельзя оставить только во фронтенде:

  • POST /payments/robokassa/result — серверное подтверждение оплаты;
  • GET /downloads/:token — выдача файла по одноразовому токену.

Остальная бизнес-логика (регистрация, кабинет, админка, создание счёта) живёт в tRPC и вызывается сайтом.

Пакеты

  • packages/database — Drizzle-схема, миграции, сид. Пароли хеширует Argon2.
  • packages/trpc — процедуры API и запасной каталог контента.
  • packages/payments — сборка ссылки Robokassa и проверка подписи ResultURL.
  • packages/mail — шаблоны писем и отправка SMTP.
  • packages/env — разбор переменных для процесса API.

Как запрос доходит до кода

Nginx принимает 80/443.

  • /, страницы, /api/trpc, /api/admin/upload — процесс на порту 3000.
  • /downloads/ и /payments/ — процесс на порту 4000.

Загрузка архива вынесена в отдельный location: тело до 1 ГБ, таймауты до часа, буферизация прокси выключена. Иначе большой ZIP обрывается примерно на минуте.

Где лежат файлы

  • Код и .env: /opt/daydreammods.
  • ZIP сборок: /opt/daydreammods/storage/packs (переменная PACK_STORAGE_DIR). Это не public.
  • Картинки из админки (баннеры, favicon): apps/web/public/uploads. У них есть URL вида /uploads/…, потому что это оформление, а не платный архив.
  • База: PostgreSQL на том же сервере, строка в DATABASE_URL.

Роли процессов systemd

  • daydreammods-web — next start из apps/web.
  • daydreammods-api — bun src/main.ts из apps/server.

Оба должны читать один и тот же .env. Если мигрировать базу без этого файла, скрипт уйдёт в локальный PGlite и упадёт.