# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview **LiveServer-M1** — образовательная видеоконференц-платформа на базе LiveKit. Ключевые фичи: - AI-ассистент: авто-подключение к комнатам, real-time транскрипция (Deepgram), суммаризация лекций (OpenAI GPT) - Защита от Zoom-bombing: lobby/waiting room, PIN-коды, kick/ban по session fingerprint, panic button - Пост-лекционная панель: чат, файлы, транскрипт, AI-саммари, экспорт в PDF/Google Drive Полная спецификация: **`PROMPT.md`** ## Tech Stack | Слой | Технологии | |------|-----------| | Frontend | Next.js 16 (App Router), React 19, TypeScript, Tailwind CSS v4 | | Video | LiveKit Cloud → self-hosted, `@livekit/components-react` | | Backend | Next.js Route Handlers, `livekit-server-sdk` | | Auth | `better-auth` + Prisma adapter | | AI Agent | Python, `livekit-agents`, `livekit-plugins-deepgram` (STT), `livekit-plugins-openai` (LLM) | | DB | PostgreSQL + Prisma 7 ORM + PgBouncer (connection pooling) | | Cache/PubSub | Redis 7 (rate limiting, lobby pub/sub, chat real-time, caching) | | Storage | MinIO (S3-compatible) | | Proxy | Traefik v3 + Let's Encrypt (prod only) | ## Architecture ``` ├── src/ │ ├── app/ │ │ ├── api/ # 15 API route handlers │ │ │ ├── auth/[...all]/ # better-auth catch-all │ │ │ ├── rooms/ # CRUD, by-code lookup │ │ │ ├── rooms/[roomId]/ # join, lobby, lobby/stream (SSE), chat, files, moderate, hand-raise, start, end │ │ │ └── livekit/ # token generation, webhook receiver │ │ ├── (dashboard)/ # Host/Admin pages │ │ ├── join/[code]/ # Guest entry │ │ ├── room/[code]/ # Video room (LiveKit) │ │ ├── login/ & register/ # Auth pages │ │ └── page.tsx # Landing │ ├── components/ │ │ ├── room/ # ChatPanel, ModerationPanel (with self-ban protection) │ │ └── lobby/ # WaitingRoom, LobbyManager (scrollable) │ ├── lib/ # prisma, auth, auth-helpers, livekit, redis, rate-limit, lobby-pubsub, chat-pubsub │ ├── middleware.ts # Dev protection (DEV_ACCESS_KEY, ALLOWED_IPS) │ └── types/ ├── ai-agent/ # Python LiveKit Agent ├── prisma/schema.prisma # 9 models, 3 enums ├── docker-compose.yml # Base: postgres, minio, redis, pgbouncer, ai-agent, app ├── docker-compose.override.yml # Local dev: direct ports, no SSL ├── docker-compose.prod.yml # Traefik + Let's Encrypt └── Dockerfile # Multi-stage Next.js standalone build ``` ### Key Data Flows 1. **Guest join (with lobby):** Guest → POST `/api/rooms/[id]/join` → LobbyEntry(PENDING) → SSE via Redis pub/sub → Host approves → LiveKit token → connect 2. **Real-time chat:** POST message → DB + Redis PUBLISH → SSE `/api/rooms/[id]/chat/stream` → instant delivery to all participants 3. **Real-time transcription:** Audio tracks → AI Agent (Deepgram STT) → DataChannel → Live captions 4. **Post-lecture (`room_finished` webhook):** Transcript → OpenAI GPT → LectureArtifact → `/lectures/[id]` ### User Roles - **ADMIN** — global panel, all rooms monitoring - **HOST** — room creation, moderation, security - **GUEST** — join by link, no registration required ### Security Layers - `sessionFingerprint` (required) for bans — IP is secondary only - PIN hashed with bcrypt, rate-limited (5 attempts/min per IP) - Chat/files auth: verified via ParticipantHistory - SSE lobby: verified against existing LobbyEntry - Token generation: only room owner can get LiveKit token - `DEV_ACCESS_KEY` middleware: protects local dev from network access - LiveKit webhook: signature verification via `WebhookReceiver` ## Performance Architecture ``` Clients → Traefik (LB) → Next.js (x2 replicas) → PgBouncer (pool 25, max 500) → PostgreSQL → Redis (pub/sub, cache, rate limit) → LiveKit (video/audio SFU) ``` - **Redis pub/sub** replaces DB polling for lobby SSE and chat — instant delivery (<10ms) - **PgBouncer** multiplexes 500 client connections into 25 real PostgreSQL connections - **Chat SSE** via Redis pub/sub + optimistic UI on client — messages appear before server confirms - **Rate limiting** via Redis INCR+EXPIRE — works across multiple app replicas - **Chat cache** in Redis (10s TTL) — reduces DB reads during active rooms - **Prisma select** — only fetch needed columns, not full rows ## Commands ```bash # Dev npm run dev # Next.js dev server (localhost:3000) npm run dev:https # Dev server with self-signed HTTPS (media devices on LAN) docker compose up -d postgres minio redis pgbouncer # DB + Storage + Redis + PgBouncer npm run lint # TypeScript type-check (tsc --noEmit) # Database npx prisma migrate dev # Create + apply migration npx prisma db push # Quick schema sync (no migration) npx prisma studio # DB GUI npx prisma generate # Regenerate Prisma Client # Build npm run build -- --webpack # Production build (Webpack, not Turbopack — WASM limitation on Windows) # Docker (full stack) docker compose up -d --build # Local docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build # Prod (with Traefik) # AI Agent cd ai-agent && python main.py start # Setup script (universal installer/doctor) ./setup.sh # Interactive menu ./setup.sh dev # Kill old + pull + deps + containers + wait healthy + prisma + run ./setup.sh install # Full first-time setup (auto-generates passwords) ./setup.sh update # git pull + npm install + prisma + rebuild ./setup.sh doctor # Diagnose & auto-fix common issues ./setup.sh status # Health check all services ./setup.sh restart # Restart all containers ./setup.sh logs [service] # Tail service logs (default: app) ./setup.sh admin # Promote user to ADMIN role ./setup.sh reset # Full teardown (with confirmation) ``` ## Environment Variables See `.env.example` for all variables. Key ones: ```env DATABASE_URL=postgresql://postgres:postgres@localhost:5432/liveserver LIVEKIT_URL=wss://... LIVEKIT_API_KEY=... LIVEKIT_API_SECRET=... BETTER_AUTH_SECRET=... # Auth — trusted origins for non-localhost access (comma-separated) BETTER_AUTH_TRUSTED_ORIGINS=http://192.168.1.78:3000,http://localhost:3000 # LAN IP for auto trustedOrigins in dev (auto-detected by ./setup.sh dev) # LAN_HOST=192.168.1.78 # Local dev protection (when DOMAIN is not set) DEV_ACCESS_KEY=mySecretKey123 # ALLOWED_IPS=192.168.1.10,192.168.1.11 # Production # DOMAIN=live.example.com # ACME_EMAIL=admin@example.com ``` ## Deployment Modes | Mode | Command | Description | |------|---------|-------------| | **Local dev** | `docker compose up -d` | Direct ports (3000, 5432, 9000). `docker-compose.override.yml` auto-applied | | **Production** | `docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d` | Traefik reverse proxy, auto SSL via Let's Encrypt | ## Prisma 7 Notes - `prisma.config.ts` in project root — `migrate.url()` provides DATABASE_URL for CLI commands (`prisma db push`, `prisma migrate`) - **No `url` in schema datasource** — Prisma 7 forbids it, only `provider` allowed - `PrismaClient` uses `@prisma/adapter-pg` (driver adapter) — `new PrismaClient({ adapter })` with `PrismaPg({ connectionString })` - `new PrismaClient()` without adapter **will crash** in Prisma 7 (no datasourceUrl/datasources support) - Use `-- --webpack` flag for `next build` on Windows (Turbopack WASM issue) ## Auth Notes - `better-auth` handles registration/login via `/api/auth/[...all]` catch-all route - **Client:** `auth-client.ts` uses `createAuthClient()` without `baseURL` — auto-detects current origin (works from any IP/domain) - **Server:** `auth.ts` uses `BETTER_AUTH_URL` for `baseURL` and `BETTER_AUTH_TRUSTED_ORIGINS` (comma-separated) for CSRF origin validation. Without `BETTER_AUTH_TRUSTED_ORIGINS`, auto-detects all machine IPs via `os.networkInterfaces()` + localhost on ports 3000–3010, both `http://` and `https://` protocols - First admin: first registered user automatically becomes ADMIN (via `databaseHooks.user.create.before` in `auth.ts`) - **No `"type"` field in `package.json`** — removed to fix Turbopack ESM/CJS conflict in dev mode. Next.js handles ESM in `.ts/.tsx` automatically ## Agent Orchestration | Task | Agent | |------|-------| | API routes, backend logic | `backend-architect` | | React components, LiveKit UI | `frontend-developer` | | DB schema, indexes, queries | `database-optimizer` | | Python AI Agent, Deepgram/OpenAI | `ai-engineer` | | System architecture | `software-architect` | | UI design, components | `ui-designer` | | UX flows, accessibility | `ux-architect` | | Code review before commit | `code-reviewer` | **Rule:** for tasks spanning 2+ layers — use `agents-orchestrator`. ## Post-Change Checklist (ОБЯЗАТЕЛЬНО) После **каждого** изменения кода или конфигурации Claude **обязан** выполнить: 1. **Обновить `CLAUDE.md`** — если изменились архитектура, endpoints, модели, команды, стек или conventions 2. **Проверить `setup.sh`** — все новые сервисы/зависимости должны быть в скрипте (readiness checks, .env переменные, docker compose команды) 3. **Проверить `README.md`** — инструкции, API таблицы, структура проекта должны отражать текущее состояние 4. **Проверить `PROMPT.md`** — спецификация должна соответствовать реализации 5. **Проверить `.env.example`** — все новые переменные окружения должны быть задокументированы Не пропускать этот чеклист. Если изменение не затрагивает файл — просто подтвердить что файл актуален. ## Known Issues & Fixes | Issue | Cause | Fix | |-------|-------|-----| | White screen in dev (ESM/CJS conflict) | `"type": "module"` or `"type": "commonjs"` in package.json | Remove the `"type"` field entirely — Next.js doesn't need it | | Auth silently fails from LAN | `auth-client.ts` had hardcoded `baseURL` → requests went to localhost from browser | Removed `baseURL`, added `trustedOrigins` config | | `setup.sh doctor` crashes on .env check | Comments in `.env.example` parsed as variable names by `set -euo pipefail` | Fixed parsing logic | | `setup.sh update` runs git pull after stash declined | Missing `else` branch after stash prompt | Fixed control flow | | Auth form resets on non-localhost (no error shown) | `trustedOrigins` fallback only had `localhost:3000`, CSRF rejected other origins silently | `auth.ts` auto-allows ports 3000–3010 + `LAN_HOST`; `setup.sh dev` auto-detects LAN IP | | Media devices blocked on LAN (not HTTPS) | Browsers require secure context for getUserMedia | `npm run dev:https` (Next.js `--experimental-https`), `setup.sh dev` auto-uses HTTPS | | Chat messages duplicated | Race: SSE delivers msg before POST response, seenIds doesn't have real id yet | Check if SSE already delivered the message before replacing optimistic | | Ban self crashes room | No server-side check for self-ban/kick | API rejects `targetSessionId === session.user.id`, UI hides buttons for self | ## UI Architecture - **Design system:** CSS custom properties in `globals.css` via Tailwind v4 `@theme` — surface levels (0–3), accent (indigo), status colors, border tokens - **Room layout (Google Meet style):** - Minimal top bar (room name + code) - Video area: `GridLayout` (cameras) or `FocusLayout` (screen share active — carousel + focused view) - Bottom control bar: mic, camera, screen share | chat, lobby, moderation, hand raise | leave/end - **Screen share:** auto-detected via `useTracks(Track.Source.ScreenShare)`, switches to focus layout with `CarouselLayout` for camera feeds - **Sidebar:** 320px right panel for chat, lobby manager, moderation — stacked, each with own scroll - **Components use LiveKit hooks:** `useLocalParticipant`, `useParticipants`, `useRoomContext`, `useTracks` ## Conventions - Communication language: Russian - Guests don't register, but `user_id` reserved for future LMS - `sessionId` (UUID) is the cross-cutting key linking LobbyEntry, ParticipantHistory, ChatMessage, SharedFile for guests - `Room.code` is the user-facing invite code (not the DB id) - Files store `fileKey` (S3 object key), not full URLs - `LectureArtifact` is 1:1 with Room - All cascade deletes: removing Room removes all related data - MinIO stays as S3 storage — GitHub repo archived (Feb 2026) but Docker image still maintained, no viable lightweight alternative