Skip to content

Repository files navigation

Basalt Arena

Платформа соревновательных спринтов по разработке. Участники зачисляются в спринт, отправляют репозиторий и демо; наставник проверяет работу и выставляет балл — лучшие решения попадают в зал славы. Есть профиль со статистикой, рейтинг, призовые и админ-панель.

Прод: basalt-arena.onrender.com

Возможности

  • Спринты — один активный спринт на главной, история и зал славы с пагинацией.
  • Отправки — репозиторий + демо, валидация ссылок, статусы (на проверке / принято / отозвано / удалено).
  • Проверка — наставник одобряет отправку, выставляет балл (0–100), участник получает уведомление.
  • Зал славы — топ решений по баллу и лайкам, ранги и призовые после дедлайна.
  • Профиль — баллы, глобальный ранг, пройденные спринты, заработок, ачивки, in-app уведомления.
  • Realtime — мгновенные обновления зала/лидерборда/статусов через SSE (GET /v2/events), с поллингом как фолбэком.
  • Админка — пользователи, спринты, зачисления, проверка отправок, решения, каталог ачивок, журнал действий.

Стек

Слой Технологии
Frontend React 19, React Router 7, Tailwind 4, Vite 8
Backend NestJS 11, class-validator, Swagger
ORM / БД Prisma 6 · SQLite локально, PostgreSQL на проде (Supabase)
Auth JWT access (15 мин) + refresh (30 дней, в БД), bcrypt
Монорепо npm workspaces (client, server)
Деплой Render (один сервис: API + статика клиента)

Архитектура

В проде фронт раздаётся со статик-CDN, а API — отдельным сервисом NestJS (он же умеет отдавать статику для single-service режима). Весь API под префиксом /api/mock/v1 (вне префикса только /health).

  • HTTP-модули: auth (логин/refresh/logout), v2 (участник), admin (под AdminGuard).
  • Domain-сервисы (CoreModule, @Global): бизнес-логика разбита по зонам — пользователи, спринты, отправки, решения, призовая логика, уведомления, аудит.
  • Guards: AuthGuardreq.basaltUser, OptionalAuthGuard (публичные эндпоинты с опциональным пользователем), AdminGuardreq.basaltAdmin.
  • Realtime через SSE: доменные сервисы публикуют событие «данные изменились», эндпоинт GET /v2/events транслирует его клиентам (без Redis/WebSocket — один сервис).
  • Производные метрики (globalRank, число пройденных спринтов) считаются на чтении, а не денормализуются в БД.
  • Призовые начисляются победителю автоматически после дедлайна (фоновый тик + пересчёт на чтении).
  • Ошибки проходят через GlobalExceptionFilter — клиенту не уходит stack/Prisma-детали.

Структура

client/         — SPA (pages, components, hooks, api-клиент)
server/
  prisma/       — schema.prisma (SQLite) + schema.postgres.prisma (PostgreSQL)
  src/
    modules/    — HTTP-контроллеры: auth, v2, admin
    domain/     — бизнес-логика (домен-сервисы)
    auth/       — сессии и JWT
    common/     — guards, filters, presenters, utils, constants
scripts/        — smoke-тесты API и обслуживающие скрипты
docs/api.md     — краткий контракт эндпоинтов

Быстрый старт

npm install
cp server/.env.example server/.env   # заполнить JWT_SECRET и BASALT_DEV_REGISTER_KEY
npm run dev
URL
http://localhost:5173 фронтенд (прокси /api → :3001)
http://localhost:3001/health healthcheck
http://localhost:3001/api/docs Swagger UI

Первый администратор

В server/.env:

BASALT_BOOTSTRAP_ADMIN_HANDLE=admin
BASALT_BOOTSTRAP_ADMIN_EMAIL=admin@example.com
BASALT_BOOTSTRAP_ADMIN_PASSWORD=ваш-пароль
npm run bootstrap:admin -w server

Вход на /login по email или handle. Саморегистрация (POST /auth/register) включается только при заданном BASALT_DEV_REGISTER_KEY и требует одноимённый заголовок.

Переменные окружения

Шаблон: server/.env.example.

Переменная Описание
DATABASE_URL file:./dev.db локально; PostgreSQL URI на проде
JWT_SECRET Секрет JWT — обязателен в production (иначе сервер не стартует)
BASALT_DEV_REGISTER_KEY Ключ для POST /auth/register (без него регистрация отключена)
BASALT_CORS_ORIGIN Разрешённые origin через запятую (прод)
BASALT_APP_BUILD, BASALT_PRIZE_* Метаданные для GET /v2/meta (опционально)
BASALT_BOOTSTRAP_ADMIN_* Создание первого администратора

