Files
learning/internal/config/config.go
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

98 lines
3.2 KiB
Go
Raw Permalink 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.
// Package config — env-based конфиг learning-сервиса. Стиль envStr идентичен
// candidates/tasks/booking — чтобы k8s-манифесты выглядели единообразно.
package config
import (
"os"
"strconv"
)
type Config struct {
ServerPort string
DatabaseURL string
MigrationsDir string
// InternalAPIKey — общий ключ с порталом для server-to-server auth.
// Portal-proxy ставит X-Internal-Key + X-User-Id; сервис верифицирует
// ключ и доверяет X-User-Id (JWT уже провалидирован на портале).
InternalAPIKey string
// PortalURL — для in-app уведомлений (назначен курс/тест/пройден тест).
// Если пусто — notify-вызовы тихо no-op'ятся, основной flow работает.
PortalURL string
// PublicBaseURL — внешний origin, под которым отдаются public-ссылки
// для кандидатов (например, https://portal.estateliga.work). Используется
// в формате URL'ей: <PUBLIC_BASE>/public/learning/test/<token>.
PublicBaseURL string
// MinIO для видео-уроков. Структура та же что в telephony (records bucket),
// но bucket отдельный — public-link'и читают видео через стрим-прокси.
MinIOEndpoint string
MinIOAccessKey string
MinIOSecretKey string
MinIOBucket string
MinIOUseSSL bool
// RedisAddr — пустой = eventbus отключён, сервис работает без публикации
// событий (попадание кандидата на тест не уведомит HR в realtime).
RedisAddr string
RedisPassword string
RedisDB int
PodName string
}
func Load() *Config {
return &Config{
ServerPort: envStr("SERVER_PORT", "3001"),
DatabaseURL: envStr("DATABASE_URL", "postgres://learning:learning@localhost:5432/learning?sslmode=disable"),
MigrationsDir: envStr("MIGRATIONS_DIR", "/migrations"),
InternalAPIKey: envStr("INTERNAL_API_KEY", envStr("PORTAL_INTERNAL_API_KEY", "")),
PortalURL: envStr("PORTAL_URL", ""),
PublicBaseURL: envStr("PUBLIC_BASE_URL", ""),
MinIOEndpoint: envStr("MINIO_ENDPOINT", ""),
MinIOAccessKey: envStr("MINIO_ACCESS_KEY", ""),
MinIOSecretKey: envStr("MINIO_SECRET_KEY", ""),
MinIOBucket: envStr("MINIO_BUCKET", "learning-videos"),
MinIOUseSSL: envBool("MINIO_USE_SSL", false),
RedisAddr: envStr("REDIS_ADDR", ""),
RedisPassword: envStr("REDIS_PASSWORD", ""),
RedisDB: envInt("REDIS_DB", 0),
PodName: envStr("POD_NAME", hostname()),
}
}
func envInt(key string, def int) int {
if v := os.Getenv(key); v != "" {
if n, err := strconv.Atoi(v); err == nil {
return n
}
}
return def
}
func envBool(key string, def bool) bool {
if v := os.Getenv(key); v != "" {
if b, err := strconv.ParseBool(v); err == nil {
return b
}
}
return def
}
func envStr(key, def string) string {
if v := os.Getenv(key); v != "" {
return v
}
return def
}
func hostname() string {
h, err := os.Hostname()
if err != nil {
return "unknown"
}
return h
}