Gamified Ukrainian NMT prep: real tests on zno.osvita.ua, live XP, streaks, badges, quests, duels, seasons, leaderboards — with an anti-AI fair-play system.
Production: https://nmt-arena.fun (web) · https://api.nmt-arena.fun (self-hosted Supabase gateway)
apps/
web/ Next.js 15 (App Router, standalone output) — site, API routes, admin, Telegram Mini App admin
extension/ WXT browser extension (Chrome/Edge, Firefox, Safari; MV3) for zno.osvita.ua
packages/
shared/ constants, zod schemas, XP/coins/anti-cheat rules, TERMS_VERSION
supabase/ typed Supabase clients + DB types
supabase/
migrations/ SQL schema, RLS, functions (applied once each by db-init)
seed.sql
docker/
gateway/ nginx gateway: nginx.conf (local), nginx.prod.conf (prod: CORS allowlist, rate limits)
db-init/ one-shot migration runner + grants
postgres/init/ Supabase roles for a fresh cluster
deploy/ production scripts + host nginx configs + runbook (deploy/README.md)
docker-compose.yml local full stack
docker-compose.dev.yml local overrides (Postgres on 127.0.0.1:54322)
docker-compose.prod.yml standalone production stack (nekuserver)Product features (web): profiles, global/subject leaderboards, groups & classes (teacher fair-play
report), friends, daily challenges & quests, badges, гривеньки (virtual currency) & cosmetics
shop, LiqPay paid backpacks (49/99/199/399 ₴) and cosmetics, promo codes, rewarded ads,
seasons (free/premium track), duels, notification center + Resend email reminders, Ko-fi
supporter perks, admin (/admin) and Telegram Mini App admin (/admin/telegram), Turnstile on
auth, GA4/Google Ads/PostHog behind a consent banner, legal pages (/terms, /privacy-policy, /faq,
/fair-play, /about, /support).
Requirements: Node 20, pnpm 9.15.4 (corepack enable), Docker.
pnpm install --frozen-lockfile
# Full local stack (Postgres + GoTrue + PostgREST + gateway + Studio + web in Docker):
docker compose --env-file .env.docker up --build
# web http://localhost:3000 · api http://localhost:8000 · studio http://localhost:54323
# Expose Postgres for type generation / psql:
docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.docker up -d
# Or run only the backend in Docker and the web app with hot reload:
docker compose --env-file .env.docker up -d db auth rest gateway db-init
cp .env.example apps/web/.env.local # fill ANON/SERVICE keys from .env.docker
pnpm --filter @nmt-arena/web dev
# Extension
pnpm --filter @nmt-arena/extension dev # Chrome
pnpm --filter @nmt-arena/extension dev:firefox.env.docker (gitignored) holds local demo secrets. The local gateway only allows CORS from
http://localhost:*, http://127.0.0.1:* and extension origins. Captcha is off locally unless
CAPTCHA_ENABLED=true and both Turnstile keys are set (Cloudflare test keys work).
Every variable is documented in .env.example (local) and .env.production.example (prod).
pnpm typecheck && pnpm lint && pnpm test
pnpm build:web
pnpm --filter @nmt-arena/extension build # chrome + firefox + safari
pnpm --filter @nmt-arena/extension lint:firefox # web-ext lint
docker compose -f docker-compose.prod.yml --env-file .env.production.example config -q
docker build -f apps/web/Dockerfile .CI (.github/workflows/ci.yml): frozen-lockfile install, typecheck, lint, test, web build,
all extension targets, web-ext lint, compose validation, nginx -t (gateway + host configs
with stub certs), shellcheck, Docker image build.
Self-hosted on nekuserver behind Cloudflare + host nginx. Full runbook: deploy/README.md; release checklist: RELEASE.md.
deploy/generate-secrets.sh # once: deploy/.env.production (or --update to add new keys)
deploy/import-beta-env.sh # once: OAuth/GA/Telegram/Turnstile from the beta env
deploy/deploy.sh # rsync -> build on server -> db-init migrations -> health checks
deploy/install-cron.sh # host crontab (daily 00:05 Kyiv, reminders 18:00 Kyiv)
deploy/rollback.sh | logs.sh | backup-now.sh | restore.sh | studio-tunnel.shSee LICENSE.