From 2740d223cbf58b673d0079d0f484b180071bae58 Mon Sep 17 00:00:00 2001 From: darwincereska Date: Wed, 18 Feb 2026 20:25:24 -0500 Subject: [PATCH] feat: added strapi echo routes --- go.mod | 9 +++ go.sum | 19 ++++++ internal/echo/handlers/params.go | 27 -------- internal/echo/handlers/post_handler.go | 15 +---- internal/echo/logger/logger.go | 3 +- internal/echo/routes/routes.go | 91 ++++++++++++++++++++++++-- 6 files changed, 117 insertions(+), 47 deletions(-) delete mode 100644 internal/echo/handlers/params.go diff --git a/go.mod b/go.mod index d0b1bb0..5d716f3 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charmbracelet/colorprofile v0.4.2 // indirect github.com/charmbracelet/lipgloss v1.1.0 // indirect @@ -33,6 +34,7 @@ require ( github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/labstack/echo-contrib/v5 v5.0.0 // indirect github.com/labstack/echo/v5 v5.0.4 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect @@ -40,15 +42,22 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.20 // indirect github.com/muesli/termenv v0.16.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/vektah/gqlparser/v2 v2.5.31 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.uber.org/atomic v1.11.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/crypto v0.48.0 // indirect golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.14.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect ) diff --git a/go.sum b/go.sum index d95da23..ccee8bc 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,11 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNg github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= @@ -80,6 +83,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/labstack/echo-contrib/v5 v5.0.0 h1:ZukJ7gzW/gEe9ZiqpSQGAkRR8E35eTvu1PQBjmp2tDs= +github.com/labstack/echo-contrib/v5 v5.0.0/go.mod h1:oUtPer7/M+vUJjDATlgVUhHvVUDc7Nh/4Xs+/m52OKA= github.com/labstack/echo/v5 v5.0.4 h1:ll3I/O8BifjMztj9dD1vx/peZQv8cR2CTUdQK6QxGGc= github.com/labstack/echo/v5 v5.0.4/go.mod h1:SyvlSdObGjRXeQfCCXW/sybkZdOOQZBmpKF0bvALaeo= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= @@ -99,8 +104,18 @@ github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjc github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/redis/go-redis/v9 v9.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1DQx4= github.com/redis/go-redis/v9 v9.17.3/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs= @@ -129,6 +144,8 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= @@ -155,6 +172,8 @@ golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/internal/echo/handlers/params.go b/internal/echo/handlers/params.go deleted file mode 100644 index 3ace1e3..0000000 --- a/internal/echo/handlers/params.go +++ /dev/null @@ -1,27 +0,0 @@ -package handlers - -import ( - "strconv" - - "github.com/labstack/echo/v5" -) - -// Helper method to get int param or default value -func GetIntParam(c *echo.Context, name string, defaultValue int) int { - value, err := strconv.Atoi(c.ParamOr(name, strconv.Itoa(defaultValue))) - if err != nil { - return defaultValue - } - - return value -} - -// Helper method to get bool param or default value -func GetBoolParam(c *echo.Context, name string, defaultValue bool) bool { - value, err := strconv.ParseBool(c.ParamOr(name, strconv.FormatBool(defaultValue))) - if err != nil { - return defaultValue - } - - return value -} diff --git a/internal/echo/handlers/post_handler.go b/internal/echo/handlers/post_handler.go index 7f1de0d..6122bea 100644 --- a/internal/echo/handlers/post_handler.go +++ b/internal/echo/handlers/post_handler.go @@ -8,10 +8,7 @@ import ( ) // GetAllPosts returns a list of all posts -func GetAllPosts(c *echo.Context, s *services.StrapiService) error { - pageSize := GetIntParam(c, "pageSize", 10) - page := GetIntParam(c, "page", 1) - +func GetAllPosts(c *echo.Context, s *services.StrapiService, pageSize, page int) error { posts, err := s.GetAllPosts(c.Request().Context(), pageSize, page) if err != nil { return c.JSON(http.StatusInternalServerError, err) @@ -21,10 +18,7 @@ func GetAllPosts(c *echo.Context, s *services.StrapiService) error { } // GetFeaturedPosts returns a list of featured posts -func GetFeaturedPosts(c *echo.Context, s *services.StrapiService) error { - pageSize := GetIntParam(c, "pageSize", 10) - page := GetIntParam(c, "page", 1) - +func GetFeaturedPosts(c *echo.Context, s *services.StrapiService, pageSize, page int) error { posts, err := s.GetFeaturedPosts(c.Request().Context(), pageSize, page) if err != nil { return c.JSON(http.StatusInternalServerError, err) @@ -44,10 +38,7 @@ func GetPost(c *echo.Context, s *services.StrapiService, slug string) error { } // GetPostSummaries returns post summaries -func GetPostSummaries(c *echo.Context, s *services.StrapiService) error { - pageSize := GetIntParam(c, "pageSize", 10) - page := GetIntParam(c, "page", 1) - +func GetPostSummaries(c *echo.Context, s *services.StrapiService, pageSize, page int) error { posts, err := s.GetPostSummaries(c.Request().Context(), pageSize, page) if err != nil { return c.JSON(http.StatusInternalServerError, err) diff --git a/internal/echo/logger/logger.go b/internal/echo/logger/logger.go index 1d56383..eb08290 100644 --- a/internal/echo/logger/logger.go +++ b/internal/echo/logger/logger.go @@ -12,8 +12,9 @@ func NewCharmSlog() *slog.Logger { // 1. Initialize Charmbracelet options := log.Options{ ReportTimestamp: true, - ReportCaller: true, + ReportCaller: false, Level: log.DebugLevel, + Prefix: "ECHO", } handler := log.NewWithOptions(os.Stderr, options) diff --git a/internal/echo/routes/routes.go b/internal/echo/routes/routes.go index b4a1f03..a2001c2 100644 --- a/internal/echo/routes/routes.go +++ b/internal/echo/routes/routes.go @@ -3,14 +3,38 @@ package routes import ( "blog/internal/cache" "blog/internal/echo/handlers" - "blog/internal/echo/middleware" "blog/internal/services" "net/http" + "strconv" + "time" "github.com/charmbracelet/log" "github.com/labstack/echo/v5" + "github.com/labstack/echo/v5/middleware" + "github.com/labstack/echo-contrib/v5/echoprometheus" ) +// Helper method to get int param or default value +func GetIntQueryParam(c *echo.Context, name string, defaultValue int) int { + value, err := strconv.Atoi(c.QueryParamOr(name, strconv.Itoa(defaultValue))) + if err != nil { + return defaultValue + } + + return value +} + +// Helper method to get bool param or default value +func GetBoolQueryParam(c *echo.Context, name string, defaultValue bool) bool { + value, err := strconv.ParseBool(c.QueryParamOr(name, strconv.FormatBool(defaultValue))) + if err != nil { + return defaultValue + } + + return value +} + + // Souces is a struct that has the sources for any data that the routes need type Sources struct { StrapiService *services.StrapiService @@ -23,17 +47,48 @@ func SetupRoutes(e *echo.Echo, sources Sources) { } // Global middleware - e.Use(middleware.ServerHandler) + + // Remove trailing slash + e.Pre(middleware.RemoveTrailingSlash()) + // Use GZIP compression + e.Use(middleware.GzipWithConfig(middleware.GzipConfig{ Level: 5, })) + // CORS origin + e.Use(middleware.CORS("https://blog.darwincereska.dev", "http://localhost:3000", "http://0.0.0.0:3000")) + // Request logger + e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ + LogStatus: true, + LogURI: true, + LogMethod: true, + HandleError: true, + LogLatency: true, + LogRemoteIP: true, + LogURIPath: true, + LogValuesFunc: func(c *echo.Context, v middleware.RequestLoggerValues) error { + if v.Error == nil { + e.Logger.Info("REQUEST", "method", v.Method, "uri", v.URI, "status", v.Status, "latency", v.Latency, "remote-ip", v.RemoteIP) + } else { + e.Logger.Error("REQUEST_ERROR", "error", v.Error.Error(), "method", v.Method, "uri", v.URIPath, "status", v.Status, "latency", v.Latency, "remote-ip", v.RemoteIP) + } + return nil + }, + })) + // Context timeout : DEFAULT 60s + e.Use(middleware.ContextTimeout(time.Second * 60)) + // Prometheus metrics + e.Use(echoprometheus.NewMiddleware("blog")) // Routes strapiRoutes(e, sources.StrapiService) + // Static routes + e.Static("/public", "web/static") + // Special routes - e.GET("/api", func(c *echo.Context) error { - // Load all routes - // routes, _ := json.MarshalIndent(e.Router().Routes(), "", "") + e.GET("/api*", func(c *echo.Context) error { return c.JSON(http.StatusOK, e.Router().Routes()) }) + + e.GET("/metrics", echoprometheus.NewHandler()) } // Setup Strapi routes @@ -43,11 +98,33 @@ func strapiRoutes(e *echo.Echo, s *services.StrapiService) { // GET /api/posts/all posts.GET("/all", func(c *echo.Context) error { - return handlers.GetAllPosts(c, s) + pageSize := GetIntQueryParam(c, "pageSize", 10) + page := GetIntQueryParam(c, "page", 1) + + return handlers.GetPostSummaries(c, s, pageSize, page) }) // GET /api/posts/featured posts.GET("/featured", func(c *echo.Context) error { - return handlers.GetFeaturedPosts(c, s) + pageSize := GetIntQueryParam(c, "pageSize", 10) + page := GetIntQueryParam(c, "page", 1) + + return handlers.GetFeaturedPosts(c, s, pageSize, page) + }) + + // GET /api/posts/tag/:tag + posts.GET("/tag/:tag", func(c *echo.Context) error { + tag := c.Param("tag") + pageSize := GetIntQueryParam(c, "pageSize", 10) + page := GetIntQueryParam(c, "page", 1) + + return handlers.GetPostsByTag(c, s, tag, pageSize, page) + }) + + // GET /api/posts/post/:slug + posts.GET("/post/:slug", func(c *echo.Context) error { + slug := c.Param("slug") + + return handlers.GetPost(c, s, slug) }) }