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

343 lines
26 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 граф знаний