diff --git a/cmd/main.go b/cmd/main.go index 73d6da8..0a0a071 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -4,17 +4,19 @@ import ( "blog/internal/cache" "blog/internal/config" "blog/internal/database" + "blog/internal/echo/routes" "blog/internal/services" - "context" - "os" + "fmt" "github.com/charmbracelet/log" + "github.com/labstack/echo/v5" + "github.com/labstack/echo/v5/middleware" ) func main() { // Server config server_config := config.NewServerConfig() - server_config.LoadConfig() + server_config.LoadConfig() // Database Config db_config := config.NewDatabaseConfig() @@ -23,7 +25,7 @@ func main() { // Connect to database db, err := database.Connect(db_config.GetDSN()) if err != nil { - log.Fatal("Failed to connect to database: ", err) + log.Error("Failed to connect to database: ", err) } defer db.Close() @@ -34,20 +36,16 @@ func main() { // Create Strapi service strapi_service := services.NewStrapiService(server_config.StrapiEndpoint+"/graphql", server_config.StrapiToken, strapi_cache) - // Strapi logger - strapi_logger := log.NewWithOptions(os.Stderr, log.Options{ - ReportTimestamp: true, - Prefix: "STRAPI", - }) + // Setup echo server + e := echo.New() + e.Use(middleware.RequestLogger()) - // Test strapi get - posts, err := strapi_service.GetFeaturedPosts(context.Background(), 10, 1) - if err != nil { - strapi_logger.Error(err) - os.Exit(0) + // Setup routes + routes.SetupRoutes(e, strapi_service) + + // Start server + host := fmt.Sprintf("%s:%d", server_config.Host, server_config.Port) + if err := e.Start(host); err != nil { + log.Fatal("Failed to start server", "error", err) } - - post := posts[0] - - strapi_logger.Info(post) } diff --git a/go.mod b/go.mod index 16fe808..c3068f5 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,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/v5 v5.0.4 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.20 // indirect @@ -45,4 +46,5 @@ require ( 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 ) diff --git a/go.sum b/go.sum index 4d32f7f..6c9042b 100644 --- a/go.sum +++ b/go.sum @@ -80,6 +80,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/v5 v5.0.4 h1:ll3I/O8BifjMztj9dD1vx/peZQv8cR2CTUdQK6QxGGc= +github.com/labstack/echo/v5 v5.0.4/go.mod h1:SyvlSdObGjRXeQfCCXW/sybkZdOOQZBmpKF0bvALaeo= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= @@ -139,6 +141,8 @@ golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +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= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/echo/handlers/post_handler.go b/internal/echo/handlers/post_handler.go new file mode 100644 index 0000000..6122bea --- /dev/null +++ b/internal/echo/handlers/post_handler.go @@ -0,0 +1,58 @@ +package handlers + +import ( + "blog/internal/services" + "net/http" + + "github.com/labstack/echo/v5" +) + +// GetAllPosts returns a list of all posts +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) + } + + return c.JSON(http.StatusOK, posts) +} + +// GetFeaturedPosts returns a list of featured posts +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) + } + + return c.JSON(http.StatusOK, posts) +} + +// GetPost returns a specific post +func GetPost(c *echo.Context, s *services.StrapiService, slug string) error { + post, err := s.GetPost(c.Request().Context(), slug) + if err != nil { + return c.JSON(http.StatusInternalServerError, err) + } + + return c.JSON(http.StatusOK, post) +} + +// GetPostSummaries returns post summaries +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) + } + + return c.JSON(http.StatusOK, posts) +} + +// GetPostsByTag returns a list of posts by tag +func GetPostsByTag(c *echo.Context, s *services.StrapiService, tag string, pageSize, page int) error { + posts, err := s.GetPostsByTag(c.Request().Context(), tag, pageSize, page) + if err != nil { + return c.JSON(http.StatusInternalServerError, err) + } + + return c.JSON(http.StatusOK, posts) +} diff --git a/internal/echo/middleware/middleware.go b/internal/echo/middleware/middleware.go new file mode 100644 index 0000000..4dbfda0 --- /dev/null +++ b/internal/echo/middleware/middleware.go @@ -0,0 +1,33 @@ +package middleware + +import ( + "time" + + "github.com/charmbracelet/log" + "github.com/labstack/echo/v5" +) + +func ServerHandler(next echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) error { + start := time.Now() + + // Process the request + err := next(c) + if err != nil { + c.Logger().Error(err.Error()) + } + + stop := time.Now() + req := c.Request() + + // Log using charmbracelet + log.Info("Request handled", + "method", req.Method, + "path", req.URL.Path, + "latency", stop.Sub(start), + "ip", c.RealIP(), + ) + + return nil + } +} diff --git a/internal/echo/routes/routes.go b/internal/echo/routes/routes.go new file mode 100644 index 0000000..ecc023b --- /dev/null +++ b/internal/echo/routes/routes.go @@ -0,0 +1,36 @@ +package routes + +import ( + "blog/internal/echo/handlers" + "blog/internal/echo/middleware" + "blog/internal/services" + "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 +} + +func SetupRoutes(e *echo.Echo, s *services.StrapiService) { + // Global middleware + e.Use(middleware.ServerHandler) + + // Post routes + posts := e.Group("/posts") // Routing group + + // GET /posts/all + posts.GET("/all", func(c *echo.Context) error { + pageSize := getIntParam(c, "pageSize", 10) + page := getIntParam(c, "page", 1) + + return handlers.GetAllPosts(c, s, pageSize, page) + }) +}