93e74951a7
Introduces BalanceTransaction model to track per-user balance history (prepayments, refunds, partner credits). Admin can add/delete transactions; current balance is computed as the running sum. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
309 lines
14 KiB
Markdown
309 lines
14 KiB
Markdown
# AGENTS.md — LMS Second Brain
|
||
|
||
Собственная LMS-платформа для образовательных курсов по PKM и Obsidian.
|
||
Заменяет emdesell.ru. Масштаб: ~1000 аккаунтов, ~200 активных, до 10 курсов.
|
||
Production: **https://school.second-brain.ru**
|
||
|
||
> Подробная техническая документация — в `TECHNICAL.md`.
|
||
> Роадмап и текущий статус — в `ROADMAP.md`.
|
||
> Полные правила для Claude Code — в `CLAUDE.md`.
|
||
|
||
---
|
||
|
||
## Стек
|
||
|
||
| Слой | Технология | Версия |
|
||
|------|-----------|--------|
|
||
| Фреймворк | Next.js (App Router) | **16.2.2** |
|
||
| Язык | TypeScript (strict) | 5.x |
|
||
| UI | React | 19 |
|
||
| Стили | Tailwind CSS (CSS-based, **без** tailwind.config.ts) | 4.x |
|
||
| Компоненты | shadcn/ui (Base UI, **не Radix**) | v4 |
|
||
| ORM | Prisma | 7.x |
|
||
| Auth | Better Auth (**не NextAuth**) | 1.6.0 |
|
||
| Редактор | TipTap WYSIWYG | 2.x |
|
||
| Drag-and-drop | @dnd-kit | latest |
|
||
| БД | PostgreSQL | 16 |
|
||
| Email | Resend | latest |
|
||
| Хранилище | Hetzner Object Storage (S3-совместимый) | — |
|
||
| Видео | Kinescope (iframe embed) | — |
|
||
| Валидация | Zod | 3.x |
|
||
|
||
---
|
||
|
||
## Критические отличия от стандартных версий
|
||
|
||
Эти технологии отличаются от того, что содержится в обучающих данных большинства моделей. **Читай документацию перед написанием кода.**
|
||
|
||
### Next.js 16.2.2
|
||
- Используется `proxy.ts` вместо `middleware.ts`
|
||
- Экспортируемая функция называется `proxy`, не `middleware`
|
||
- Перед написанием кода смотри `node_modules/next/dist/docs/`
|
||
|
||
### Tailwind CSS v4
|
||
- **Нет файла `tailwind.config.ts`** — вся кастомизация через CSS
|
||
- Конфиг: `@import "tailwindcss"` и `@theme` в `globals.css`
|
||
|
||
### shadcn/ui v4
|
||
- Базируется на `@base-ui/react`, **не Radix**
|
||
- Нет пропа `asChild` — триггеры обычные элементы
|
||
- Установка: `npx shadcn@latest add <component>`
|
||
|
||
### Prisma 7.x
|
||
- Импорт: `from "@/generated/prisma/client"` (не `from "@/generated/prisma"`)
|
||
- Требует адаптер: `new PrismaPg({ connectionString })`
|
||
- Не генерирует `index.ts`
|
||
|
||
### Better Auth 1.6.0
|
||
- **Не путать с NextAuth** — другая библиотека, другое API
|
||
- В этом проекте используется **bcrypt** (не scrypt по умолчанию)
|
||
- Настройки `password.hash` / `password.verify` в `src/lib/auth.ts`
|
||
- `auth-client.ts` не использует `baseURL` — берёт `window.location.origin`
|
||
- Seed-пользователи вставлены через SQL с `emailVerified = true`
|
||
|
||
---
|
||
|
||
## Команды
|
||
|
||
```bash
|
||
# Разработка
|
||
npm run dev # localhost:3000
|
||
docker compose up -d # Поднять PostgreSQL локально
|
||
|
||
# Проверка качества
|
||
npm run lint # ESLint
|
||
npm run type-check # tsc --noEmit
|
||
|
||
# Сборка
|
||
npm run build
|
||
npm run start
|
||
|
||
# База данных
|
||
npx prisma migrate dev --name <snake_case_name> # Новая миграция
|
||
npx prisma migrate deploy # Применить в production
|
||
npx prisma generate # Пересоздать клиент
|
||
npx prisma db seed # Заполнить тестовыми данными
|
||
npx prisma studio # GUI для БД
|
||
|
||
# Production-деплой (на сервере в /root/digital-household/lms-sb/)
|
||
git pull
|
||
docker compose -f docker-compose.prod.yml up -d --build
|
||
```
|
||
|
||
При старте production-контейнер автоматически запускает `prisma migrate deploy`, затем `node server.js`.
|
||
|
||
---
|
||
|
||
## Структура проекта
|
||
|
||
```
|
||
lms-system/
|
||
├── src/
|
||
│ ├── app/ # Next.js App Router
|
||
│ │ ├── (auth)/ # login, register, verify-email
|
||
│ │ ├── (student)/ # dashboard, courses/[slug], lessons/[lessonId]
|
||
│ │ ├── curator/ # homework review, dashboard
|
||
│ │ ├── admin/ # courses, users, settings, categories
|
||
│ │ └── api/ # REST endpoints + Better Auth handler
|
||
│ ├── components/
|
||
│ │ ├── ui/ # shadcn/ui (автогенерация, не трогать)
|
||
│ │ ├── editor/ # TipTap WYSIWYG
|
||
│ │ ├── player/ # Kinescope Player wrapper
|
||
│ │ ├── course/ # Компоненты курса
|
||
│ │ └── layout/ # Header, Sidebar, Footer
|
||
│ ├── lib/
|
||
│ │ ├── auth.ts # Better Auth config (сервер)
|
||
│ │ ├── auth-client.ts # Better Auth client (браузер)
|
||
│ │ ├── prisma.ts # Prisma singleton
|
||
│ │ ├── s3.ts # Hetzner S3 клиент
|
||
│ │ ├── email.ts # Resend email helpers
|
||
│ │ └── utils.ts # cn() и утилиты
|
||
│ ├── types/ # TypeScript-типы
|
||
│ ├── proxy.ts # Auth middleware (защита маршрутов)
|
||
│ └── middleware.ts # Обёртка над proxy
|
||
├── prisma/
|
||
│ ├── schema.prisma # Схема БД (~314 строк)
|
||
│ ├── seed.ts # Тестовые данные
|
||
│ └── migrations/ # НЕ РЕДАКТИРОВАТЬ ВРУЧНУЮ
|
||
├── docker-compose.yml # Локальная разработка
|
||
├── docker-compose.prod.yml # Production
|
||
├── Dockerfile # Multi-stage build
|
||
├── .env.example # Шаблон переменных (без секретов)
|
||
└── .env.local # Локальные секреты (в .gitignore)
|
||
```
|
||
|
||
---
|
||
|
||
## Роли и маршруты
|
||
|
||
| Роль | Маршруты | Описание |
|
||
|------|---------|----------|
|
||
| `admin` | `/admin/*`, `/curator/*`, всё | Полный доступ |
|
||
| `curator` | `/curator/*`, `/dashboard` | Проверка ДЗ, комментарии |
|
||
| `student` | `/dashboard`, `/courses/*` | Просмотр курсов, прогресс |
|
||
|
||
Защита маршрутов — в `src/proxy.ts` + проверка сессии в layout/page.
|
||
|
||
---
|
||
|
||
## Модель данных (ключевые сущности)
|
||
|
||
```
|
||
User → Session, Account, Verification # Better Auth
|
||
Category → Course → Module → Lesson # Структура контента
|
||
Lesson → LessonFile # Файлы к уроку
|
||
CourseEnrollment (userId + courseId) # Доступ с expiresAt
|
||
AccessLog # Аудит доступов
|
||
LessonProgress (userId + lessonId) # Прогресс ученика
|
||
Lesson → Homework → HomeworkSubmission → HomeworkFeedback # ДЗ
|
||
Lesson → LessonComment # Обсуждения (soft-delete)
|
||
Lesson → Quiz → QuizQuestion → QuizOption # Тесты
|
||
Quiz → QuizAttempt # Результаты тестов
|
||
Settings (key-value) # Настройки платформы
|
||
```
|
||
|
||
---
|
||
|
||
## Дизайн-система «Second Brain Aubade»
|
||
|
||
Типографский, монохромный, газетный стиль.
|
||
|
||
| Токен | Значение |
|
||
|-------|---------|
|
||
| Шрифт | Fira Mono (400/500/700, Latin + Cyrillic) |
|
||
| Фон | `#F5F5F0` (тёплый off-white) |
|
||
| Текст | `#323232` |
|
||
| Поверхность | `#E8E8E0` |
|
||
| Акцент | `#E8F0D8` (зелёный) |
|
||
| Border | `#AAAAAA` |
|
||
| Сайдбар | `#2A2A28` (тёмный) |
|
||
|
||
**Aubade-эффект** (карточки и кнопки):
|
||
- Border: `2px solid #AAAAAA`
|
||
- Shadow: `4px 4px 0 0 #AAAAAA`
|
||
- Hover: `translate(-2px, -2px)` + shadow `6px 6px`
|
||
- Active: `translate(2px, 2px)`, shadow убирается
|
||
- CSS-классы: `.card-aubade`, `.btn-aubade`, `.btn-aubade-accent`, `.tag-aubade`
|
||
|
||
---
|
||
|
||
## Инфраструктура
|
||
|
||
| Компонент | Значение |
|
||
|-----------|---------|
|
||
| Сервер | Hetzner VPS — 8 vCPU / 16 GB RAM / 320 GB NVMe |
|
||
| Reverse proxy | Caddy (auto HTTPS, Let's Encrypt) |
|
||
| Порт | 3010 (внутри контейнера 3000) |
|
||
| БД | PostgreSQL 16 (контейнер `lms-sb-db-1`) |
|
||
| Object Storage | Hetzner S3, endpoint `nbg1.your-objectstorage.com`, бакет `second-brain-lms` |
|
||
| Git | Gitea — `https://git.second-brain.ru/admins/lms-sb` |
|
||
| Email | Resend, домен `mailsend.second-brain.ru` |
|
||
| Бэкапы | PostgreSQL → Backblaze B2 (ежедневно, 03:00, ротация 7 дней) |
|
||
|
||
---
|
||
|
||
## Переменные окружения
|
||
|
||
```env
|
||
DATABASE_URL="postgresql://lms_user:password@localhost:5432/lms_db"
|
||
BETTER_AUTH_SECRET="generate-with-openssl-rand-base64-32"
|
||
BETTER_AUTH_URL="http://localhost:3000"
|
||
RESEND_API_KEY=""
|
||
EMAIL_FROM="noreply@mailsend.second-brain.ru"
|
||
S3_ENDPOINT="https://nbg1.your-objectstorage.com"
|
||
S3_BUCKET="second-brain-lms"
|
||
S3_ACCESS_KEY=""
|
||
S3_SECRET_KEY=""
|
||
S3_REGION="eu-central"
|
||
```
|
||
|
||
Секреты — **только в `.env.local`**. При добавлении новых переменных обновлять `.env.example`.
|
||
|
||
---
|
||
|
||
## Правила написания кода
|
||
|
||
### Языки
|
||
- **UI-строки** (заголовки, кнопки, сообщения): русский
|
||
- **Переменные, функции, файлы, комментарии**: английский
|
||
- **Коммиты**: английский, imperative mood (`Add lesson progress`, `Fix auth redirect`)
|
||
|
||
### Стиль
|
||
- Server Actions для форм и мутаций
|
||
- Не добавлять абстракции «на будущее» — только текущий этап
|
||
- Нет `console.log` в production (только `console.error` для реальных ошибок)
|
||
- Нет захардкоженных секретов, URL, ID
|
||
|
||
### Миграции БД
|
||
- **Никогда** не редактировать `prisma/migrations/` вручную
|
||
- **Всегда** спрашивать перед миграцией, которая меняет или удаляет существующие поля
|
||
- Имена миграций: английский, snake_case (`add_lesson_progress`)
|
||
- Перед `prisma migrate deploy` на production — бэкап БД
|
||
|
||
### Файлы и загрузки
|
||
- Все файлы (ДЗ, PDF, изображения) — через Hetzner Object Storage, **не на диск VPS**
|
||
- Обложки курсов: 16:9, max 5 MB, JPG/PNG/WebP
|
||
- Изображения в уроках: max 10 MB
|
||
- Файлы к уроку: max 100 MB, PDF/ZIP/DOCX
|
||
|
||
### Коммиты
|
||
- Один коммит = одна логически завершённая единица
|
||
- Перед коммитом: `npm run lint && npm run type-check`
|
||
- После завершения каждого этапа ROADMAP — `git push` в Gitea
|
||
|
||
---
|
||
|
||
## Чек-лист перед коммитом
|
||
|
||
- [ ] `npm run lint` — без ошибок
|
||
- [ ] `npm run type-check` — без ошибок
|
||
- [ ] Новые `.env` переменные добавлены в `.env.example`
|
||
- [ ] Миграция БД согласована (если есть)
|
||
- [ ] Нет `console.log`, нет секретов в коде
|
||
|
||
---
|
||
|
||
## Тестовые аккаунты
|
||
|
||
| Email | Пароль | Роль |
|
||
|-------|--------|------|
|
||
| admin@second-brain.ru | Password123! | admin |
|
||
| curator@second-brain.ru | Password123! | curator |
|
||
| student@second-brain.ru | Password123! | student |
|
||
|
||
---
|
||
|
||
## Текущий статус проекта
|
||
|
||
**Завершено (9 из 13 этапов):**
|
||
- Этап 0: Каркас, auth, роли, деплой
|
||
- Этап 1: Курсы → Модули → Уроки (CRUD, drag-and-drop, TipTap, S3)
|
||
- Этап 1.5: Расширенный доступ (сроки, категории, AccessLog)
|
||
- Этап 2: Kinescope-интеграция, рендер уроков для ученика
|
||
- Этап 3: Прогресс (кнопка завершения, прогресс-бар)
|
||
- Этап 5: Домашние задания + обратная связь куратора
|
||
- Этап 6: Обсуждения под уроками
|
||
- Этап 7: Email-уведомления (Resend)
|
||
- Этап 8: Импорт уроков из Markdown (Obsidian)
|
||
|
||
**В работе:**
|
||
- Этап 9: Настройки платформы (Admin Settings)
|
||
|
||
**Впереди:**
|
||
- Этап 11: Импорт/экспорт учеников (CSV, миграция с emdesell)
|
||
- Этап 12: Telegram-бот + аналитика (Yandex.Metrika)
|
||
- Этап 13: Тесты и квизы с автопроверкой
|
||
|
||
**Бэклог:** сертификаты, геймификация, платежи, медиатека, цифровой сад, CI/CD
|
||
|
||
Полный роадмап с деталями и критериями готовности — в `ROADMAP.md`.
|
||
|
||
---
|
||
|
||
## Известные ограничения
|
||
|
||
- Seed-пользователи вставлены через SQL с `emailVerified = true` (обход Better Auth)
|
||
- Загрузка файлов: нет лимита на уровне Next.js (только S3)
|
||
- Drag-and-drop: возможны race conditions при быстрых перетаскиваниях (некритично)
|
||
- `expiresAt` проверяется в UI, но не блокирует доступ на уровне middleware
|