Files
admins 93e74951a7 Add balance transactions to user admin panel
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>
2026-05-07 09:24:25 +05:00

14 KiB
Raw Permalink Blame History

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

Команды

# Разработка
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 дней)

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

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