Files
learning/README.md
Ilya 62519081e7 init: learning-service skeleton
Микросервис обучения портала: тесты, курсы, видео-уроки, доступы,
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>
2026-05-25 22:43:37 +03:00

101 lines
5.8 KiB
Markdown
Raw 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.
# 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
```