Команды

Команда Действие
npm run dev клиент + сервер в режиме разработки
npm run build сборка клиента
npm run build:render сборка для Render (клиент + Prisma postgres + сервер)
npm run start production-сервер
npm test unit-тесты клиента
npm run test:server:unit unit-тесты сервера (Jest + утилиты)
npm run test:server:api интеграционный smoke-тест API

API

Префикс /api/mock/v1 · Swagger /api/docs · контракт docs/api.md.

  • auth — login, register, refresh, logout
  • v2 — meta, профиль, спринты, отправки, лайки
  • admin — управление платформой (роль admin)

Префикс /mock исторический — это полноценный бэкенд на Prisma и JWT, не мок.

Тестирование

npm test                  # клиент (node:test)
npm run test:server:unit  # сервер (Jest)
npm run test:server:api   # интеграционный прогон API (SQLite)

# тот же флоу против реального PostgreSQL (через миграции):
BASALT_API_TEST_DATABASE_URL=postgres://user:pass@localhost:5432/db?schema=public \
  npm run prisma:generate:postgres -w server && npm run test:server:api

CI (GitHub Actions) на каждый push гоняет: клиентские тесты, сборку, серверные unit + интеграцию на SQLite, и тот же интеграционный флоу на настоящем PostgreSQL (поднимает postgres-сервис, применяет миграции). Это ловит расхождения диалектов SQLite↔Postgres до прода.

Миграции БД

Прод (PostgreSQL) использует версионируемые Prisma-миграции (server/prisma/migrations/):

  • prisma:deploy:postgres применяет миграции на деплое. Скрипт самобейзлайнящийся: на свежей БД применяет всё, на существующей БД из db push — помечает 0_init как применённую и катит только новое.
  • Новую миграцию сгенерировать офлайн (без локального Postgres): обновите schema.postgres.prisma, затем npm run prisma:migrate:diff:postgres -w server и сохраните вывод в prisma/migrations/<timestamp>_<name>/migration.sql.

Локальная разработка работает на SQLite через prisma db push (нулевые внешние зависимости).

Деплой (Render + Supabase)

Прод разнесён на два сервиса (см. render.yaml, Render Blueprint):

Сервис Что Хост
basalt-arena-web Статический SPA (CDN, не засыпает) Render Static
basalt-arena-api NestJS API + /health Render Web (free)
БД PostgreSQL Supabase

Сборки: фронт — npm run build:web, бэк — npm run build:api (сборка без БД). Миграции применяются при старте бэка (start:apimigrate deploy), поэтому билд не зависит от доступности БД. Связка по env:

  • web: VITE_API_BASE_URL = URL API (напр. https://basalt-arena-api.onrender.com).
  • api: DATABASE_URL (Supabase), JWT_SECRET (32+), BASALT_DEV_REGISTER_KEY, NODE_ENV=production, BASALT_CORS_ORIGIN = URL фронта.

Развернуть: Render → New → Blueprint → выбрать репозиторий → задать секреты (sync: false). Клиент берёт адрес API из VITE_API_BASE_URL (CORS/SSE работают cross-origin).

Single-service режим (один сервис отдаёт API + статику) тоже поддерживается: build:render + start.

После первого деплоя с пустой БД создать администратора:

npm run bootstrap:admin:remote -w server   # нужен postgres DATABASE_URL в server/.env

Соглашения для разработки

  • Минимальные диффы в стиле существующих сервисов.
  • Не коммитить .env и *.db.
  • Новые эндпоинты — DTO + class-validator (+ Swagger по возможности).
  • Админские действия логируются через журнал аудита.
  • Изменения схемы — в обеих схемах Prisma (SQLite и PostgreSQL) + сгенерировать миграцию (prisma:migrate:diff:postgres).
  • В production задавать сильный JWT_SECRET и конкретный BASALT_CORS_ORIGIN.
  • Форматирование — Prettier: npm run format / npm run format:check.
  • Pre-commit (опционально): npx husky init, затем в .husky/pre-commit указать npx lint-staged — изменённые файлы будут автоформатироваться.
  • Значимые архитектурные решения фиксируются в docs/adr/.

Releases

Packages

Contributors

Languages