first commit
This commit is contained in:
@@ -0,0 +1,384 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Config holds all application configuration
|
||||
type Config struct {
|
||||
Server ServerConfig `mapstructure:"server"`
|
||||
Database DatabaseConfig `mapstructure:"database"`
|
||||
App AppConfig `mapstructure:"app"`
|
||||
Security SecurityConfig `mapstructure:"security"`
|
||||
Logging LoggingConfig `mapstructure:"logging"`
|
||||
External ExternalConfig `mapstructure:"external_services"`
|
||||
Features FeatureConfig `mapstructure:"features"`
|
||||
Defaults DefaultConfig `mapstructure:"defaults"`
|
||||
}
|
||||
|
||||
// ServerConfig holds server-related configuration
|
||||
type ServerConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
ReadTimeout time.Duration `mapstructure:"read_timeout"`
|
||||
WriteTimeout time.Duration `mapstructure:"write_timeout"`
|
||||
IdleTimeout time.Duration `mapstructure:"idle_timeout"`
|
||||
ShutdownTimeout time.Duration `mapstructure:"shutdown_timeout"`
|
||||
}
|
||||
|
||||
// DatabaseConfig holds database-related configuration
|
||||
type DatabaseConfig struct {
|
||||
Path string `mapstructure:"path"`
|
||||
ConnectionPool DatabaseConnectionConfig `mapstructure:"connection_pool"`
|
||||
Logging DatabaseLoggingConfig `mapstructure:"logging"`
|
||||
Migration DatabaseMigrationConfig `mapstructure:"migration"`
|
||||
Performance DatabasePerformanceConfig `mapstructure:"performance"`
|
||||
}
|
||||
|
||||
// DatabaseConnectionConfig holds database connection pool settings
|
||||
type DatabaseConnectionConfig struct {
|
||||
MaxIdleConnections int `mapstructure:"max_idle_connections"`
|
||||
MaxOpenConnections int `mapstructure:"max_open_connections"`
|
||||
ConnectionMaxLifetime time.Duration `mapstructure:"connection_max_lifetime"`
|
||||
ConnectionMaxIdleTime time.Duration `mapstructure:"connection_max_idle_time"`
|
||||
}
|
||||
|
||||
// DatabaseLoggingConfig holds database logging settings
|
||||
type DatabaseLoggingConfig struct {
|
||||
Level string `mapstructure:"level"`
|
||||
SlowQueryThreshold time.Duration `mapstructure:"slow_query_threshold"`
|
||||
Debug bool `mapstructure:"debug"`
|
||||
}
|
||||
|
||||
// DatabaseMigrationConfig holds database migration settings
|
||||
type DatabaseMigrationConfig struct {
|
||||
AutoMigrate bool `mapstructure:"auto_migrate"`
|
||||
DropTablesFirst bool `mapstructure:"drop_tables_first"`
|
||||
CreateBatchSize int `mapstructure:"create_batch_size"`
|
||||
}
|
||||
|
||||
// DatabasePerformanceConfig holds database performance settings
|
||||
type DatabasePerformanceConfig struct {
|
||||
PrepareStatements bool `mapstructure:"prepare_statements"`
|
||||
DisableForeignKeyCheck bool `mapstructure:"disable_foreign_key_check"`
|
||||
IgnoreRelationshipsWhenMigrating bool `mapstructure:"ignore_relationships_when_migrating"`
|
||||
QueryFields bool `mapstructure:"query_fields"`
|
||||
DryRun bool `mapstructure:"dry_run"`
|
||||
CreateInBatches int `mapstructure:"create_in_batches"`
|
||||
}
|
||||
|
||||
// AppConfig holds general application settings
|
||||
type AppConfig struct {
|
||||
Name string `mapstructure:"name"`
|
||||
Version string `mapstructure:"version"`
|
||||
Environment string `mapstructure:"environment"`
|
||||
Debug bool `mapstructure:"debug"`
|
||||
}
|
||||
|
||||
// SecurityConfig holds security-related settings
|
||||
type SecurityConfig struct {
|
||||
Session SessionConfig `mapstructure:"session"`
|
||||
Password PasswordConfig `mapstructure:"password"`
|
||||
}
|
||||
|
||||
// SessionConfig holds session management settings
|
||||
type SessionConfig struct {
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
CookieName string `mapstructure:"cookie_name"`
|
||||
SecureCookies bool `mapstructure:"secure_cookies"`
|
||||
HttpOnly bool `mapstructure:"http_only"`
|
||||
}
|
||||
|
||||
// PasswordConfig holds password requirements
|
||||
type PasswordConfig struct {
|
||||
MinLength int `mapstructure:"min_length"`
|
||||
RequireUppercase bool `mapstructure:"require_uppercase"`
|
||||
RequireLowercase bool `mapstructure:"require_lowercase"`
|
||||
RequireNumbers bool `mapstructure:"require_numbers"`
|
||||
RequireSpecialChars bool `mapstructure:"require_special_chars"`
|
||||
}
|
||||
|
||||
// LoggingConfig holds logging settings
|
||||
type LoggingConfig struct {
|
||||
Level string `mapstructure:"level"`
|
||||
Format string `mapstructure:"format"`
|
||||
Output string `mapstructure:"output"`
|
||||
FilePath string `mapstructure:"file_path"`
|
||||
Rotation LogRotationConfig `mapstructure:"rotation"`
|
||||
}
|
||||
|
||||
// LogRotationConfig holds log rotation settings
|
||||
type LogRotationConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
MaxSize string `mapstructure:"max_size"`
|
||||
MaxAge string `mapstructure:"max_age"`
|
||||
MaxBackups int `mapstructure:"max_backups"`
|
||||
}
|
||||
|
||||
// ExternalConfig holds external service configurations
|
||||
type ExternalConfig struct {
|
||||
OverpassAPI OverpassAPIConfig `mapstructure:"overpass_api"`
|
||||
}
|
||||
|
||||
// OverpassAPIConfig holds OpenStreetMap Overpass API settings
|
||||
type OverpassAPIConfig struct {
|
||||
URL string `mapstructure:"url"`
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
MaxRetries int `mapstructure:"max_retries"`
|
||||
SearchRadius int `mapstructure:"search_radius"`
|
||||
}
|
||||
|
||||
// FeatureConfig holds feature flag settings
|
||||
type FeatureConfig struct {
|
||||
FuelStationSearch bool `mapstructure:"fuel_station_search"`
|
||||
VehicleManagement bool `mapstructure:"vehicle_management"`
|
||||
StatisticsDashboard bool `mapstructure:"statistics_dashboard"`
|
||||
DataExport bool `mapstructure:"data_export"`
|
||||
APIEndpoints bool `mapstructure:"api_endpoints"`
|
||||
}
|
||||
|
||||
// DefaultConfig holds default values for new entities
|
||||
type DefaultConfig struct {
|
||||
Currency string `mapstructure:"currency"`
|
||||
FuelType string `mapstructure:"fuel_type"`
|
||||
DistanceUnit string `mapstructure:"distance_unit"`
|
||||
VolumeUnit string `mapstructure:"volume_unit"`
|
||||
}
|
||||
|
||||
// Load loads configuration from file, environment variables, and defaults
|
||||
func Load(configPath string) (*Config, error) {
|
||||
v := viper.New()
|
||||
|
||||
// Set defaults
|
||||
setDefaults(v)
|
||||
|
||||
// Configure Viper
|
||||
if configPath != "" {
|
||||
v.SetConfigFile(configPath)
|
||||
} else {
|
||||
v.SetConfigName("config")
|
||||
v.SetConfigType("yaml")
|
||||
v.AddConfigPath(".")
|
||||
v.AddConfigPath("./config")
|
||||
v.AddConfigPath("$HOME/.tankstopp")
|
||||
v.AddConfigPath("/etc/tankstopp")
|
||||
}
|
||||
|
||||
// Enable environment variable binding
|
||||
v.AutomaticEnv()
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
v.SetEnvPrefix("TANKSTOPP")
|
||||
|
||||
// Read config file
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
||||
return nil, fmt.Errorf("error reading config file: %w", err)
|
||||
}
|
||||
// Config file not found, continue with defaults and env vars
|
||||
}
|
||||
|
||||
// Unmarshal into struct
|
||||
var config Config
|
||||
if err := v.Unmarshal(&config); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling config: %w", err)
|
||||
}
|
||||
|
||||
// Validate configuration
|
||||
if err := config.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid configuration: %w", err)
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// setDefaults sets default values for configuration
|
||||
func setDefaults(v *viper.Viper) {
|
||||
// Server defaults
|
||||
v.SetDefault("server.host", "localhost")
|
||||
v.SetDefault("server.port", 8081)
|
||||
v.SetDefault("server.read_timeout", "30s")
|
||||
v.SetDefault("server.write_timeout", "30s")
|
||||
v.SetDefault("server.idle_timeout", "120s")
|
||||
v.SetDefault("server.shutdown_timeout", "10s")
|
||||
|
||||
// Database defaults
|
||||
v.SetDefault("database.path", "fuel_stops.db")
|
||||
v.SetDefault("database.connection_pool.max_idle_connections", 10)
|
||||
v.SetDefault("database.connection_pool.max_open_connections", 100)
|
||||
v.SetDefault("database.connection_pool.connection_max_lifetime", "1h")
|
||||
v.SetDefault("database.connection_pool.connection_max_idle_time", "30m")
|
||||
v.SetDefault("database.logging.level", "warn")
|
||||
v.SetDefault("database.logging.slow_query_threshold", "200ms")
|
||||
v.SetDefault("database.logging.debug", false)
|
||||
v.SetDefault("database.migration.auto_migrate", true)
|
||||
v.SetDefault("database.migration.drop_tables_first", false)
|
||||
v.SetDefault("database.migration.create_batch_size", 1000)
|
||||
v.SetDefault("database.performance.prepare_statements", true)
|
||||
v.SetDefault("database.performance.disable_foreign_key_check", false)
|
||||
v.SetDefault("database.performance.ignore_relationships_when_migrating", false)
|
||||
v.SetDefault("database.performance.query_fields", true)
|
||||
v.SetDefault("database.performance.dry_run", false)
|
||||
v.SetDefault("database.performance.create_in_batches", 100)
|
||||
|
||||
// App defaults
|
||||
v.SetDefault("app.name", "TankStopp")
|
||||
v.SetDefault("app.version", "1.0.0")
|
||||
v.SetDefault("app.environment", "development")
|
||||
v.SetDefault("app.debug", true)
|
||||
|
||||
// Security defaults
|
||||
v.SetDefault("security.session.timeout", "24h")
|
||||
v.SetDefault("security.session.cookie_name", "tankstopp_session")
|
||||
v.SetDefault("security.session.secure_cookies", false)
|
||||
v.SetDefault("security.session.http_only", true)
|
||||
v.SetDefault("security.password.min_length", 8)
|
||||
v.SetDefault("security.password.require_uppercase", true)
|
||||
v.SetDefault("security.password.require_lowercase", true)
|
||||
v.SetDefault("security.password.require_numbers", true)
|
||||
v.SetDefault("security.password.require_special_chars", false)
|
||||
|
||||
// Logging defaults
|
||||
v.SetDefault("logging.level", "info")
|
||||
v.SetDefault("logging.format", "text")
|
||||
v.SetDefault("logging.output", "stdout")
|
||||
v.SetDefault("logging.file_path", "logs/tankstopp.log")
|
||||
v.SetDefault("logging.rotation.enabled", false)
|
||||
v.SetDefault("logging.rotation.max_size", "100MB")
|
||||
v.SetDefault("logging.rotation.max_age", "30d")
|
||||
v.SetDefault("logging.rotation.max_backups", 5)
|
||||
|
||||
// External services defaults
|
||||
v.SetDefault("external_services.overpass_api.url", "https://overpass-api.de/api/interpreter")
|
||||
v.SetDefault("external_services.overpass_api.timeout", "30s")
|
||||
v.SetDefault("external_services.overpass_api.max_retries", 3)
|
||||
v.SetDefault("external_services.overpass_api.search_radius", 5000)
|
||||
|
||||
// Feature flags defaults
|
||||
v.SetDefault("features.fuel_station_search", true)
|
||||
v.SetDefault("features.vehicle_management", true)
|
||||
v.SetDefault("features.statistics_dashboard", true)
|
||||
v.SetDefault("features.data_export", true)
|
||||
v.SetDefault("features.api_endpoints", true)
|
||||
|
||||
// Default values
|
||||
v.SetDefault("defaults.currency", "EUR")
|
||||
v.SetDefault("defaults.fuel_type", "Super E5")
|
||||
v.SetDefault("defaults.distance_unit", "km")
|
||||
v.SetDefault("defaults.volume_unit", "liters")
|
||||
}
|
||||
|
||||
// Validate validates the configuration
|
||||
func (c *Config) Validate() error {
|
||||
// Validate server config
|
||||
if c.Server.Port < 1 || c.Server.Port > 65535 {
|
||||
return fmt.Errorf("invalid server port: %d", c.Server.Port)
|
||||
}
|
||||
|
||||
// Validate database config
|
||||
if c.Database.Path == "" {
|
||||
return fmt.Errorf("database path cannot be empty")
|
||||
}
|
||||
|
||||
if c.Database.ConnectionPool.MaxIdleConnections < 0 {
|
||||
return fmt.Errorf("max idle connections cannot be negative")
|
||||
}
|
||||
|
||||
if c.Database.ConnectionPool.MaxOpenConnections < 0 {
|
||||
return fmt.Errorf("max open connections cannot be negative")
|
||||
}
|
||||
|
||||
// Validate app config
|
||||
if c.App.Name == "" {
|
||||
return fmt.Errorf("app name cannot be empty")
|
||||
}
|
||||
|
||||
validEnvs := []string{"development", "production", "test"}
|
||||
if !contains(validEnvs, c.App.Environment) {
|
||||
return fmt.Errorf("invalid environment: %s (must be one of: %v)", c.App.Environment, validEnvs)
|
||||
}
|
||||
|
||||
// Validate security config
|
||||
if c.Security.Password.MinLength < 4 {
|
||||
return fmt.Errorf("minimum password length cannot be less than 4")
|
||||
}
|
||||
|
||||
// Validate logging config
|
||||
validLogLevels := []string{"debug", "info", "warn", "error"}
|
||||
if !contains(validLogLevels, c.Logging.Level) {
|
||||
return fmt.Errorf("invalid log level: %s (must be one of: %v)", c.Logging.Level, validLogLevels)
|
||||
}
|
||||
|
||||
validLogFormats := []string{"json", "text"}
|
||||
if !contains(validLogFormats, c.Logging.Format) {
|
||||
return fmt.Errorf("invalid log format: %s (must be one of: %v)", c.Logging.Format, validLogFormats)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsProduction returns true if the app is running in production environment
|
||||
func (c *Config) IsProduction() bool {
|
||||
return c.App.Environment == "production"
|
||||
}
|
||||
|
||||
// IsDevelopment returns true if the app is running in development environment
|
||||
func (c *Config) IsDevelopment() bool {
|
||||
return c.App.Environment == "development"
|
||||
}
|
||||
|
||||
// IsTest returns true if the app is running in test environment
|
||||
func (c *Config) IsTest() bool {
|
||||
return c.App.Environment == "test"
|
||||
}
|
||||
|
||||
// GetServerAddress returns the server address in host:port format
|
||||
func (c *Config) GetServerAddress() string {
|
||||
return fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port)
|
||||
}
|
||||
|
||||
// String returns a string representation of the configuration (without sensitive data)
|
||||
func (c *Config) String() string {
|
||||
return fmt.Sprintf(`TankStopp Configuration:
|
||||
Server: %s
|
||||
Database: %s
|
||||
Environment: %s
|
||||
Debug: %t
|
||||
Features: %+v`,
|
||||
c.GetServerAddress(),
|
||||
c.Database.Path,
|
||||
c.App.Environment,
|
||||
c.App.Debug,
|
||||
c.Features)
|
||||
}
|
||||
|
||||
// contains checks if a slice contains a string
|
||||
func contains(slice []string, item string) bool {
|
||||
for _, s := range slice {
|
||||
if s == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetConfigFromEnv returns environment-specific configuration
|
||||
func GetConfigFromEnv() string {
|
||||
if configPath := os.Getenv("TANKSTOPP_CONFIG_PATH"); configPath != "" {
|
||||
return configPath
|
||||
}
|
||||
|
||||
env := os.Getenv("TANKSTOPP_ENV")
|
||||
if env == "" {
|
||||
env = os.Getenv("ENV")
|
||||
}
|
||||
if env == "" {
|
||||
env = "development"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("config.%s.yaml", env)
|
||||
}
|
||||
Reference in New Issue
Block a user