From fde01b4cef006acc13fd81cca1a77be41afd95b4 Mon Sep 17 00:00:00 2001 From: Philippe Caseiro Date: Mon, 13 Oct 2025 11:07:53 +0200 Subject: [PATCH] feat(refactor): moving configuration and api to proper packages --- main.go => cmd/api/realz.go | 98 ++++++++++++------------------------- internal/config/config.go | 67 +++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 67 deletions(-) rename main.go => cmd/api/realz.go (65%) create mode 100644 internal/config/config.go diff --git a/main.go b/cmd/api/realz.go similarity index 65% rename from main.go rename to cmd/api/realz.go index 93ad434..25452d3 100644 --- a/main.go +++ b/cmd/api/realz.go @@ -1,33 +1,17 @@ -package main +package api import ( "database/sql" "fmt" "log" "net/http" - "os" - "strings" + + "forge.cadoles.com/LaCanne/realZ/internal/config" "github.com/gin-gonic/gin" _ "github.com/lib/pq" // Driver PostgreSQL - "github.com/spf13/viper" ) -// Config struct to hold all configuration for our application -type Config struct { - Database struct { - User string `mapstructure:"user"` - Password string `mapstructure:"password"` - Dbname string `mapstructure:"dbname"` - Host string `mapstructure:"host"` - Port int `mapstructure:"port"` - Sslmode string `mapstructure:"sslmode"` - } `mapstructure:"database"` - Server struct { - Address string `mapstructure:"address"` - } `mapstructure:"server"` -} - // GpsCoord représente les coordonnées GPS reçues en entrée. // Les noms de champs commencent par une majuscule pour être exportables // et donc accessibles par le décodeur JSON de Gin. @@ -132,57 +116,35 @@ func getCorrectedAltitude(db *sql.DB) gin.HandlerFunc { } } -func main() { - // --- Chargement de la configuration --- - // 0. Configuration pour les variables d'environnement - viper.SetEnvPrefix("APP") // Les variables devront commencer par "APP_" (ex: APP_SERVER_ADDRESS) - - // 1. Fichier de configuration de base - viper.SetConfigName("config") // config.yaml - viper.SetConfigType("yaml") - viper.AddConfigPath(".") - - // Lecture du fichier de base. On ne s'arrête pas s'il n'est pas trouvé. - if err := viper.ReadInConfig(); err != nil { - if _, ok := err.(viper.ConfigFileNotFoundError); !ok { - log.Fatalf("Erreur lors de la lecture du fichier de base config.yaml: %s", err) - } - } - - // 2. Surcharge par environnement (via la variable APP_ENV) - if env := os.Getenv("APP_ENV"); env != "" { - viper.SetConfigName("config." + env) // ex: config.prod.yaml - // MergeInConfig fusionne la nouvelle configuration avec celle déjà chargée. - // Les valeurs du nouveau fichier écrasent les anciennes. - if err := viper.MergeInConfig(); err != nil { - log.Printf("Avertissement : impossible de charger le fichier de configuration pour l'environnement '%s': %v", env, err) - } - } - - // 2a. Indiquer à Viper de lire les variables d'environnement APRÈS avoir lu les fichiers. - viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) // Remplace les "." par des "_" (ex: server.address -> SERVER_ADDRESS) - viper.AutomaticEnv() - - // 3. "Unmarshal" de la configuration finale dans la struct - var config Config - if err := viper.Unmarshal(&config); err != nil { - log.Fatalf("Impossible de décoder la configuration dans la structure : %v", err) - } - // --- Fin du chargement de la configuration --- - - // Chaîne de connexion à votre base de données PostgreSQL. - // Construite à partir de la configuration. +// initDB initializes and returns a database connection. +func initDB(cfg *config.DatabaseConfig) (*sql.DB, error) { connStr := fmt.Sprintf("user=%s password=%s dbname=%s host=%s port=%d sslmode=%s", - config.Database.User, config.Database.Password, config.Database.Dbname, - config.Database.Host, config.Database.Port, config.Database.Sslmode, + cfg.User, cfg.Password, cfg.Dbname, + cfg.Host, cfg.Port, cfg.Sslmode, ) - // Connexion à la base de données db, err := sql.Open("postgres", connStr) if err != nil { - log.Fatal("Impossible de se connecter à la base de données:", err) - } else { - log.Println("Connecté à la base de données PostgreSQL") + return nil, fmt.Errorf("could not open database connection: %w", err) + } + + if err = db.Ping(); err != nil { + return nil, fmt.Errorf("could not ping database: %w", err) + } + + log.Println("Connected to PostgreSQL database") + return db, nil +} + +func main() { + cfg, err := config.Load() + if err != nil { + log.Fatalf("Error loading configuration: %v", err) + } + + db, err := initDB(&cfg.Database) + if err != nil { + log.Fatalf("Could not initialize database: %v", err) } defer db.Close() @@ -197,6 +159,8 @@ func main() { // Nouvelle route pour obtenir l'altitude corrigée. router.POST("/getcorrectedaltitude", getCorrectedAltitude(db)) - log.Printf("Démarrage du serveur sur %s", config.Server.Address) - router.Run(config.Server.Address) + log.Printf("Starting server on %s", cfg.Server.Address) + if err := router.Run(cfg.Server.Address); err != nil { + log.Fatalf("Failed to run server: %v", err) + } } diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..e8560e0 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,67 @@ +package config + +import ( + "fmt" + "log" + "os" + "strings" + + "github.com/spf13/viper" +) + +// Config holds all configuration for the application. +type Config struct { + Database DatabaseConfig `mapstructure:"database"` + Server ServerConfig `mapstructure:"server"` +} + +// DatabaseConfig holds database connection configuration. +type DatabaseConfig struct { + User string `mapstructure:"user"` + Password string `mapstructure:"password"` + Dbname string `mapstructure:"dbname"` + Host string `mapstructure:"host"` + Port int `mapstructure:"port"` + Sslmode string `mapstructure:"sslmode"` +} + +// ServerConfig holds server-specific configuration. +type ServerConfig struct { + Address string `mapstructure:"address"` +} + +// Load reads configuration from file and environment variables. +func Load() (*Config, error) { + // 0. Configuration for environment variables + viper.SetEnvPrefix("APP") // Variables must start with "APP_" (e.g., APP_SERVER_ADDRESS) + + // 1. Base configuration file + viper.SetConfigName("config") // config.yaml + viper.SetConfigType("yaml") + viper.AddConfigPath(".") + + // Read the base file. Don't stop if it's not found. + if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); !ok { + return nil, fmt.Errorf("error reading base config file config.yaml: %w", err) + } + } + + // 2. Override with environment-specific file (via APP_ENV) + if env := os.Getenv("APP_ENV"); env != "" { + viper.SetConfigName("config." + env) // e.g., config.prod.yaml + if err := viper.MergeInConfig(); err != nil { + log.Printf("Warning: could not load config file for environment '%s': %v", env, err) + } + } + + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.AutomaticEnv() + + var cfg Config + if err := viper.Unmarshal(&cfg); err != nil { + return nil, fmt.Errorf("unable to decode config into struct: %w", err) + } + + return &cfg, nil +}