Микросервис обучения портала: тесты, курсы, видео-уроки, доступы, public-ссылки для кандидатов с email-валидацией. В этой итерации: - Skeleton (config, migrate, main, health) по паттерну tasks/candidates - Migration 001_init: 10 таблиц (tests/questions/answers/attempts/ attempt_answers + courses/lessons/lesson_progress + access_grants + public_tokens) с подробными комментариями why - Tests: полный CRUD + вопросы/ответы; non-owner'у is_correct и explanation скрываются в выдаче - Заглушки 501 для attempts / courses / lessons / video-stream / access / public-tokens — следующие итерации - k8s: namespace, configmap, secrets, postgres, deployment с HPA, service с portal-discovery annotations - Dockerfile, Makefile, .gitignore См. README.md для полного списка отложенного и инструкций запуска. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
101 lines
5.8 KiB
Markdown
101 lines
5.8 KiB
Markdown
# learning-service
|
||
|
||
Микросервис обучения портала: тесты, курсы с видео-уроками,
|
||
гранулярные доступы, public-ссылки для кандидатов с email-валидацией.
|
||
|
||
## Стек
|
||
|
||
- Go 1.25, chi/v5, pgx/v5, portal-common
|
||
- PostgreSQL (отдельная БД `learning`)
|
||
- MinIO (bucket `learning-videos`) — стрим-прокси, MinIO URL не светится
|
||
клиенту (по аналогии с telephony record stream)
|
||
- Redis (eventbus, опционально) — публикация событий
|
||
`test.submitted` / `course.enrolled` для portal-real-time
|
||
- k8s (namespace `learning`)
|
||
|
||
## Сущности (см. `migrations/001_init.up.sql`)
|
||
|
||
| Сущность | Назначение |
|
||
|---|---|
|
||
| `tests` | Тесты: title, passing_score, max_attempts, time_limit_sec, is_published, owner |
|
||
| `test_questions` | Вопросы (single / multi / text), points, explanation |
|
||
| `test_answers` | Варианты ответа с is_correct |
|
||
| `test_attempts` | Попытки прохождения: user_id ИЛИ public_token_id; score/passed |
|
||
| `test_attempt_answers` | Ответы пользователя в попытке (JSONB payload) |
|
||
| `courses` | Курсы: title, slug, cover, is_published |
|
||
| `lessons` | Уроки курса: markdown + video_key + опциональный test_id |
|
||
| `lesson_progress` | Прогресс «просмотрено / завершено» по урокам |
|
||
| `access_grants` | Гранулярный ACL: ресурс × subject (user/role/department/position/public) |
|
||
| `public_tokens` | Одноразовые ссылки кандидатам с intended_email + max_attempts |
|
||
|
||
## Permission-модель (на стороне portal-backend)
|
||
|
||
| Permission | Кому | Что даёт |
|
||
|---|---|---|
|
||
| `service.learning.access` | Все активные сотрудники | Видимость раздела, прохождение назначенного |
|
||
| `service.learning.author` | HR, тренеры, лиды | Создание/редактирование своих материалов, public-ссылки |
|
||
| `service.learning.admin` | Admin | Глобальный доступ к чужим материалам, аналитика |
|
||
|
||
См. portal-backend `migrations/049_learning_permissions.up.sql`.
|
||
|
||
## Что реализовано в этой итерации
|
||
|
||
**Backend:**
|
||
- Skeleton сервиса (config, migrate, main, health)
|
||
- Migration `001_init.up.sql` — полная схема всех 10 таблиц
|
||
- **Tests:** полный CRUD + вопросы/ответы + scoped-выдача (не-владельцу
|
||
is_correct скрывается, explanation тоже)
|
||
- Заглушки `notImplemented` (501) для остальных эндпоинтов
|
||
|
||
**Portal-backend:**
|
||
- Migration `049_learning_permissions.up.sql` — 3 пермы + грантование admin'у
|
||
- `/api/learning/*` — прокси под `service.learning.access`
|
||
- `/public/learning/*` — прокси без auth для кандидатов
|
||
- `Services.LearningURL` + env-переменная
|
||
|
||
**Frontend:**
|
||
- Route `/learning/*` под guard'ом `service.learning.access`
|
||
- Landing-страница с тремя картчоками
|
||
- `/learning/tests` — список + создание (полностью рабочий)
|
||
- `/learning/courses`, `/learning/admin` — заглушки `LearningStubComponent`
|
||
- Sidebar-пункт «Обучение» + sub-навигация
|
||
|
||
**k8s:** полный набор манифестов (namespace / configmap / secrets /
|
||
postgres / deployment с HPA / service с portal-discovery annotations).
|
||
|
||
## Что отложено в следующие итерации
|
||
|
||
| Фича | План |
|
||
|---|---|
|
||
| **Attempts (прохождение)** | Repository + handler: start attempt → fetch questions → submit → auto-grade (single/multi) + manual review для text |
|
||
| **Courses + Lessons CRUD** | Repository + handler по тому же паттерну что Tests |
|
||
| **Видео-стрим** | MinIO wrapper в `internal/storage/`, handler `/lessons/{id}/video/stream` с Range-поддержкой; копировать stream-pattern из telephony |
|
||
| **Access grants** | Repository + handler + helper «может ли user X пройти ресурс Y» (учитывает user/role/department/position иерархию через portal-internal API) |
|
||
| **Public tokens** | Repository + handler: создание (HR из portal'а), resolve (email-match) — на портале и стрим в proxy без auth |
|
||
| **Email-отправка** | Через portal Kerio integration — handler выдаёт сформированный URL + текст письма HR'у, реальная отправка через portal |
|
||
| **Frontend: конструктор теста** | Drag-and-drop редактор вопросов/ответов (паттерн как kanban-column в tasks) |
|
||
| **Frontend: плеер видео-урока** | Кастомный плеер по аналогии с call-audio-player (blob URL, controlsList=nodownload) |
|
||
| **Frontend: public-страница кандидата** | Отдельный route `/public/learning/test/:token` без layout'а портала |
|
||
|
||
## Локальный запуск
|
||
|
||
```bash
|
||
docker compose up -d postgres minio # или используй существующие
|
||
make tidy
|
||
DATABASE_URL=postgres://learning:learning@localhost:5432/learning?sslmode=disable \
|
||
INTERNAL_API_KEY=devkey \
|
||
MINIO_ENDPOINT=localhost:9000 \
|
||
MINIO_ACCESS_KEY=minioadmin \
|
||
MINIO_SECRET_KEY=minioadmin \
|
||
make run
|
||
```
|
||
|
||
## Deploy
|
||
|
||
```bash
|
||
kubectl apply -k k8s/
|
||
# Образ собирается в CI или вручную:
|
||
docker build -f Dockerfile.server -t localhost:30300/admin/learning-server:latest .
|
||
docker push localhost:30300/admin/learning-server:latest
|
||
```
|