Files
tiennm99 546e6fcde2 refactor(store): replace Redis with MongoDB datastore
Swap the Redis datastore for MongoDB via mongo-driver v2. New
internal/mongostore mirrors the old redisstore type/constant names so
consumers change only the import qualifier; document model collapses the
multi-key subscriber/settings and per-event delivery sets into single-doc
operations and a TTL-indexed delivery collection.

Config now requires MONGODB_URI (Atlas) with MONGODB_DATABASE selecting
dev vs prod on one cluster; REDIS_URL removed. Compose files run bot-only
against Atlas. Default tests stay Docker-free (poller/bot use fakes,
mongostore unit tests are pure-logic); a gated //go:build integration
suite covers the real backend via testcontainers.
2026-06-26 13:41:16 +07:00

94 lines
2.3 KiB
Go

package config
import (
"fmt"
"log/slog"
"os"
"strings"
"time"
)
type Config struct {
TelegramBotToken string
MongoURI string
MongoDatabase string
PollInterval time.Duration
HTTPTimeout time.Duration
LogLevel slog.Level
}
const (
minPollInterval = 5 * time.Second
maxPollInterval = time.Hour
minHTTPTimeout = time.Second
maxHTTPTimeout = 5 * time.Minute
defaultMongoDatabase = "openai_status_bot"
)
func LoadFromEnv() (Config, error) {
cfg := Config{
TelegramBotToken: strings.TrimSpace(os.Getenv("TELEGRAM_BOT_TOKEN")),
}
if cfg.TelegramBotToken == "" {
return Config{}, fmt.Errorf("TELEGRAM_BOT_TOKEN is required")
}
cfg.MongoURI = strings.TrimSpace(os.Getenv("MONGODB_URI"))
if cfg.MongoURI == "" {
return Config{}, fmt.Errorf("MONGODB_URI is required")
}
cfg.MongoDatabase = getEnv("MONGODB_DATABASE", defaultMongoDatabase)
var err error
cfg.PollInterval, err = parseDurationEnv("POLL_INTERVAL", time.Minute, minPollInterval, maxPollInterval)
if err != nil {
return Config{}, err
}
cfg.HTTPTimeout, err = parseDurationEnv("HTTP_TIMEOUT", 10*time.Second, minHTTPTimeout, maxHTTPTimeout)
if err != nil {
return Config{}, err
}
cfg.LogLevel, err = parseLogLevel(getEnv("LOG_LEVEL", "info"))
if err != nil {
return Config{}, err
}
return cfg, nil
}
func getEnv(key, fallback string) string {
if value := strings.TrimSpace(os.Getenv(key)); value != "" {
return value
}
return fallback
}
func parseDurationEnv(key string, fallback, minValue, maxValue time.Duration) (time.Duration, error) {
value := strings.TrimSpace(os.Getenv(key))
if value == "" {
return fallback, nil
}
parsed, err := time.ParseDuration(value)
if err != nil {
return 0, fmt.Errorf("%s must be a duration: %w", key, err)
}
if parsed < minValue || parsed > maxValue {
return 0, fmt.Errorf("%s must be between %s and %s", key, minValue, maxValue)
}
return parsed, nil
}
func parseLogLevel(value string) (slog.Level, error) {
switch strings.ToLower(strings.TrimSpace(value)) {
case "debug":
return slog.LevelDebug, nil
case "info":
return slog.LevelInfo, nil
case "warn", "warning":
return slog.LevelWarn, nil
case "error":
return slog.LevelError, nil
default:
return slog.LevelInfo, fmt.Errorf("LOG_LEVEL must be one of debug, info, warn, warning, or error")
}
}