package config import ( "encoding/json" "os" "path/filepath" "sync" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) // Environment represents the application environment type type Environment string const ( // EnvDevelopment represents development environment EnvDevelopment Environment = "development" // EnvProduction represents production environment EnvProduction Environment = "production" ) // Config holds all configuration for the application type Config struct { // App settings AppName string `json:"app_name"` Version string `json:"version"` Environment Environment `json:"environment"` // Database settings (SQLite for app data) Database DatabaseConfig `json:"database"` // Encryption settings Encryption EncryptionConfig `json:"encryption"` // Logger settings Logger LoggerConfig `json:"logger"` // API settings (for debug HTTP server) API APIConfig `json:"api"` // File paths DataDir string `json:"-"` } // DatabaseConfig holds database configuration type DatabaseConfig struct { // SQLite database file path for app data SQLitePath string `json:"sqlite_path"` // Max open connections MaxOpenConns int `json:"max_open_conns"` // Max idle connections MaxIdleConns int `json:"max_idle_conns"` // Connection max lifetime in minutes MaxLifetime int `json:"max_lifetime"` } // EncryptionConfig holds encryption configuration type EncryptionConfig struct { // Key for encrypting sensitive data (passwords, etc.) // In production, this should be loaded from secure storage Key string `json:"-"` // KeyFile path to load encryption key from KeyFile string `json:"key_file"` } // LoggerConfig holds logger configuration type LoggerConfig struct { // Log level: debug, info, warn, error Level string `json:"level"` // Log format: json, console Format string `json:"format"` // Output file path (empty for stdout) OutputPath string `json:"output_path"` } // APIConfig holds HTTP API configuration type APIConfig struct { // Enable HTTP API server (for debugging) Enabled bool `json:"enabled"` // Port for HTTP API server Port string `json:"port"` } var ( instance *Config once sync.Once logger *zap.Logger ) // Get returns the singleton config instance func Get() *Config { return instance } // GetLogger returns the zap logger func GetLogger() *zap.Logger { return logger } // Init initializes the configuration // If config file doesn't exist, creates default config func Init(dataDir string) (*Config, error) { var err error once.Do(func() { instance = &Config{ DataDir: dataDir, } err = instance.load(dataDir) }) return instance, err } // load loads configuration from file or creates default func (c *Config) load(dataDir string) error { configPath := filepath.Join(dataDir, "config.json") // Try to load existing config if _, err := os.Stat(configPath); err == nil { data, err := os.ReadFile(configPath) if err != nil { return err } if err := json.Unmarshal(data, c); err != nil { return err } } else { // Create default config c.setDefaults() if err := c.save(configPath); err != nil { return err } } // Override with environment variables c.loadEnv() // Initialize logger if err := c.initLogger(); err != nil { return err } logger.Info("configuration loaded", zap.String("environment", string(c.Environment)), zap.String("data_dir", c.DataDir), ) return nil } // setDefaults sets default configuration values func (c *Config) setDefaults() { c.AppName = "uzdb" c.Version = "1.0.0" c.Environment = EnvDevelopment c.Database = DatabaseConfig{ SQLitePath: filepath.Join(c.DataDir, "uzdb.db"), MaxOpenConns: 25, MaxIdleConns: 5, MaxLifetime: 5, } c.Encryption = EncryptionConfig{ Key: "", // Will be generated if empty KeyFile: filepath.Join(c.DataDir, "encryption.key"), } c.Logger = LoggerConfig{ Level: "debug", Format: "console", OutputPath: "", } c.API = APIConfig{ Enabled: true, Port: "8080", } } // loadEnv loads configuration from environment variables func (c *Config) loadEnv() { if env := os.Getenv("UZDB_ENV"); env != "" { c.Environment = Environment(env) } if port := os.Getenv("UZDB_API_PORT"); port != "" { c.API.Port = port } if logLevel := os.Getenv("UZDB_LOG_LEVEL"); logLevel != "" { c.Logger.Level = logLevel } if dbPath := os.Getenv("UZDB_DB_PATH"); dbPath != "" { c.Database.SQLitePath = dbPath } } // save saves configuration to file func (c *Config) save(path string) error { data, err := json.MarshalIndent(c, "", " ") if err != nil { return err } return os.WriteFile(path, data, 0600) } // initLogger initializes the zap logger func (c *Config) initLogger() error { var cfg zap.Config switch c.Logger.Format { case "json": cfg = zap.NewProductionConfig() default: cfg = zap.NewDevelopmentConfig() } // Set log level level, parseErr := zapcore.ParseLevel(c.Logger.Level) if parseErr != nil { level = zapcore.InfoLevel } cfg.Level.SetLevel(level) // Configure output if c.Logger.OutputPath != "" { cfg.OutputPaths = []string{c.Logger.OutputPath} cfg.ErrorOutputPaths = []string{c.Logger.OutputPath} } var buildErr error logger, buildErr = cfg.Build( zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel), ) if buildErr != nil { return buildErr } return nil } // IsDevelopment returns true if running in development mode func (c *Config) IsDevelopment() bool { return c.Environment == EnvDevelopment } // IsProduction returns true if running in production mode func (c *Config) IsProduction() bool { return c.Environment == EnvProduction } // Sync flushes any buffered log entries func Sync() error { if logger != nil { return logger.Sync() } return nil }