Интерактивный граф знаний по математике для подготовки к ЕГЭ и олимпиадам.
Стек: Next.js 16 · NestJS 11 · PostgreSQL · Neo4j · Redis · Prisma · ReactFlow
- Архитектура
- Быстрый старт
- Переменные окружения
- Структура проекта
- API
- Модели данных
- Граф Neo4j
- Фичи
- Деплой
monorepo (npm workspaces)
├── apps/api — NestJS бэкенд
├── apps/web — Next.js фронтенд
└── libs/shared — общие типы (TypeScript)
Хранилища:
| Хранилище | Назначение |
|---|---|
| PostgreSQL | Пользователи, узлы графа, прогресс, заметки, квизы, версии |
| Neo4j | Граф связей: CONTAINS, PREREQ_REQUIRED |
| Redis | Кэш топик-страниц, rate-limit |
npm installdocker-compose up -dЗапускает: PostgreSQL (порт 5433), Neo4j (7474/7687), Redis (6379).
cp apps/api/.env.example apps/api/.env
# Отредактируйте значения (см. раздел ниже)
# Фронтенд
echo "NEXT_PUBLIC_API_URL=http://localhost:3001" > apps/web/.env.localcd apps/api
npx prisma migrate deploy
npx tsx prisma/seed.tsСид создаёт: 26 топиков, 170+ узлов, 250+ рёбер, 10 квиз-вопросов, admin-аккаунт.
Admin по умолчанию: al.zar.evg13@gmail.com / admin123
# API (порт 3001)
npm run dev:api
# Фронтенд (порт 3000)
npm run dev:webnpm run test:web # Vitest (фронтенд)
npm run test:api # Jest (бэкенд)PORT=3001
# PostgreSQL
DATABASE_URL=postgresql://postgres:postgres@localhost:5433/mathgraph?schema=public
# Neo4j
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=testtest1
# Redis (формат: redis://:пароль@хост:порт)
REDIS_URL=redis://:redis@localhost:6379
# JWT
JWT_SECRET=your-secret-here
JWT_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=7d
# LLM (OpenRouter / DeepSeek / любой OpenAI-совместимый)
DEEPSEEK_API_KEY=sk-...
DEEPSEEK_BASE_URL=https://openrouter.ai/api/v1
DEEPSEEK_MODEL=nvidia/nemotron-3-super-120b-a12b:freeNEXT_PUBLIC_API_URL=http://localhost:3001apps/api/src/
├── auth/ — регистрация, логин, JWT, роли (USER / COMPOSER / ADMIN)
├── knowledge/ — CRUD узлов графа, экспорт/импорт JSON
├── topic-page/ — сборка топик-страницы (узлы + слоты)
├── topics/ — список топиков
├── neo4j/ — Neo4j сервис (prereq subgraph, topic subgraph)
├── progress/ — прогресс пользователя по узлам
├── analytics/ — heatmap активности, статистика по ролям
├── versioning/ — история версий узлов, откат
├── search/ — Postgres FTS + ILIKE fallback
├── notes/ — личные заметки к узлам (markdown)
├── quiz/ — вопросы и попытки ответов
├── route/ — AI-маршрут обучения (Neo4j + LLM)
├── assistant/ — AI-чат (сессии, история)
├── study-plan/ — план изучения
├── roadmap/ — roadmap страница
├── redis/ — кэш-сервис
└── prisma/ — Prisma сервис
apps/web/src/
├── app/ — Next.js страницы
│ ├── / — Главная (список топиков)
│ ├── /topic/[id] — Граф топика
│ ├── /profile — Личный кабинет
│ ├── /analytics — Дашборд аналитики
│ ├── /presets — Учебные треки (ЕГЭ / Олимпиады)
│ ├── /route — AI-маршрут обучения
│ ├── /roadmap — Дорожная карта тем
│ ├── /editor — Редактор графа (COMPOSER / ADMIN)
│ ├── /admin — Панель администратора
│ └── /auth — Авторизация
├── components/
│ └── Navbar.tsx — Единый навбар (desktop + mobile bottom nav)
├── features/
│ ├── graph-view/ — ReactFlow граф (KgNode, RowLabel, MobileGraphList)
│ ├── topic-page/ — Лейаут топик-страницы + bottom sheet панель
│ ├── notes/ — Редактор заметок с markdown-превью
│ ├── quiz/ — Панель квиза (MC + text input)
│ ├── editor/ — LaTeX WYSIWYG (MathLive)
│ ├── search/ — Глобальный поиск с клавиатурной навигацией
│ └── assistant/ — AI-чат кнопка и панель
├── lib/ — API-клиенты (authApi, progressApi, quizApi, ...)
├── hooks/ — useMobileDetect, useSwipeDown, useGraphKeyboardNav
└── providers/ — ThemeProvider, AuthProvider
Swagger UI доступен по адресу: http://localhost:3001/api
| Метод | Путь | Описание |
|---|---|---|
POST |
/auth/register |
Регистрация |
POST |
/auth/login |
Вход, возвращает accessToken + refreshToken |
POST |
/auth/refresh |
Обновление access-токена |
GET |
/auth/profile |
Профиль текущего пользователя |
PATCH |
/auth/profile |
Обновить имя пользователя |
| Метод | Путь | Описание |
|---|---|---|
GET |
/knowledge/nodes |
Список всех узлов |
POST |
/knowledge/nodes |
Создать узел |
PATCH |
/knowledge/nodes/:id |
Обновить узел |
DELETE |
/knowledge/nodes/:id |
Удалить узел |
POST |
/knowledge/edges |
Создать ребро |
DELETE |
/knowledge/edges |
Удалить ребро |
GET |
/knowledge/export |
Экспорт всего графа (JSON) |
POST |
/knowledge/import |
Импорт графа (JSON) |
| Метод | Путь | Описание |
|---|---|---|
GET |
/topic/:id/page?track=school&depth=1 |
Данные для страницы топика |
| Метод | Путь | Описание |
|---|---|---|
POST |
/progress/toggle/:nodeId |
Переключить статус узла |
GET |
/progress/topic?nodeIds=... |
Прогресс по набору узлов |
GET |
/progress/summary |
Прогресс по всем топикам |
| Метод | Путь | Описание |
|---|---|---|
GET |
/quiz/:nodeId |
Вопросы для узла (без ответов) |
POST |
/quiz/answer |
Отправить ответ → { correct, correctAnswer, explanation } |
GET |
/quiz/:nodeId/stats |
Статистика попыток пользователя |
POST |
/quiz/:nodeId |
Создать вопрос (ADMIN) |
| Метод | Путь | Описание |
|---|---|---|
POST |
/route |
Построить AI-маршрут обучения по цели |
POST |
/assistant/ask |
Разовый вопрос AI-ассистенту |
POST |
/assistant/sessions |
Создать чат-сессию |
POST |
/assistant/sessions/:id/message |
Отправить сообщение в сессию |
| Метод | Путь | Описание |
|---|---|---|
GET |
/search?q=...&limit=20 |
Полнотекстовый поиск узлов |
GET |
/notes/:nodeId |
Заметка пользователя к узлу |
PUT |
/notes/:nodeId |
Сохранить заметку |
GET |
/analytics/summary |
Сводная статистика |
GET |
/analytics/activity |
Активность за 90 дней |
model KgNodeRegistry {
id String — UUID
title String
role TopicNodeRole — TOPIC | CONCEPT | METHOD | SKILL | TASK
description String?
content String?
resources String[]
fipiCode String?
status NodeStatus — DRAFT | PUBLISHED | ARCHIVED
}
model User {
id String
email String @unique
name String?
role UserRole — USER | COMPOSER | ADMIN
passwordHash String
}
model NodeProgress — userId + nodeId + completed + completedAt
model NodeNote — userId + nodeId + content (markdown)
model NodeQuestion — nodeId + type (MC/TEXT) + question + options + answer
model QuizAttempt — userId + questionId + answer + correct
model KgNodeVersion — nodeId + version + snapshot полей
model ChatSession — userId + messages (JSON) + topicId| Роль | Цвет | Назначение |
|---|---|---|
| TOPIC | синий | Тема верхнего уровня |
| CONCEPT | фиолетовый | Понятие / определение |
| METHOD | оранжевый | Способ решения |
| SKILL | зелёный | Практический навык |
| TASK | красный | Задача / упражнение |
| Тип | Направление | Смысл |
|---|---|---|
CONTAINS |
Topic → Node | Топик содержит узел |
PREREQ_REQUIRED |
Node → Node | Узел A нужен перед узлом B |
MATCH path = (prereq:KGNode)-[:PREREQ_REQUIRED*0..6]->(target:KGNode {id: $topicId})
WHERE prereq.role = 'TOPIC'
WITH prereq, min(length(path)) AS dist
RETURN prereq.id AS id, prereq.title AS title, dist
ORDER BY dist DESC- Режимы отображения: Обзор / Фокус / Путь / Зависимости
- Режим зависимостей: BFS по
PREREQ_REQUIRED— подсвечивает что нужно знать перед выбранным узлом - Адаптивная ширина узлов — рассчитывается по длине заголовка
- Row-labels — виртуальные узлы-разделители строк в ReactFlow
- Мобильная версия — список с аккордеонами вместо графа
- Desktop: выезжает справа
- Mobile: bottom sheet с drag handle + свайп вниз для закрытия
- Содержит: детали узла, прогресс, видео-эмбед, markdown-заметку, квиз
Автодетект URL в поле resources:
- YouTube — thumbnail превью → iframe с autoplay
- VK / vkvideo.ru — цветная карточка → iframe
- Rutube — карточка → iframe
В редакторе узлов кнопка ∑ формула открывает модалку:
- MathLive WYSIWYG-редактор
- 24 кнопки быстрой вставки
- Live KaTeX-превью
- Вставка в текст на позицию курсора (
$...$или$$...$$)
- Пользователь вводит цель: "хочу понять логарифмы"
- FTS-поиск → матч с топиком в Postgres
- BFS по
PREREQ_REQUIREDв Neo4j → цепочка тем - LLM генерирует объяснение каждого шага и вступление
- Timeline с кнопками "Открыть →" для каждой темы
В редакторе: ↑ Импорт CSV — поддерживает запятую и точку с запятой.
Формат:
title,role,description,content,fipiCode,resources
Теорема Пифагора,CONCEPT,Квадрат гипотенузы...,,,| Трек | Тем | Аудитория |
|---|---|---|
| ЕГЭ База | 10 | Базовый уровень |
| ЕГЭ Профиль | 24 | Профильный уровень |
| Олимпиады | 12 | Математические олимпиады |
Прогресс-кольцо на основе progress/summary.
| Сервис | Назначение | Лимит free tier |
|---|---|---|
| Vercel | Next.js фронтенд | Без лимита |
| Railway | NestJS API | 500 часов/мес |
| Neon | PostgreSQL | 0.5 GB |
| Neo4j Aura Free | Neo4j | 200 MB, 1 инстанс |
| Upstash | Redis | 10K команд/день |
В Railway/Vercel установить:
DATABASE_URL= # Neon connection string
NEO4J_URI= # Neo4j Aura bolt URL
NEO4J_USER=neo4j
NEO4J_PASSWORD= # Aura password
REDIS_URL= # Upstash redis URL (с паролем)
JWT_SECRET= # случайная строка 64+ символов
DEEPSEEK_API_KEY= # ключ LLM-провайдера
DEEPSEEK_BASE_URL=
DEEPSEEK_MODEL=
- Создать инстансы в Neon, Neo4j Aura, Upstash
- Запустить миграции:
npx prisma migrate deploy - Запустить сид:
npx tsx prisma/seed.ts - Задеплоить API на Railway (
apps/api) - Задеплоить фронтенд на Vercel (
apps/web) - Установить
NEXT_PUBLIC_API_URLв Vercel → URL Railway-инстанса - Настроить CORS в API: разрешить домен Vercel
Важно: Railway усыпляет инстанс после 30 минут неактивности. Для production рекомендуется платный план или холодный старт через
/healthendpoint.
cd apps/api
npx prisma migrate dev --name <название>
npx prisma generatecd apps/api
npx tsx prisma/seed.tscurl -X POST http://localhost:3001/quiz/<nodeId> \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"type": "MULTIPLE_CHOICE",
"question": "Чему равен дискриминант?",
"options": ["b²-4ac", "b²+4ac", "-b/2a"],
"answer": "b²-4ac",
"explanation": "D = b² - 4ac"
}'