241e3b5f2d
- Introduced a new `auditlog` package to handle audit logging for user actions, including creation, updates, deletions, and authentication events. - Enhanced existing services (customer, user) to log relevant actions using the new audit logger, capturing details such as actor ID, action type, target type, and success status. - Added middleware to enrich request context with metadata for audit logging, ensuring comprehensive tracking of user interactions. - Integrated Elasticsearch for persistent storage of audit logs, with fallback to file-only logging if Elasticsearch is unavailable. - Updated API documentation to include new audit log endpoints for administrative access. This update significantly improves the system's ability to track and audit user actions, enhancing security and accountability within the application.
301 lines
9.4 KiB
Go
301 lines
9.4 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"`
|
|
}
|
|
|
|
type ElasticsearchConfig struct {
|
|
URL string `env:"ELASTICSEARCH_URL"`
|
|
Username string `env:"ELASTICSEARCH_USERNAME"`
|
|
Password string `env:"ELASTICSEARCH_PASSWORD"`
|
|
IndexPrefix string `env:"ELASTICSEARCH_INDEX_PREFIX"`
|
|
Enabled bool `env:"ELASTICSEARCH_ENABLED"`
|
|
RequestTimeout time.Duration `env:"ELASTICSEARCH_REQUEST_TIMEOUT"`
|
|
BulkFlushPeriod time.Duration `env:"ELASTICSEARCH_BULK_FLUSH_PERIOD"`
|
|
QueueSize int `env:"ELASTICSEARCH_QUEUE_SIZE"`
|
|
}
|
|
|
|
// 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
|
|
}
|