# 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 ```