package core import ( "fmt" "strings" "time" "github.com/gofiber/fiber/v2" "github.com/sirupsen/logrus" ) type JSONErrorResponse struct { Error string `json:"error"` Code int `json:"code"` Message string `json:"message,omitempty"` } type CORSConfig struct { AllowOrigins string AllowMethods string AllowHeaders string MaxAge int } func DefaultCORSConfig() CORSConfig { return CORSConfig{ AllowOrigins: "*", AllowMethods: "GET, POST, OPTIONS", AllowHeaders: "Origin, Content-Type, Accept, Authorization, X-Use-Proxy", MaxAge: 86400, } } func CORSMiddleware(cfg CORSConfig) fiber.Handler { cfg = normalizeCORSConfig(cfg) return func(c *fiber.Ctx) error { c.Set("Access-Control-Allow-Origin", cfg.AllowOrigins) c.Set("Access-Control-Allow-Methods", cfg.AllowMethods) c.Set("Access-Control-Allow-Headers", cfg.AllowHeaders) c.Set("Access-Control-Max-Age", fmt.Sprintf("%d", cfg.MaxAge)) if c.Method() == "OPTIONS" { return c.SendStatus(fiber.StatusNoContent) } return c.Next() } } // normalizeCORSConfig keeps CORS behavior predictable when config provides partial values. func normalizeCORSConfig(cfg CORSConfig) CORSConfig { defaults := DefaultCORSConfig() if strings.TrimSpace(cfg.AllowOrigins) == "" { cfg.AllowOrigins = defaults.AllowOrigins } if strings.TrimSpace(cfg.AllowMethods) == "" { cfg.AllowMethods = defaults.AllowMethods } if strings.TrimSpace(cfg.AllowHeaders) == "" { cfg.AllowHeaders = defaults.AllowHeaders } if cfg.MaxAge <= 0 { cfg.MaxAge = defaults.MaxAge } return cfg } func RequestLoggerMiddleware() fiber.Handler { return func(c *fiber.Ctx) error { start := time.Now() err := c.Next() latency := time.Since(start) status := c.Response().StatusCode() if err != nil { if e, ok := err.(*fiber.Error); ok { status = e.Code } else { status = fiber.StatusInternalServerError } } logFields := logrus.Fields{ "method": c.Method(), "path": c.Path(), "status": status, "latency": latency.String(), "ip": c.IP(), } if query := c.Query("text"); query != "" { logFields["query"] = query } entry := logrus.WithFields(logFields) if status >= 500 { entry.Errorf("%s - request failed", c.Path()) } else if status >= 400 { entry.Warnf("%s - request error", c.Path()) } else { entry.Infof("%s - request completed", c.Path()) } return err } } func JSONErrorMiddleware() fiber.ErrorHandler { return func(c *fiber.Ctx, err error) error { code := fiber.StatusInternalServerError if e, ok := err.(*fiber.Error); ok { code = e.Code } resp := JSONErrorResponse{ Error: statusText(code), Code: code, Message: err.Error(), } c.Set("Content-Type", "application/json") return c.Status(code).JSON(resp) } } func statusText(code int) string { switch { case code == 400: return "bad_request" case code == 404: return "not_found" case code == 429: return "rate_limited" case code == 503: return "service_unavailable" case code >= 400 && code < 500: return "client_error" case code >= 500: return "server_error" default: return "error" } }