package main import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/gin-gonic/gin" "go.uber.org/zap" "github.com/RyanCopley/skybridge/user/internal/config" "github.com/RyanCopley/skybridge/user/internal/database" "github.com/RyanCopley/skybridge/user/internal/handlers" "github.com/RyanCopley/skybridge/user/internal/middleware" "github.com/RyanCopley/skybridge/user/internal/repository/postgres" "github.com/RyanCopley/skybridge/user/internal/services" ) func main() { // Initialize configuration cfg := config.NewConfig() if err := cfg.Validate(); err != nil { log.Fatal("Configuration validation failed:", err) } // Initialize logger logger := initLogger(cfg) defer logger.Sync() logger.Info("Starting User Management Service", zap.String("version", cfg.GetString("APP_VERSION")), zap.String("environment", cfg.GetString("APP_ENV")), ) // Initialize database logger.Info("Connecting to database", zap.String("dsn", cfg.GetDatabaseDSNForLogging())) db, err := database.NewPostgresProvider( cfg.GetDatabaseDSN(), cfg.GetInt("DB_MAX_OPEN_CONNS"), cfg.GetInt("DB_MAX_IDLE_CONNS"), cfg.GetString("DB_CONN_MAX_LIFETIME"), ) if err != nil { logger.Fatal("Failed to initialize database", zap.String("dsn", cfg.GetDatabaseDSNForLogging()), zap.Error(err)) } logger.Info("Database connection established successfully") // Initialize repositories userRepo := postgres.NewUserRepository(db) profileRepo := postgres.NewUserProfileRepository(db) // Initialize services userService := services.NewUserService(userRepo, profileRepo, nil, logger) // Initialize handlers healthHandler := handlers.NewHealthHandler(db, logger) userHandler := handlers.NewUserHandler(userService, logger) // Set up router router := setupRouter(cfg, logger, healthHandler, userHandler) // Create HTTP server srv := &http.Server{ Addr: cfg.GetServerAddress(), Handler: router, ReadTimeout: cfg.GetDuration("SERVER_READ_TIMEOUT"), WriteTimeout: cfg.GetDuration("SERVER_WRITE_TIMEOUT"), IdleTimeout: cfg.GetDuration("SERVER_IDLE_TIMEOUT"), } // Start server in goroutine go func() { logger.Info("Starting HTTP server", zap.String("address", srv.Addr)) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { logger.Fatal("Failed to start server", zap.Error(err)) } }() // Wait for interrupt signal to gracefully shutdown the server quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit logger.Info("Shutting down server...") // Give outstanding requests time to complete ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Shutdown server if err := srv.Shutdown(ctx); err != nil { logger.Error("Server forced to shutdown", zap.Error(err)) } logger.Info("Server exited") } func initLogger(cfg config.ConfigProvider) *zap.Logger { var logger *zap.Logger var err error if cfg.IsProduction() { logger, err = zap.NewProduction() } else { logger, err = zap.NewDevelopment() } if err != nil { log.Fatal("Failed to initialize logger:", err) } return logger } func setupRouter(cfg config.ConfigProvider, logger *zap.Logger, healthHandler *handlers.HealthHandler, userHandler *handlers.UserHandler) *gin.Engine { // Set Gin mode based on environment if cfg.IsProduction() { gin.SetMode(gin.ReleaseMode) } router := gin.New() // Add middleware router.Use(middleware.Logger(logger)) router.Use(middleware.Recovery(logger)) router.Use(middleware.CORS()) // Health check endpoints (no authentication required) router.GET("/health", healthHandler.Health) router.GET("/ready", healthHandler.Ready) // API routes api := router.Group("/api") { // Protected routes (require authentication) protected := api.Group("/") protected.Use(middleware.Authentication(cfg, logger)) { // User management protected.GET("/users", userHandler.List) protected.POST("/users", userHandler.Create) protected.GET("/users/:id", userHandler.GetByID) protected.PUT("/users/:id", userHandler.Update) protected.DELETE("/users/:id", userHandler.Delete) // User lookup endpoints protected.GET("/users/email/:email", userHandler.GetByEmail) protected.GET("/users/exists/:email", userHandler.ExistsByEmail) // Documentation endpoint protected.GET("/docs", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "service": "User Management Service", "version": cfg.GetString("APP_VERSION"), "documentation": "User service API endpoints", "endpoints": map[string]interface{}{ "users": []string{ "GET /api/users", "POST /api/users", "GET /api/users/:id", "PUT /api/users/:id", "DELETE /api/users/:id", "GET /api/users/email/:email", "GET /api/users/exists/:email", }, }, }) }) } } return router }