Add Markdown import from Obsidian (Stage 8)
- md-to-tiptap.ts: remark-based converter (headings, lists, blockquotes, code blocks, bold/italic/strike, links, images, hr) - Obsidian ![[wikilink]] stripped, [[link|alias]] → plain text - POST /api/admin/import-md: parses frontmatter (gray-matter) + converts content - LessonEditor: "Импорт .md" button populates editor without auto-save - ROADMAP: marked Stages 2, 3, 5, 6, 7, 8 as complete, fixed numbering Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+52
-91
@@ -6,13 +6,12 @@
|
||||
---
|
||||
|
||||
## Этап 0 — Каркас, auth, роли ✅ ЗАВЕРШЁН (07.04.2026)
|
||||
**Задеплоено на:** https://school.second-brain.ru
|
||||
|
||||
- [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] proxy.ts: защита маршрутов по сессии
|
||||
- [x] Middleware: защита маршрутов по сессии
|
||||
- [x] Дашборды для трёх ролей
|
||||
- [x] Страница входа, регистрации, подтверждения email
|
||||
- [x] Seed: admin/curator/student (пароль: Password123!)
|
||||
@@ -28,17 +27,15 @@
|
||||
- [x] Admin: список модулей внутри курса, drag-and-drop сортировка
|
||||
- [x] Admin: список уроков внутри модуля, drag-and-drop сортировка
|
||||
- [x] Admin: редактор урока с TipTap (заголовки, списки, цитаты, код, картинки, ссылки)
|
||||
- [x] Загрузка картинок в уроке → Hetzner Object Storage (second-brain-lms, Nuremberg)
|
||||
- [x] Поле для Kinescope ID в уроке (просто текстовое, без интеграции — это Этап 2)
|
||||
- [x] Загрузка картинок в уроке → Hetzner Object Storage
|
||||
- [x] Поле для Kinescope ID в уроке
|
||||
- [x] Публикация/скрытие курса и урока (черновик / опубликован)
|
||||
- [x] Управление доступом: выдать / забрать доступ к курсу для пользователя
|
||||
- [x] Дизайн в стиле Second Brain: Fira Mono, #F5F5F0, Aubade-карточки
|
||||
- [x] Дизайн: Fira Mono, #F5F5F0, Aubade-карточки
|
||||
- [x] Admin: таблица пользователей (/admin/users)
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Этап 1.5 — Расширенное управление доступом ✅ ЗАВЕРШЁН (07.04.2026)
|
||||
|
||||
- [x] Срок доступа: `expiresAt` в `CourseEnrollment`, просроченный подсвечивается красным
|
||||
@@ -48,99 +45,76 @@
|
||||
|
||||
---
|
||||
|
||||
## Этап 2 — Интеграция Kinescope, рендер уроков для ученика
|
||||
**Цель:** ученик видит урок с видео Kinescope и текстом.
|
||||
## Этап 2 — Интеграция Kinescope, рендер уроков для ученика ✅ ЗАВЕРШЁН (07.04.2026)
|
||||
|
||||
- [ ] Компонент `KinescopePlayer` — обёртка над `@kinescope/react-kinescope-player`
|
||||
- [ ] Рендер урока для ученика: видео (если есть Kinescope ID) + текст + файлы
|
||||
- [ ] Загрузка PDF/файлов к уроку (Object Storage), список для скачивания
|
||||
- [ ] Страница курса для ученика: список модулей и уроков, статус прохождения
|
||||
- [ ] Навигация по урокам: предыдущий / следующий
|
||||
- [ ] Блокировка доступа к курсу без enrollment (middleware или server component)
|
||||
- [ ] Страница «Мои курсы» в личном кабинете ученика
|
||||
|
||||
**Кинескоп-нюанс:** пока без DRM (бесплатный план), просто передаём `videoId` в компонент. Когда появится платный план — добавим signed URLs в отдельной задаче.
|
||||
|
||||
**Критерий готовности:** ученик открывает урок, смотрит видео из Kinescope, читает текст, скачивает PDF.
|
||||
- [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 — Прогресс и линейное открытие уроков
|
||||
**Цель:** ученик проходит курс последовательно, прогресс сохраняется.
|
||||
## Этап 3 — Прогресс ✅ ЗАВЕРШЁН (07.04.2026)
|
||||
|
||||
- [ ] Prisma: таблица `LessonProgress` (userId, lessonId, completedAt)
|
||||
- [ ] Кнопка «Урок завершён» — создаёт запись в LessonProgress
|
||||
- [ ] Логика блокировки: следующий урок открыт только если предыдущий завершён
|
||||
- [ ] Прогресс-бар по курсу (% завершённых уроков)
|
||||
- [ ] Прогресс-бар по модулю
|
||||
- [ ] API: `POST /api/progress/complete` — принимает lessonId, создаёт прогресс
|
||||
- [ ] Иконки статуса уроков в боковой панели: ✓ пройден / 🔒 заблокирован / → текущий
|
||||
|
||||
**Примечание:** если в уроке есть тест (Этап 4) или ДЗ (Этап 5) — кнопка «Завершить» появляется только после их прохождения/отправки. Эту логику дорабатываем на соответствующих этапах.
|
||||
|
||||
**Критерий готовности:** прохожу урок 1, нажимаю «Завершён» — открывается урок 2. Урок 3 заблокирован. Прогресс-бар показывает 33%.
|
||||
- [x] Prisma: таблица `LessonProgress` (userId, lessonId, completedAt)
|
||||
- [x] Кнопка «Урок завершён» — создаёт/удаляет запись в LessonProgress
|
||||
- [x] Прогресс-бар по курсу в боковой панели (% завершённых уроков)
|
||||
- [x] Прогресс-бар по курсу на дашборде студента
|
||||
- [x] Admin bypass: администратор видит все уроки без отметок о прогрессе
|
||||
|
||||
---
|
||||
|
||||
## Этап 5 — Домашние задания и обратная связь куратора
|
||||
**Цель:** ученик сдаёт ДЗ, куратор оставляет комментарий.
|
||||
## Этап 5 — Домашние задания и обратная связь куратора ✅ ЗАВЕРШЁН (07.04.2026)
|
||||
|
||||
- [ ] Prisma: Homework, HomeworkSubmission, HomeworkFeedback
|
||||
- [ ] Admin: добавить блок ДЗ к уроку (текст задания)
|
||||
- [ ] Ученик: форма отправки ДЗ (текст + файлы → Object Storage)
|
||||
- [ ] Ученик: ДЗ засчитывается **автоматически при отправке** (урок открывается сразу)
|
||||
- [ ] Куратор: список ДЗ на проверку (все или по курсу), статус «новое / просмотрено»
|
||||
- [ ] Куратор: просмотр ДЗ, оставить текстовый комментарий
|
||||
- [ ] История обмена по ДЗ (ученик может ответить на комментарий куратора)
|
||||
- [ ] Уведомление ученику когда куратор оставил комментарий (заглушка — реальные email на Этапе 7)
|
||||
|
||||
**Критерий готовности:** отправляю ДЗ — урок открывается. Куратор заходит в панель, видит ДЗ, оставляет комментарий. Ученик видит комментарий в карточке урока.
|
||||
- [x] Prisma: Homework, HomeworkSubmission, HomeworkFeedback
|
||||
- [x] Admin: добавить / редактировать / удалить блок ДЗ к уроку (HomeworkEditor)
|
||||
- [x] Ученик: форма отправки ДЗ (текст + файлы → Object Storage)
|
||||
- [x] Ученик: видит статус ДЗ и фидбек куратора в карточке урока
|
||||
- [x] Куратор / Admin: список ДЗ на проверку (`/curator/homework`)
|
||||
- [x] Куратор / Admin: просмотр работы, текстовый комментарий с обратной связью
|
||||
- [x] Admin: AdminShell на `/curator/*` маршрутах (сайдбар не пропадает)
|
||||
- [x] Admin: реальная статистика на дашборде (студенты, курсы, ДЗ, прогресс)
|
||||
|
||||
---
|
||||
|
||||
## Этап 6 — Обсуждения под уроками ← ТЕКУЩИЙ
|
||||
**Цель:** ученики могут общаться под каждым уроком.
|
||||
## Этап 6 — Обсуждения под уроками ✅ ЗАВЕРШЁН (07.04.2026)
|
||||
|
||||
- [ ] Prisma: LessonComment (с поддержкой вложенных ответов — опционально)
|
||||
- [ ] Рендер треда комментариев под уроком
|
||||
- [ ] Форма отправки комментария (только для enrolled учеников)
|
||||
- [ ] Модерация: куратор/админ может удалить комментарий
|
||||
- [ ] Пагинация или infinite scroll для длинных тредов
|
||||
|
||||
**Критерий готовности:** ученик оставляет комментарий, другой ученик его видит, куратор может удалить.
|
||||
- [x] Prisma: LessonComment (soft-delete через поле `deleted`)
|
||||
- [x] Рендер списка комментариев под уроком (server-fetched, client-rendered)
|
||||
- [x] Форма отправки комментария (только для enrolled учеников и admin)
|
||||
- [x] Модерация: автор, куратор или admin может удалить комментарий
|
||||
- [x] Счётчик активных комментариев в заголовке секции
|
||||
|
||||
---
|
||||
|
||||
## Этап 7 — Email-уведомления ✅ ЗАВЕРШЁН (07.04.2026)
|
||||
**Цель:** все участники получают нужные письма через Resend.
|
||||
|
||||
- [ ] Базовый email-шаблон (HTML, фирменный стиль)
|
||||
- [ ] Ученик: подтверждение регистрации (уже частично с Этапа 0, финализировать)
|
||||
- [ ] Ученик: письмо когда куратор оставил комментарий к ДЗ
|
||||
- [ ] Ученик: письмо когда ответили на его комментарий в уроке
|
||||
- [ ] Куратор / Админ: новое ДЗ на проверку
|
||||
- [ ] Куратор / Админ: новый комментарий в обсуждении
|
||||
- [ ] Админ: зарегистрирован новый ученик
|
||||
- [ ] Очередь отправки (edge case: Resend временно недоступен → retry)
|
||||
|
||||
**Критерий готовности:** отправляю ДЗ — куратор получает email. Куратор отвечает — ученик получает email.
|
||||
- [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)
|
||||
**Цель:** могу импортировать урок из .md-файла Obsidian одним действием.
|
||||
## Этап 8 — Импорт уроков из Markdown (Obsidian) ✅ ЗАВЕРШЁН (07.04.2026)
|
||||
|
||||
- [ ] API: `POST /api/admin/lessons/import-md` — принимает .md-файл
|
||||
- [ ] Парсинг frontmatter (title, order, kinescopeId и кастомные поля) → метаданные урока
|
||||
- [ ] Конвертация Markdown-тела в TipTap JSON (через remark / rehype)
|
||||
- [ ] UI в админке: кнопка «Импортировать из .md» на странице урока
|
||||
- [ ] Обработка картинок в Markdown (локальные пути → Object Storage)
|
||||
|
||||
**Критерий готовности:** беру .md-файл из Obsidian с frontmatter и текстом → импортирую → урок создан с правильными метаданными и контентом.
|
||||
- [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 — Миграция с emdesell
|
||||
## Этап 9 — Миграция с emdesell ← СЛЕДУЮЩИЙ
|
||||
**Цель:** все пользователи и контент перенесены в новую LMS.
|
||||
|
||||
- [ ] Скрипт импорта пользователей из CSV-экспорта emdesell (email, имя, курсы)
|
||||
@@ -164,26 +138,11 @@
|
||||
|
||||
---
|
||||
|
||||
## Этап 11 — Деплой на Hetzner
|
||||
**Цель:** LMS работает на production-сервере по своему домену с SSL.
|
||||
|
||||
- [ ] `docker-compose.prod.yml`: app + PostgreSQL + Redis + Nginx
|
||||
- [ ] Nginx: SSL через Let's Encrypt (certbot), reverse proxy на Next.js
|
||||
- [ ] GitHub Actions: CI/CD pipeline (lint → build → push Docker image → deploy)
|
||||
- [ ] Резервное копирование PostgreSQL (cron → Object Storage)
|
||||
- [ ] Мониторинг uptime (UptimeRobot или аналог)
|
||||
- [ ] `.env` на сервере через Hetzner Secrets Manager или vault-файл вне репозитория
|
||||
- [ ] Smoke-тест: регистрация → урок → ДЗ → куратор → email
|
||||
|
||||
**Критерий MVP готов:** создаю курс из админки, добавляю уроки с Kinescope, импортирую ученика из emdesell, даю доступ — ученик регистрируется, проходит урок, сдаёт тест, отправляет ДЗ, получает автодоступ к следующему уроку, позже — комментарий куратора на email.
|
||||
|
||||
---
|
||||
|
||||
## Этап 11 — Тесты и квизы
|
||||
**Цель:** можно добавить тест к уроку, ученик проходит и получает результат.
|
||||
|
||||
- [ ] Prisma: Quiz, QuizQuestion, QuizOption, QuizAttempt (схема уже есть)
|
||||
- [ ] Admin: создание теста к уроку (добавить вопрос → варианты ответов → отметить правильный)
|
||||
- [ ] Admin: создание теста к уроку (вопрос → варианты → отметить правильный)
|
||||
- [ ] Типы вопросов: одиночный выбор, множественный выбор, короткий текст
|
||||
- [ ] Рендер теста в уроке для ученика
|
||||
- [ ] Авто-проверка (single/multiple choice), результат сразу
|
||||
@@ -196,6 +155,8 @@
|
||||
|
||||
## Бэклог (после MVP)
|
||||
|
||||
- Резервное копирование PostgreSQL (cron → Object Storage)
|
||||
- GitHub Actions: CI/CD pipeline (lint → build → push Docker image → deploy)
|
||||
- Сертификаты по окончании курса
|
||||
- Геймификация (баллы, бейджи, рейтинги)
|
||||
- Промокоды и интеграция с платёжными системами
|
||||
|
||||
Reference in New Issue
Block a user