Files
skybridge/user/cmd/server/main.go
2025-08-31 22:35:23 -04:00

182 lines
4.8 KiB
Go

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
}