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>
343 lines
26 KiB
Markdown
343 lines
26 KiB
Markdown
# ROADMAP — LMS Second Brain
|
||
|
||
**Стек:** Next.js 16.2.2 · React 19 · PostgreSQL 16 · Prisma 7 · Better Auth 1.6 · Tailwind v4 · shadcn/ui · TipTap · Kinescope · Resend · Hetzner Object Storage
|
||
**Принцип:** один этап — одна рабочая фича. Не переходим к следующему, пока текущий не работает end-to-end.
|
||
|
||
---
|
||
|
||
## Этап 0 — Каркас, auth, роли ✅ ЗАВЕРШЁН (07.04.2026)
|
||
|
||
- [x] Next.js 16.2.2 (App Router, TypeScript, Tailwind v4)
|
||
- [x] Docker Compose: PostgreSQL 16 (прод на Hetzner)
|
||
- [x] Prisma 7: схема User, Session, Account + полная LMS-модель
|
||
- [x] Better Auth: вход по email/password, роли student/curator/admin
|
||
- [x] Middleware: защита маршрутов по сессии
|
||
- [x] Дашборды для трёх ролей
|
||
- [x] Страница входа, регистрации, подтверждения email
|
||
- [x] Seed: admin/curator/student (пароль: Password123!)
|
||
- [x] Dockerfile multi-stage + docker-compose.prod.yml
|
||
- [x] Caddy: school.second-brain.ru → порт 3010
|
||
|
||
---
|
||
|
||
## Этап 1 — Курсы → Модули → Уроки (CRUD в админке) ✅ ЗАВЕРШЁН (07.04.2026)
|
||
|
||
- [x] Prisma-схема: Course, Module, Lesson (с порядком, статусом published)
|
||
- [x] Admin: список курсов, создать / редактировать / удалить курс
|
||
- [x] Admin: список модулей внутри курса, drag-and-drop сортировка
|
||
- [x] Admin: список уроков внутри модуля, drag-and-drop сортировка
|
||
- [x] Admin: редактор урока с TipTap (заголовки, списки, цитаты, код, картинки, ссылки)
|
||
- [x] Загрузка картинок в уроке → Hetzner Object Storage
|
||
- [x] Поле для Kinescope ID в уроке
|
||
- [x] Публикация/скрытие курса и урока (черновик / опубликован)
|
||
- [x] Управление доступом: выдать / забрать доступ к курсу для пользователя
|
||
- [x] Дизайн: Fira Mono, #F5F5F0, Aubade-карточки
|
||
- [x] Admin: таблица пользователей (/admin/users)
|
||
|
||
---
|
||
|
||
**Доработки таблицы пользователей (добавить в рамках Этапа 9):**
|
||
- [ ] Фильтры: по роли (Ученик / Куратор / Администратор), по статусу email (подтверждён/нет)
|
||
- [ ] Поиск по имени / email
|
||
- [ ] Пагинация + выбор кол-ва записей на странице (20/50/100)
|
||
- [ ] Ховер-попап на строке: список курсов пользователя со сроками доступа + email + телефон
|
||
- [ ] Быстрые действия в строке: кнопка «Выдать доступ» без перехода на страницу пользователя
|
||
|
||
---
|
||
|
||
## Этап 1.5 — Расширенное управление доступом ✅ ЗАВЕРШЁН (07.04.2026)
|
||
|
||
- [x] Срок доступа: `expiresAt` в `CourseEnrollment`, просроченный подсвечивается красным
|
||
- [x] Категории курсов: таблица `Category`, `/admin/categories`, привязка к курсу
|
||
- [x] Расширенный энролл: `/admin/users/[userId]` — выбор нескольких курсов + срок одной операцией
|
||
- [x] История доступа: `AccessLog` — каждая операция логируется (кто, когда, метод, примечание)
|
||
|
||
---
|
||
|
||
## Этап 2 — Интеграция Kinescope, рендер уроков для ученика ✅ ЗАВЕРШЁН (07.04.2026)
|
||
|
||
- [x] Компонент `KinescopePlayer` — обёртка над `@kinescope/react-kinescope-player`
|
||
- [x] Рендер урока для ученика: видео (если есть Kinescope ID) + текст + файлы
|
||
- [x] Загрузка PDF/файлов к уроку (Object Storage), список для скачивания
|
||
- [x] Страница курса для ученика: список модулей и уроков, статус прохождения
|
||
- [x] Навигация по урокам: предыдущий / следующий
|
||
- [x] Блокировка доступа к курсу без enrollment (layout server component)
|
||
- [x] Страница «Мои курсы» в личном кабинете ученика (dashboard)
|
||
- [x] Кнопки Сохранить / Просмотр в редакторе урока
|
||
- [x] Иконка-статус уроков в боковой панели курса (✓ пройден)
|
||
|
||
---
|
||
|
||
## Этап 3 — Прогресс ✅ ЗАВЕРШЁН (07.04.2026)
|
||
|
||
- [x] Prisma: таблица `LessonProgress` (userId, lessonId, completedAt)
|
||
- [x] Кнопка «Урок завершён» — создаёт/удаляет запись в LessonProgress
|
||
- [x] Прогресс-бар по курсу в боковой панели (% завершённых уроков)
|
||
- [x] Прогресс-бар по курсу на дашборде студента
|
||
- [x] Admin bypass: администратор видит все уроки без отметок о прогрессе
|
||
|
||
---
|
||
|
||
## Этап 5 — Домашние задания и обратная связь куратора ✅ ЗАВЕРШЁН (07.04.2026)
|
||
|
||
- [x] Prisma: Homework, HomeworkSubmission, HomeworkFeedback
|
||
- [x] Admin: добавить / редактировать / удалить блок ДЗ к уроку (HomeworkEditor)
|
||
- [x] Ученик: форма отправки ДЗ (текст + файлы → Object Storage)
|
||
- [x] Ученик: видит статус ДЗ и фидбек куратора в карточке урока
|
||
- [x] Куратор / Admin: список ДЗ на проверку (`/curator/homework`)
|
||
- [x] Куратор / Admin: просмотр работы, текстовый комментарий с обратной связью
|
||
- [x] Admin: AdminShell на `/curator/*` маршрутах (сайдбар не пропадает)
|
||
- [x] Admin: реальная статистика на дашборде (студенты, курсы, ДЗ, прогресс)
|
||
|
||
**Доработки (добавить в рамках Этапа 9):**
|
||
- [ ] Фильтры в списке ДЗ: по имени/email ученика, по уроку, по курсу, по статусу
|
||
- [ ] Поиск по имени/email ученика
|
||
- [ ] Пагинация + выбор кол-ва записей на странице (20/50/100)
|
||
- [ ] Расширенные статусы: «Новое» / «Просмотрено» / «Прокомментировано»
|
||
- [ ] Шаблоны ответов куратора — готовые тексты фидбека, выбираемые из списка
|
||
|
||
---
|
||
|
||
## Этап 6 — Обсуждения под уроками ✅ ЗАВЕРШЁН (07.04.2026)
|
||
|
||
- [x] Prisma: LessonComment (soft-delete через поле `deleted`)
|
||
- [x] Рендер списка комментариев под уроком (server-fetched, client-rendered)
|
||
- [x] Форма отправки комментария (только для enrolled учеников и admin)
|
||
- [x] Модерация: автор, куратор или admin может удалить комментарий
|
||
- [x] Счётчик активных комментариев в заголовке секции
|
||
|
||
**Не реализовано (добавить в Этап 9 или отдельно):**
|
||
- [ ] `/admin/comments` — сводная таблица всех комментариев по всем урокам
|
||
- Колонки: №, Имя ученика, Урок (ссылка), Время, кол-во ответов, превью текста
|
||
- Удалить комментарий прямо из списка
|
||
- Пагинация
|
||
- Ссылка в сайдбаре AdminNav
|
||
|
||
---
|
||
|
||
## Этап 7 — Email-уведомления ✅ ЗАВЕРШЁН (07.04.2026)
|
||
|
||
- [x] Базовый HTML email-шаблон (фирменный стиль Second Brain)
|
||
- [x] Приветственное письмо при регистрации (`databaseHooks.user.create.after`)
|
||
- [x] Письмо ученику об открытии доступа к курсу
|
||
- [x] Куратор / Admin: уведомление о новом ДЗ на проверку
|
||
- [x] Ученик: уведомление о полученном фидбеке
|
||
- [x] Resend domain: mailsend.second-brain.ru (verified)
|
||
|
||
---
|
||
|
||
## Этап 8 — Импорт уроков из Markdown (Obsidian) ✅ ЗАВЕРШЁН (07.04.2026)
|
||
|
||
- [x] API: `POST /api/admin/import-md` — принимает .md-файл
|
||
- [x] Парсинг frontmatter (title, kinescopeId, order, published) через `gray-matter`
|
||
- [x] Конвертация Markdown → TipTap JSON через `unified` + `remark-parse`
|
||
- [x] Поддержка: заголовки, параграфы, жирный/курсив/зачёркнутый, инлайн-код, блоки кода, цитаты, списки, ссылки, изображения (HTTP), горизонтальные разделители
|
||
- [x] Очистка Obsidian-синтаксиса: `![[image]]` удаляется, `[[link|alias]]` → текст
|
||
- [x] UI: кнопка «Импорт .md» в редакторе урока — заполняет форму без автосохранения
|
||
|
||
---
|
||
|
||
## Этап 9 — Настройки платформы (Admin Settings) ✅ ЗАВЕРШЁН (08.04.2026)
|
||
**Цель:** администратор управляет ключевыми параметрами платформы без правки кода.
|
||
|
||
### Основное
|
||
- [ ] Название школы (используется в заголовке сайта, подписи писем)
|
||
- [ ] Описание школы (мета-тег description)
|
||
- [ ] Ключевые слова (мета-тег keywords)
|
||
- [ ] Режим тех. работ: вкл/выкл (показывает заглушку всем кроме admin)
|
||
- [ ] Регистрация учеников: вкл/выкл
|
||
|
||
### Оформление
|
||
- [ ] Логотип школы (загрузка → Object Storage, отображается в шапке)
|
||
- [ ] Фавикон (загрузка → Object Storage)
|
||
- [ ] Показывать логотип: да/нет
|
||
|
||
### Уведомления
|
||
- [ ] Email(ы) для системных уведомлений (кому слать письма о ДЗ, вопросах, регистрациях)
|
||
- [ ] Уведомление куратору/админу о новом ДЗ: вкл/выкл
|
||
- [ ] Уведомление куратору/админу о новом вопросе ученика: вкл/выкл
|
||
- [ ] Уведомление админу о новой регистрации: вкл/выкл
|
||
- [ ] Уведомление ученику при ответе на ДЗ/вопрос: вкл/выкл
|
||
|
||
### Данные ученика
|
||
- [ ] Требовать подтверждение email перед доступом к курсам: да/нет
|
||
- [ ] Фамилия при регистрации: обязательная / необязательная / выключена
|
||
- [ ] Телефон при регистрации: обязательный / необязательный / выключен
|
||
|
||
### Защита
|
||
- [ ] Одна активная сессия на аккаунт: вкл/выкл
|
||
- [ ] CAPTCHA на форме регистрации: вкл/выкл (reCAPTCHA v3)
|
||
|
||
### Права куратора
|
||
- [ ] Куратор видит ДЗ: по всем курсам / только по назначенным курсам
|
||
- [ ] Куратор может отвечать на вопросы учеников: да/нет
|
||
- [ ] Куратор видит список всех студентов: да/нет
|
||
|
||
### Вставка кода
|
||
- [ ] Произвольный код в `<head>` (Yandex.Metrika, Google Analytics, пиксели)
|
||
- [ ] Произвольный код в `<body>` (виджеты, чаты поддержки)
|
||
|
||
### Юридические документы
|
||
- [ ] URL Политики конфиденциальности (ссылка на внешний документ)
|
||
- [ ] URL Согласия на обработку персональных данных
|
||
- [ ] URL Договора-оферты
|
||
- [ ] Показывать чекбокс «Я принимаю условия» при регистрации: да/нет
|
||
- [ ] Реквизиты организации (текстовое поле, отображается в подвале)
|
||
|
||
### Соц. сети
|
||
- [ ] YouTube: одна ссылка
|
||
- [ ] VK: несколько ссылок (название + URL), например «Основная группа» и «Канал»
|
||
- [ ] Telegram: несколько ссылок (название + URL), например «Основной канал» и «Канал курса»
|
||
(отображаются в подвале личного кабинета ученика; хранятся как JSON-массив в Settings)
|
||
|
||
### Вопросы учеников
|
||
- [ ] Система вопросов глобально: вкл/выкл
|
||
- [ ] Куратор/админ может написать ученику первым: да/нет
|
||
- [ ] Вопросы только по курсам ученика: да/нет
|
||
- [ ] Включать вопросы для новых курсов автоматически: да/нет
|
||
|
||
**Хранение:** таблица `Settings` (key-value), доступна через `getSettings()` в server components.
|
||
**Критерий готовности:** меняю название школы → оно появляется в заголовке. Включаю тех. работы → ученики видят заглушку. Куратор привязан к курсу — видит только его ДЗ.
|
||
|
||
---
|
||
|
||
## Этап 11 — Импорт/Экспорт учеников и миграция с emdesell
|
||
**Цель:** все пользователи и контент перенесены в новую LMS. Раздел `/admin/import-export`.
|
||
|
||
### Импорт учеников (CSV)
|
||
- [ ] Скачать файл-шаблон CSV (Email, Имя, Фамилия, Телефон)
|
||
- [ ] Загрузка CSV, поддержка кодировок Windows-1251 и UTF-8
|
||
- [ ] Опция: подтверждать email автоматически (да/нет)
|
||
- [ ] Опция: обновлять уже существующие аккаунты (да/нет)
|
||
- [ ] Присвоение доступов к курсам при импорте (выбор курса + срок в днях, 0 = бессрочно)
|
||
- [ ] Опция: отправить письмо-уведомление ученику (со ссылкой для установки пароля)
|
||
- [ ] Предпросмотр перед применением (таблица: кто создаётся, кто обновляется, кому даётся доступ)
|
||
- [ ] Применить импорт — создать пользователей, выдать доступы, отправить письма
|
||
|
||
### Экспорт учеников (CSV)
|
||
- [ ] Все ученики или фильтр по конкретному курсу/доступу
|
||
- [ ] Фильтр по просмотрам уроков (экспортировать только тех кто смотрел)
|
||
- [ ] Выбор кодировки: Windows-1251 (для Excel) / UTF-8
|
||
- [ ] Поля: Email, Имя, Фамилия, Телефон, Дата регистрации, Курсы, Прогресс
|
||
|
||
### Миграция контента
|
||
- [ ] Чек-лист ручного переноса контента (уроки, PDF, структура курсов)
|
||
- [ ] Скрипт проверки целостности: все enrolled пользователи имеют доступ к нужным курсам
|
||
- [ ] QA: проверить 10 случайных аккаунтов после импорта
|
||
|
||
**Критерий готовности:** загружаю CSV из emdesell → предпросмотр показывает корректные данные → применяю → ученики получают письма → могут войти и продолжить обучение.
|
||
|
||
---
|
||
|
||
## Этап 12 — Telegram-бот и аналитика
|
||
**Цель:** уведомления в Telegram для всех участников, базовая аналитика.
|
||
|
||
**Настройки (в разделе Настройки → Telegram):**
|
||
- Токен бота (вводится в админке, хранится в Settings)
|
||
- Интеграция вкл/выкл глобально
|
||
- Показывать кнопку «Подключить Telegram» в кабинете ученика: да/нет
|
||
|
||
**Уведомления куратору/админу:**
|
||
- [ ] Новое ДЗ на проверку
|
||
- [ ] Новый вопрос от ученика
|
||
- [ ] Новая регистрация студента
|
||
- [ ] Ошибки платформы (500-е, failed email и т.д.)
|
||
|
||
**Уведомления ученику:**
|
||
- [ ] Получен фидбек по ДЗ
|
||
- [ ] Ответ куратора на вопрос
|
||
- [ ] Открыт доступ к новому курсу
|
||
|
||
**Реализация:**
|
||
- [ ] Ученик привязывает Telegram через `/start` в боте (сохраняется `telegramChatId` в User)
|
||
- [ ] Кнопка «Подключить Telegram» в личном кабинете ученика
|
||
- [ ] Админ/куратор вводит свой `chatId` в профиле или через `/start`
|
||
- [ ] Настройки бота в разделе Настройки → Telegram
|
||
- [ ] Yandex.Metrika: базовое подключение (pageviews)
|
||
- [ ] Admin: простая страница аналитики (активные ученики, прогресс по курсам)
|
||
|
||
---
|
||
|
||
## Этап 13 — Тесты и квизы
|
||
**Цель:** можно добавить тест к уроку, ученик проходит и получает результат.
|
||
|
||
- [ ] Prisma: Quiz, QuizQuestion, QuizOption, QuizAttempt (схема уже есть)
|
||
- [ ] Admin: создание теста к уроку (вопрос → варианты → отметить правильный)
|
||
- [ ] Типы вопросов: одиночный выбор, множественный выбор, короткий текст
|
||
- [ ] Рендер теста в уроке для ученика
|
||
- [ ] Авто-проверка (single/multiple choice), результат сразу
|
||
- [ ] Настройка: показывать правильные ответы после прохождения (да/нет)
|
||
- [ ] Интеграция с прогрессом: урок с тестом засчитан только после прохождения теста
|
||
|
||
**Критерий готовности:** добавляю тест из 3 вопросов к уроку, ученик проходит, видит результат, урок засчитывается.
|
||
|
||
---
|
||
|
||
## Бэклог (после MVP)
|
||
|
||
- **Миграция email-шаблонов на React Email 6 + Resend CLI 2.0** (Resend Launch Week 6, 24.04.2026):
|
||
- React Email 6: новые шаблоны для auth и ecommerce flows (welcome, password reset, purchase confirmation, course progress) — можно взять за основу вместо своих
|
||
- Resend CLI 2.0: локальный preview и тестирование шаблонов (`resend send --local ...`), 50+ команд
|
||
- Embeddable open-source editor (в одну строку) — отложить, пока не требуется
|
||
- Сейчас Этап 7 (Email-уведомления) завершён на базовой связке, задача — рефакторинг на React Email
|
||
|
||
- **Самостоятельная регистрация + автоматический онбординг** — два сценария входа и воронка после регистрации:
|
||
|
||
**Сценарии регистрации:**
|
||
- С лендинга через покупку — пользователь оплачивает курс, аккаунт создаётся автоматически, письмо с доступом приходит сразу
|
||
- Прямой вход на платформу — пользователь приходит по реферальной ссылке, из соцсетей, от партнёров — регистрируется сам без покупки
|
||
|
||
**Автоматический онбординг после регистрации:**
|
||
- Автоназначение вводных / вотер-модулей курсов (бесплатные превью, чтобы зацепить)
|
||
- Доступ к базовой библиотеке материалов по умолчанию (статьи, шаблоны, гайды — определяется в настройках)
|
||
- Приветственная воронка: серия писем / уведомлений, которая ведёт к первой покупке
|
||
- Уведомление администратора о новой регистрации (email + Telegram)
|
||
|
||
**Что нужно проработать:**
|
||
- Публичная страница регистрации (+ CAPTCHA, опционально)
|
||
- Настройка в Этапе 9: «Регистрация открыта: да/нет» + выбор вводных курсов/модулей, которые назначаются автоматически
|
||
- Интеграция с платёжной системой: оплата на лендинге → автосоздание аккаунта → автовыдача доступа к купленному курсу
|
||
- Разграничение: что видит гость / зарегистрированный без покупки / купивший курс
|
||
|
||
- Резервное копирование PostgreSQL (cron → Object Storage)
|
||
- GitHub Actions: CI/CD pipeline (lint → build → push Docker image → deploy)
|
||
- Сертификаты по окончании курса
|
||
- Геймификация (баллы, бейджи, рейтинги)
|
||
- Промокоды и интеграция с платёжными системами
|
||
- Дедлайны и расписания
|
||
- Kinescope DRM (signed URLs) — при переходе на платный план
|
||
- Водяные знаки на PDF и картинках
|
||
- Мобильное приложение
|
||
- **Вопросы учеников** — система тикетов `/admin/questions` и `/questions` для ученика:
|
||
- Таблица в админке: №, Имя, Курс, Тема, Статус (Ожидает / Отвечено), Дата
|
||
- Статусы отсортированы: сначала «Ожидает ответа»
|
||
- Куратор/Admin может создать обращение первым (написать ученику)
|
||
- Внутри тикета: история переписки, смена статуса
|
||
- **База знаний** — FAQ, который ученик видит до отправки вопроса
|
||
- **Шаблоны ответов** — куратор выбирает готовый ответ из списка
|
||
- Email + Telegram уведомления обеим сторонам
|
||
|
||
- **Главная страница ученика** — кастомизируемый экран после входа:
|
||
- Приветственный баннер с описанием школы (редактируется в настройках)
|
||
- Список курсов ученика с прогрессом
|
||
- Блок бесплатных/открытых материалов (статьи, PDF, видео)
|
||
- Анонсы ближайших событий и новых курсов
|
||
|
||
- **Медиатека (Файлы)** — централизованное файловое хранилище `/admin/files`:
|
||
- Prisma: `MediaFolder` (id, name, courseId?, createdAt) + `MediaFile` (id, folderId?, name, url, size, mimeType, uploadedById, createdAt)
|
||
- Папки автоматически создаются по курсам + «Common» для общих файлов
|
||
- Вид: грид (карточки с иконкой типа) или список — переключатель
|
||
- Breadcrumb-навигация: Все файлы / Название папки
|
||
- Загрузка файлов (PDF, изображения, любые) → Object Storage
|
||
- Создание папки вручную
|
||
- Клик на файл → диалог: имя (редактируемое), дата загрузки, размер, автор
|
||
- Действия в диалоге: скопировать ссылку, скачать, удалить
|
||
- Вставка файлов из медиатеки в урок (вместо повторной загрузки)
|
||
|
||
- **Цифровой сад** — публичный раздел платформы для сообщества:
|
||
- Методические материалы и статьи (PKM, Obsidian, Second Brain)
|
||
- Рекомендованная литература с аннотациями
|
||
- Записи открытых встреч и вебинаров
|
||
- Календарь: предстоящие открытые уроки, запуски курсов, события
|
||
- Возможно: публичный Obsidian-like граф знаний
|