package server import ( "context" "fmt" "net/http" "time" "attune-heart-therapy/internal/config" "attune-heart-therapy/internal/container" "attune-heart-therapy/internal/health" "attune-heart-therapy/internal/logger" "attune-heart-therapy/internal/middleware" "attune-heart-therapy/internal/services" "github.com/gin-gonic/gin" ) // MiddlewareContainer holds all middleware functions type MiddlewareContainer struct { jwtService services.JWTService } // Auth returns the authentication middleware func (m *MiddlewareContainer) Auth() gin.HandlerFunc { return middleware.AuthMiddleware(m.jwtService) } // RequireAdmin returns the admin authentication middleware func (m *MiddlewareContainer) RequireAdmin() gin.HandlerFunc { return middleware.RequireAdmin(m.jwtService) } // StrictRateLimit returns the strict rate limiting middleware func (m *MiddlewareContainer) StrictRateLimit() gin.HandlerFunc { return middleware.StrictRateLimitMiddleware() } type Server struct { config *config.Config container *container.Container router *gin.Engine middleware *MiddlewareContainer httpServer *http.Server healthChecker *health.Checker log *logger.Logger } func New(cfg *config.Config) *Server { // Set Gin mode based on environment gin.SetMode(gin.ReleaseMode) router := gin.New() // Configure middleware stack setupMiddlewareStack(router) // Initialize health checker healthChecker := health.NewChecker() // Initialize logger log := logger.New("server") return &Server{ config: cfg, router: router, healthChecker: healthChecker, log: log, } } // setupMiddlewareStack configures the Gin middleware stack func setupMiddlewareStack(router *gin.Engine) { // Tracing middleware - should be first to ensure trace ID is available router.Use(middleware.TracingMiddleware()) // Security middleware router.Use(middleware.SecurityMiddleware()) // CORS middleware for frontend integration router.Use(middleware.CORSMiddleware()) // Request logging middleware router.Use(middleware.StructuredLoggingMiddleware()) // Monitoring middleware for error tracking and metrics router.Use(middleware.HealthCheckMiddleware()) // Error handling and recovery middleware router.Use(middleware.ErrorHandlerMiddleware()) // Rate limiting middleware for API protection router.Use(middleware.RateLimitMiddleware()) // Handle 404 and 405 errors router.NoRoute(middleware.NotFoundHandler()) router.NoMethod(middleware.MethodNotAllowedHandler()) } // SetContainer sets the dependency injection container func (s *Server) SetContainer(container *container.Container) { s.container = container } // Initialize sets up all application dependencies func (s *Server) Initialize() error { s.log.Info("Initializing server...") if s.container == nil { return fmt.Errorf("container not set - call SetContainer first") } // Initialize middleware container with JWT service s.middleware = &MiddlewareContainer{ jwtService: s.container.GetJWTService(), } // Register health checks s.registerHealthChecks() s.log.Info("Server initialization completed successfully") return nil } func (s *Server) Start() error { // Initialize server components if err := s.Initialize(); err != nil { return err } // Setup routes s.setupRoutes() // Create HTTP server with proper configuration addr := fmt.Sprintf("%s:%s", s.config.Server.Host, s.config.Server.Port) s.httpServer = &http.Server{ Addr: addr, Handler: s.router, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 120 * time.Second, } s.log.Info("Starting server", map[string]interface{}{ "address": addr, }) return s.httpServer.ListenAndServe() } // Shutdown gracefully shuts down the server func (s *Server) Shutdown(ctx context.Context) error { s.log.Info("Shutting down server...") var shutdownErrors []error // Shutdown HTTP server gracefully if s.httpServer != nil { s.log.Info("Shutting down HTTP server...") if err := s.httpServer.Shutdown(ctx); err != nil { s.log.Error("Error shutting down HTTP server", err) shutdownErrors = append(shutdownErrors, fmt.Errorf("HTTP server shutdown error: %w", err)) } else { s.log.Info("HTTP server shutdown completed") } } // Shutdown application dependencies if s.container != nil { if err := s.container.Shutdown(); err != nil { s.log.Error("Error shutting down dependencies", err) shutdownErrors = append(shutdownErrors, err) } } if len(shutdownErrors) > 0 { return fmt.Errorf("server shutdown completed with errors: %v", shutdownErrors) } s.log.Info("Server shutdown completed successfully") return nil } func (s *Server) setupRoutes() { // Health check endpoints - no middleware needed s.router.GET("/health", s.healthCheck) s.router.GET("/health/detailed", s.detailedHealthCheck) s.router.GET("/metrics", s.metricsEndpoint) // API v1 routes group with base middleware v1 := s.router.Group("/api") { // Public routes group - no authentication required public := v1.Group("/") { // Auth routes - public endpoints for registration and login auth := public.Group("/auth") { // Apply strict rate limiting to auth endpoints auth.Use(s.middleware.StrictRateLimit()) auth.POST("/register", s.container.AuthHandler.Register) auth.POST("/login", s.container.AuthHandler.Login) } // Schedule routes - public endpoint for getting available slots public.GET("/schedules", s.container.BookingHandler.GetAvailableSlots) // Payment webhook - public endpoint for Stripe webhooks (no auth needed) public.POST("/payments/webhook", s.container.PaymentHandler.HandleWebhook) } // Authenticated routes group - require JWT authentication authenticated := v1.Group("/") authenticated.Use(s.middleware.Auth()) { // Auth profile routes - require authentication authProfile := authenticated.Group("/auth") { authProfile.GET("/profile", s.container.AuthHandler.GetProfile) authProfile.PUT("/profile", s.container.AuthHandler.UpdateProfile) authProfile.POST("/logout", s.container.AuthHandler.Logout) } // Booking routes - require authentication bookings := authenticated.Group("/bookings") { bookings.GET("/", s.container.BookingHandler.GetUserBookings) bookings.POST("/", s.container.BookingHandler.CreateBooking) bookings.PUT("/:id/cancel", s.container.BookingHandler.CancelBooking) bookings.PUT("/:id/reschedule", s.container.BookingHandler.RescheduleBooking) } // Meeting routes - require authentication meetings := authenticated.Group("/meetings") { meetings.GET("/:id/link", s.container.MeetingHandler.GetMeetingLink) } // Payment routes - require authentication (except webhook) payments := authenticated.Group("/payments") { payments.POST("/intent", s.container.PaymentHandler.CreatePaymentIntent) payments.POST("/confirm", s.container.PaymentHandler.ConfirmPayment) } } // Admin routes - require admin authentication admin := v1.Group("/admin") admin.Use(s.middleware.RequireAdmin()) { admin.GET("/dashboard", s.container.AdminHandler.GetDashboard) admin.POST("/schedules", s.container.AdminHandler.CreateSchedule) admin.PUT("/schedules/:id", s.container.AdminHandler.UpdateSchedule) admin.GET("/users", s.container.AdminHandler.GetUsers) admin.GET("/bookings", s.container.AdminHandler.GetBookings) admin.GET("/reports/financial", s.container.AdminHandler.GetFinancialReports) } } } // healthCheck handles the basic health check endpoint func (s *Server) healthCheck(c *gin.Context) { // Simple health check - just return OK if server is running c.JSON(200, gin.H{ "status": "ok", "message": "Video Conference Booking System API", "timestamp": time.Now().UTC().Format(time.RFC3339), }) } // detailedHealthCheck handles the detailed health check endpoint func (s *Server) detailedHealthCheck(c *gin.Context) { ctx := c.Request.Context() // Perform comprehensive health checks response := s.healthChecker.BuildResponse(ctx) // Determine HTTP status code based on health status statusCode := 200 switch response.Status { case health.StatusUnhealthy: statusCode = 503 case health.StatusDegraded: statusCode = 503 } c.JSON(statusCode, response) } // metricsEndpoint handles the metrics endpoint func (s *Server) metricsEndpoint(c *gin.Context) { // Import monitoring package to get metrics // Note: In a real implementation, you might want to use a proper metrics format like Prometheus response := map[string]interface{}{ "status": "ok", "timestamp": time.Now().UTC().Format(time.RFC3339), "service": "Video Conference Booking System", "message": "Metrics endpoint - monitoring data would be available here", } c.JSON(200, response) } // registerHealthChecks registers all health checks func (s *Server) registerHealthChecks() { s.log.Info("Registering health checks...") // Database health check s.healthChecker.RegisterCheck("database", health.DatabaseHealthCheck(s.container.Database)) // Job manager health check s.healthChecker.RegisterCheck("job_manager", health.JobManagerHealthCheck(s.container.JobManagerService)) // Monitoring system health check s.healthChecker.RegisterCheck("monitoring", health.MonitoringHealthCheck()) // Memory health check (example with 512MB limit) s.healthChecker.RegisterCheck("memory", health.MemoryHealthCheck(512)) // Disk space health check (example with 1GB minimum) s.healthChecker.RegisterCheck("disk_space", health.DiskSpaceHealthCheck("/", 1)) s.log.Info("Health checks registered successfully") }