290 lines
8.9 KiB
Go
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
|
|
}
|