package httpserver import ( "context" "fmt" echomiddleware "github.com/gocasters/rankr/pkg/echo_middleware" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "strings" "sync" "time" ) type Config struct { Host string `koanf:"host"` Port int `koanf:"port"` CORS CORS `koanf:"cors"` ShutdownTimeout time.Duration `koanf:"shutdown_context_timeout"` HideBanner bool `koanf:"hide_banner"` HidePort bool `koanf:"hide_port"` PublicPaths []string `koanf:"public_paths"` // Optional Otel middleware can be injected from outside. OtelMiddleware echo.MiddlewareFunc } type CORS struct { AllowOrigins []string `koanf:"allow_origins"` } type Server struct { router *echo.Echo config *Config requireClaimsOnce sync.Once } var basePublicPaths = []string{ "/v1/login", "/v1/refresh-token", "/v1/me", "/ping", "/ping-otel", } func New(cfg Config) (*Server, error) { if cfg.Port < 1 || cfg.Port > 65535 { return nil, fmt.Errorf("invalid port: %d", cfg.Port) } if cfg.ShutdownTimeout <= 0 { cfg.ShutdownTimeout = DefaultShutdownTimeout } e := echo.New() if cfg.OtelMiddleware != nil { e.Use(cfg.OtelMiddleware) } e.Use(middleware.RequestID()) e.Use(middleware.Logger()) e.Use(middleware.Recover()) e.Use( middleware.CORSWithConfig( middleware.CORSConfig{ AllowOrigins: cfg.CORS.AllowOrigins, }, ), ) return &Server{ router: e, config: &cfg, }, nil } func (s *Server) GetRouter() *echo.Echo { s.requireClaimsOnce.Do(func() { s.router.Use( echomiddleware.RequireUserInfo( echomiddleware.RequireUserInfoOptions{ Skipper: newPublicPathSkipper(s.config.PublicPaths...), }, ), ) }) return s.router } func (s *Server) GetConfig() *Config { return s.config } func (s *Server) Start() error { addr := fmt.Sprintf("%s:%d", s.config.Host, s.config.Port) s.router.HideBanner = s.config.HideBanner s.router.HidePort = s.config.HidePort return s.router.Start(addr) } func (s *Server) Stop(ctx context.Context) error { return s.router.Shutdown(ctx) } func newPublicPathSkipper(extraPaths ...string) middleware.Skipper { paths := make([]string, 0, len(basePublicPaths)+len(extraPaths)) paths = append(paths, basePublicPaths...) paths = append(paths, extraPaths...) staticPublicPathSkipper := echomiddleware.SkipExactPaths(paths...) return func(c echo.Context) bool { path := strings.TrimSuffix(c.Request().URL.Path, "/") return staticPublicPathSkipper(c) || strings.HasSuffix(path, "/health-check") || strings.HasSuffix(path, "/health_check") } }