package server import ( "context" "fmt" "log" "net/http" "os" "os/signal" "runtime" "syscall" "time" "otpm/config" "otpm/middleware" ) // Server represents the HTTP server type Server struct { server *http.Server router *http.ServeMux config *config.Config } // New creates a new server func New(cfg *config.Config) *Server { router := http.NewServeMux() server := &http.Server{ Addr: fmt.Sprintf(":%d", cfg.Server.Port), Handler: router, ReadTimeout: cfg.Server.ReadTimeout, WriteTimeout: cfg.Server.WriteTimeout, IdleTimeout: 120 * time.Second, } return &Server{ server: server, router: router, config: cfg, } } // Start starts the server func (s *Server) Start() error { // Apply global middleware in correct order with enhanced error handling var handler http.Handler = s.router // Logger should be first to capture all request details handler = middleware.Logger(handler) // CORS next to handle pre-flight requests handler = middleware.CORS(handler) // Then Timeout to enforce request deadlines handler = middleware.Timeout(s.config.Server.Timeout)(handler) // Recover should be outermost to catch any panics handler = middleware.Recover(handler) s.server.Handler = handler // Log server configuration at startup log.Printf("Server configuration:\n"+ "Address: %s\n"+ "Read Timeout: %v\n"+ "Write Timeout: %v\n"+ "Idle Timeout: %v\n"+ "Request Timeout: %v", s.server.Addr, s.server.ReadTimeout, s.server.WriteTimeout, s.server.IdleTimeout, s.config.Server.Timeout, ) // Start server in a goroutine serverErr := make(chan error, 1) go func() { log.Printf("Server starting on %s", s.server.Addr) if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { serverErr <- fmt.Errorf("server error: %w", err) } }() // Wait for interrupt signal or server error quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) select { case err := <-serverErr: return err case <-quit: return s.Shutdown() } } // Shutdown gracefully stops the server func (s *Server) Shutdown() error { log.Println("Shutting down server...") ctx, cancel := context.WithTimeout(context.Background(), s.config.Server.ShutdownTimeout) defer cancel() if err := s.server.Shutdown(ctx); err != nil { return fmt.Errorf("graceful shutdown failed: %w", err) } log.Println("Server stopped gracefully") return nil } // Router returns the router func (s *Server) Router() *http.ServeMux { return s.router } // RegisterRoutes registers all routes func (s *Server) RegisterRoutes(routes map[string]http.Handler) { for pattern, handler := range routes { s.router.Handle(pattern, handler) } } // RegisterAuthRoutes registers routes that require authentication func (s *Server) RegisterAuthRoutes(routes map[string]http.Handler) { for pattern, handler := range routes { // Apply authentication middleware authHandler := middleware.Auth(s.config.JWT.Secret)(handler) s.router.Handle(pattern, authHandler) } } // RegisterHealthCheck registers an enhanced health check endpoint func (s *Server) RegisterHealthCheck() { s.router.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { response := map[string]interface{}{ "status": "ok", "timestamp": time.Now().Format(time.RFC3339), "version": "1.0.0", // Hardcoded version instead of from config "system": map[string]interface{}{ "goroutines": runtime.NumGoroutine(), "memory": getMemoryUsage(), }, } // Add database status if configured if s.config.Database.DSN != "" { // Changed from URL to DSN to match config dbStatus := "ok" // Removed DB ping check since we don't have DB instance in config response["database"] = dbStatus } middleware.SuccessResponse(w, response) }) } // getMemoryUsage returns current memory usage in MB func getMemoryUsage() map[string]interface{} { var m runtime.MemStats runtime.ReadMemStats(&m) return map[string]interface{}{ "alloc_mb": bToMb(m.Alloc), "total_alloc_mb": bToMb(m.TotalAlloc), "sys_mb": bToMb(m.Sys), "num_gc": m.NumGC, } } func bToMb(b uint64) float64 { return float64(b) / 1024 / 1024 }