package main import ( "context" "fmt" "log" "net/http" _ "net/http/pprof" // MOD-P2-006: Activer pprof pour profiling "os" "os/signal" "syscall" "time" "github.com/getsentry/sentry-go" "github.com/gin-gonic/gin" "github.com/joho/godotenv" "go.uber.org/zap" "veza-backend-api/internal/api" "veza-backend-api/internal/config" "veza-backend-api/internal/metrics" _ "veza-backend-api/docs" // Import docs for swagger ) // @title Veza Backend API // @version 1.2.0 // @description Backend API for Veza platform. // @termsOfService http://swagger.io/terms/ // @contact.name API Support // @contact.url http://www.veza.app/support // @contact.email support@veza.app // @license.name Apache 2.0 // @license.url http://www.apache.org/licenses/LICENSE-2.0.html // @host localhost:8080 // @BasePath /api/v1 // @securityDefinitions.apikey BearerAuth // @in header // @name Authorization func main() { // Charger les variables d'environnement if err := godotenv.Load(); err != nil { log.Printf("ℹ️ Note: Fichier .env non trouvé, utilisation des variables d'environnement système") } // Configuration du logger logger, err := zap.NewProduction() if err != nil { log.Fatalf("Impossible d'initialiser le logger: %v", err) } defer logger.Sync() logger.Info("🚀 Démarrage de Veza Backend API") // Charger la configuration cfg, err := config.NewConfig() if err != nil { logger.Fatal("❌ Impossible de charger la configuration", zap.Error(err)) } // Valider la configuration if err := cfg.Validate(); err != nil { logger.Fatal("❌ Configuration invalide", zap.Error(err)) } // Initialiser Sentry si DSN configuré if cfg.SentryDsn != "" { err := sentry.Init(sentry.ClientOptions{ Dsn: cfg.SentryDsn, Environment: cfg.SentryEnvironment, TracesSampleRate: cfg.SentrySampleRateTransactions, SampleRate: cfg.SentrySampleRateErrors, // AttachStacktrace pour capturer les stack traces AttachStacktrace: true, }) if err != nil { logger.Warn("❌ Impossible d'initialiser Sentry", zap.Error(err)) } else { logger.Info("✅ Sentry initialisé", zap.String("environment", cfg.SentryEnvironment)) } // Flush les événements Sentry avant shutdown defer sentry.Flush(2 * time.Second) } else { logger.Info("ℹ️ Sentry non configuré (SENTRY_DSN non défini)") } // Initialisation de la base de données db := cfg.Database if db == nil { logger.Fatal("❌ Base de données non initialisée") } defer db.Close() if err := db.Initialize(); err != nil { logger.Fatal("❌ Impossible d'initialiser la base de données", zap.Error(err)) } // MOD-P2-004: Démarrer le collecteur de métriques DB pool // Collecte les stats DB pool toutes les 10 secondes et les expose via Prometheus metrics.StartDBPoolStatsCollector(db.DB, 10*time.Second) logger.Info("✅ Collecteur de métriques DB pool démarré") // Fail-Fast: Vérifier RabbitMQ si activé if cfg.RabbitMQEnable { if cfg.RabbitMQEventBus == nil { logger.Fatal("❌ RabbitMQ activé (RABBITMQ_ENABLE=true) mais non initialisé (problème de connexion?)") } else { // Optionnel: Check connection status if RabbitMQEventBus exposes it // For now, assume if initialized it's connected or retrying. // If we want STRICT fail fast, we would need to verify connection is Open here. logger.Info("✅ RabbitMQ actif") } } else { logger.Info("ℹ️ RabbitMQ désactivé") } // Démarrer le Job Worker if cfg.JobWorker != nil { workerCtx, workerCancel := context.WithCancel(context.Background()) defer workerCancel() cfg.JobWorker.Start(workerCtx) logger.Info("✅ Job Worker démarré") } else { logger.Warn("⚠️ Job Worker non initialisé") } // Configuration du mode Gin // Correction: Utilisation directe de la variable d'env car non exposée dans Config appEnv := os.Getenv("APP_ENV") if appEnv == "production" { gin.SetMode(gin.ReleaseMode) } else { gin.SetMode(gin.DebugMode) } // Créer le router Gin router := gin.New() // Middleware globaux (Logger, Recovery) recommandés par ORIGIN router.Use(gin.Logger(), gin.Recovery()) // Configuration des routes apiRouter := api.NewAPIRouter(db, cfg) // Instantiate APIRouter apiRouter.Setup(router) // Call its Setup method // Configuration du serveur HTTP port := fmt.Sprintf("%d", cfg.AppPort) if cfg.AppPort == 0 { port = "8080" } server := &http.Server{ Addr: fmt.Sprintf(":%s", port), Handler: router, ReadTimeout: 30 * time.Second, // Standards ORIGIN WriteTimeout: 30 * time.Second, } // Gestion de l'arrêt gracieux quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) go func() { logger.Info("🌐 Serveur HTTP démarré", zap.String("port", port)) if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { logger.Fatal("❌ Erreur du serveur HTTP", zap.Error(err)) } }() <-quit logger.Info("🔄 Arrêt du serveur...") ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := server.Shutdown(ctx); err != nil { logger.Error("❌ Erreur lors de l'arrêt", zap.Error(err)) } else { logger.Info("✅ Serveur arrêté proprement") } }