Files
tm_back/pkg/config/config.go
T

290 lines
8.9 KiB
Go

package config
import (
"fmt"
"os"
"reflect"
"strconv"
"time"
"github.com/subosito/gotenv"
)
type ServerConfig struct {
Host string `env:"SERVER_HOST"`
Port int `env:"SERVER_PORT"`
Timeout time.Duration `env:"SERVER_TIMEOUT"`
ReadTimeout time.Duration `env:"SERVER_READ_TIMEOUT"`
WriteTimeout time.Duration `env:"SERVER_WRITE_TIMEOUT"`
}
type DatabaseConfig struct {
MongoDB MongoConfig
}
type MongoConfig struct {
URI string `env:"MONGODB_URI"`
Name string `env:"MONGODB_NAME"`
Timeout time.Duration `env:"MONGODB_TIMEOUT"`
MaxPoolSize int `env:"MONGODB_MAX_POOL_SIZE"`
}
type CacheConfig struct {
Redis RedisConfig
}
type RedisConfig struct {
Host string `env:"REDIS_HOST"`
Port int `env:"REDIS_PORT"`
Password string `env:"REDIS_PASSWORD"`
DB int `env:"REDIS_DB"`
PoolSize int `env:"REDIS_POOL_SIZE"`
}
type LoggingConfig struct {
Level string `env:"LOG_LEVEL"`
Format string `env:"LOG_FORMAT"`
Output string `env:"LOG_OUTPUT"`
File LogFileConfig
}
type LogFileConfig struct {
Path string `env:"LOG_FILE_PATH"`
MaxSize int `env:"LOG_FILE_MAX_SIZE"`
MaxBackups int `env:"LOG_FILE_MAX_BACKUPS"`
MaxAge int `env:"LOG_FILE_MAX_AGE"`
Compress bool `env:"LOG_FILE_COMPRESS"`
}
type AssetsConfig struct {
FlagsPath string `env:"ASSETS_FLAGS_PATH"`
}
type RateLimitConfig struct {
RequestsPerMinute int `env:"RATE_LIMIT_REQUESTS_PER_MINUTE"`
Burst int `env:"RATE_LIMIT_BURST"`
}
type NotificationConfig struct {
BaseURL string `env:"NOTIFICATION_BASE_URL"`
Timeout time.Duration `env:"NOTIFICATION_TIMEOUT"`
RetryAttempts int `env:"NOTIFICATION_RETRY_ATTEMPTS"`
RetryDelay time.Duration `env:"NOTIFICATION_RETRY_DELAY"`
EnableLogging bool `env:"NOTIFICATION_ENABLE_LOGGING"`
UserAgent string `env:"NOTIFICATION_USER_AGENT"`
}
// OllamaConfig represents the configuration for the Ollama SDK
type OllamaConfig struct {
BaseURL string `env:"OLLAMA_BASE_URL" default:"http://localhost:11434"`
Timeout time.Duration `env:"OLLAMA_TIMEOUT" default:"300s"`
RetryAttempts int `env:"OLLAMA_RETRY_ATTEMPTS" default:"3"`
RetryDelay time.Duration `env:"OLLAMA_RETRY_DELAY" default:"2s"`
EnableLogging bool `env:"OLLAMA_ENABLE_LOGGING" default:"true"`
UserAgent string `env:"OLLAMA_USER_AGENT" default:"TenderManagement-OllamaSDK/1.0"`
DefaultModel string `env:"OLLAMA_DEFAULT_MODEL" default:"llama2"`
DefaultTemperature float64 `env:"OLLAMA_DEFAULT_TEMPERATURE" default:"0.7"`
DefaultTopP float64 `env:"OLLAMA_DEFAULT_TOP_P" default:"0.9"`
DefaultMaxTokens int `env:"OLLAMA_DEFAULT_MAX_TOKENS" default:"1000"`
EnableStreaming bool `env:"OLLAMA_ENABLE_STREAMING" default:"false"`
}
type HCaptchaConfig struct {
SecretKey string `env:"HCAPTCHA_SECRET_KEY"`
VerifyURL string `env:"HCAPTCHA_VERIFY_URL"`
Timeout time.Duration `env:"HCAPTCHA_TIMEOUT"`
}
type ScraperConfig struct {
BaseURL string `env:"SCRAPER_BASE_URL"`
Timeout time.Duration `env:"SCRAPER_TIMEOUT"`
}
type MinIOConfig struct {
Endpoint string `env:"MINIO_ENDPOINT" default:"localhost:9000"`
AccessKeyID string `env:"MINIO_ACCESS_KEY_ID" default:""`
SecretAccessKey string `env:"MINIO_SECRET_ACCESS_KEY" default:""`
UseSSL bool `env:"MINIO_USE_SSL" default:"false"`
Region string `env:"MINIO_REGION" default:"us-east-1"`
DefaultBucket string `env:"MINIO_DEFAULT_BUCKET" default:"files"`
HierarchicalBucket string `env:"MINIO_HIERARCHICAL_BUCKET" default:"documents"`
}
// LoadConfig loads configuration with priority: OS environment variables > .env file
// OS environment variables take precedence over .env file values
func LoadConfig[T any](path string, config T) (T, error) {
// First, try to load .env file (this will not override existing OS env vars)
envPath := fmt.Sprintf("%s/.env", path)
// Check if .env file exists before trying to load it
if _, err := os.Stat(envPath); err == nil {
// Load .env file - gotenv.Load respects existing environment variables
// and will not override them with values from the .env file
if err := gotenv.Load(envPath); err != nil {
return config, fmt.Errorf("failed to load .env file from %s: %w", envPath, err)
}
}
// If .env file doesn't exist, we continue with OS environment variables only
// Parse configuration using reflection
// This will use OS env vars first, then fall back to values loaded from .env
if err := parseConfig(config); err != nil {
return config, fmt.Errorf("failed to parse configuration: %w", err)
}
return config, nil
}
// parseConfig uses reflection to parse environment variables into the config struct
// Environment variables are read with the following priority:
// 1. OS environment variables (highest priority)
// 2. Values from .env file (loaded by gotenv.Load, lower priority)
func parseConfig(config interface{}) error {
v := reflect.ValueOf(config)
if v.Kind() != reflect.Ptr {
return fmt.Errorf("config must be a pointer")
}
v = v.Elem()
if v.Kind() != reflect.Struct {
return fmt.Errorf("config must be a struct")
}
return parseStruct(v)
}
// parseStruct recursively parses struct fields
func parseStruct(v reflect.Value) error {
t := v.Type()
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
fieldType := t.Field(i)
// Skip unexported fields
if !field.CanSet() {
continue
}
// Get env tag
envTag := fieldType.Tag.Get("env")
if envTag == "" {
// If no env tag, try to parse nested struct
if field.Kind() == reflect.Struct {
if err := parseStruct(field); err != nil {
return err
}
}
continue
}
// Get environment variable value
// os.Getenv will return OS env vars first, then values loaded from .env
envValue := os.Getenv(envTag)
if envValue == "" {
continue
}
// Parse and set the field value
if err := setFieldValue(field, envValue); err != nil {
return fmt.Errorf("failed to set field %s (env: %s): %w", fieldType.Name, envTag, err)
}
}
return nil
}
// setFieldValue sets the field value from environment variable string
func setFieldValue(field reflect.Value, value string) error {
switch field.Kind() {
case reflect.String:
field.SetString(value)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if field.Type() == reflect.TypeOf(time.Duration(0)) {
// Handle time.Duration
duration, err := time.ParseDuration(value)
if err != nil {
return fmt.Errorf("invalid duration format: %s", value)
}
field.Set(reflect.ValueOf(duration))
} else {
// Handle regular integers
intValue, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return fmt.Errorf("invalid integer format: %s", value)
}
field.SetInt(intValue)
}
case reflect.Bool:
boolValue, err := strconv.ParseBool(value)
if err != nil {
return fmt.Errorf("invalid boolean format: %s", value)
}
field.SetBool(boolValue)
case reflect.Float32, reflect.Float64:
floatValue, err := strconv.ParseFloat(value, 64)
if err != nil {
return fmt.Errorf("invalid float format: %s", value)
}
field.SetFloat(floatValue)
default:
return fmt.Errorf("unsupported field type: %s", field.Kind())
}
return nil
}
// GetEnvWithPriority gets environment variable with explicit priority handling
// Returns the value and a boolean indicating the source (true = OS env, false = .env file)
func GetEnvWithPriority(key string) (string, bool) {
// Check if the value exists in OS environment
if value, exists := os.LookupEnv(key); exists {
return value, true // From OS environment
}
// If not in OS environment, it might be from .env file
if value := os.Getenv(key); value != "" {
return value, false // From .env file
}
return "", false
}
// Helper function to get environment variable with default value
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
// Helper function to get environment variable as int with default value
func getEnvAsInt(key string, defaultValue int) int {
if value := os.Getenv(key); value != "" {
if intValue, err := strconv.Atoi(value); err == nil {
return intValue
}
}
return defaultValue
}
// Helper function to get environment variable as duration with default value
func getEnvAsDuration(key string, defaultValue time.Duration) time.Duration {
if value := os.Getenv(key); value != "" {
if duration, err := time.ParseDuration(value); err == nil {
return duration
}
}
return defaultValue
}
// Helper function to get environment variable as bool with default value
func getEnvAsBool(key string, defaultValue bool) bool {
if value := os.Getenv(key); value != "" {
if boolValue, err := strconv.ParseBool(value); err == nil {
return boolValue
}
}
return defaultValue
}