feat: added logging and some routes with fmt

This commit is contained in:
Cereska
2026-02-18 12:40:10 -05:00
parent 150f95ee0b
commit 9a0e8d46c8
21 changed files with 204 additions and 123 deletions

View File

@@ -4,13 +4,13 @@ import (
"blog/internal/cache" "blog/internal/cache"
"blog/internal/config" "blog/internal/config"
"blog/internal/database" "blog/internal/database"
"blog/internal/echo/logger"
"blog/internal/echo/routes" "blog/internal/echo/routes"
"blog/internal/services" "blog/internal/services"
"fmt" "fmt"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
"github.com/labstack/echo/v5" "github.com/labstack/echo/v5"
"github.com/labstack/echo/v5/middleware"
) )
func main() { func main() {
@@ -38,10 +38,12 @@ func main() {
// Setup echo server // Setup echo server
e := echo.New() e := echo.New()
e.Use(middleware.RequestLogger()) e.Logger = logger.NewCharmSlog()
// Setup routes // Setup routes
routes.SetupRoutes(e, strapi_service) routes.SetupRoutes(e, routes.Sources{
StrapiService: strapi_service,
})
// Start server // Start server
host := fmt.Sprintf("%s:%d", server_config.Host, server_config.Port) host := fmt.Sprintf("%s:%d", server_config.Host, server_config.Port)

4
go.mod
View File

@@ -34,11 +34,15 @@ require (
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/labstack/echo/v5 v5.0.4 // 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 github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.20 // indirect
github.com/muesli/termenv v0.16.0 // indirect github.com/muesli/termenv v0.16.0 // indirect
github.com/rivo/uniseg v0.4.7 // 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/vektah/gqlparser/v2 v2.5.31 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.uber.org/atomic v1.11.0 // indirect go.uber.org/atomic v1.11.0 // indirect

10
go.sum
View File

@@ -82,10 +82,15 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 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 h1:ll3I/O8BifjMztj9dD1vx/peZQv8cR2CTUdQK6QxGGc=
github.com/labstack/echo/v5 v5.0.4/go.mod h1:SyvlSdObGjRXeQfCCXW/sybkZdOOQZBmpKF0bvALaeo= github.com/labstack/echo/v5 v5.0.4/go.mod h1:SyvlSdObGjRXeQfCCXW/sybkZdOOQZBmpKF0bvALaeo=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 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.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
@@ -112,6 +117,10 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vektah/gqlparser/v2 v2.5.19 h1:bhCPCX1D4WWzCDvkPl4+TP1N8/kLrWnp43egplt7iSg= github.com/vektah/gqlparser/v2 v2.5.19 h1:bhCPCX1D4WWzCDvkPl4+TP1N8/kLrWnp43egplt7iSg=
github.com/vektah/gqlparser/v2 v2.5.19/go.mod h1:y7kvl5bBlDeuWIvLtA9849ncyvx6/lj06RsMrEjVy3U= github.com/vektah/gqlparser/v2 v2.5.19/go.mod h1:y7kvl5bBlDeuWIvLtA9849ncyvx6/lj06RsMrEjVy3U=
github.com/vektah/gqlparser/v2 v2.5.31 h1:YhWGA1mfTjID7qJhd1+Vxhpk5HTgydrGU9IgkWBTJ7k= github.com/vektah/gqlparser/v2 v2.5.31 h1:YhWGA1mfTjID7qJhd1+Vxhpk5HTgydrGU9IgkWBTJ7k=
@@ -132,6 +141,7 @@ golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=

View File

@@ -1,9 +1,9 @@
package config package config
import ( import (
"github.com/charmbracelet/log"
"blog/internal/config/env" "blog/internal/config/env"
"fmt" "fmt"
"github.com/charmbracelet/log"
) )
type DatabaseConfig struct { type DatabaseConfig struct {

View File

@@ -1,2 +1 @@
package config package config

View File

@@ -1,8 +1,8 @@
package config package config
import ( import (
"github.com/charmbracelet/log"
"blog/internal/config/env" "blog/internal/config/env"
"github.com/charmbracelet/log"
) )
type ServerConfig struct { type ServerConfig struct {

View File

@@ -18,7 +18,6 @@ type DB struct {
*gorm.DB *gorm.DB
} }
// Connect connects to database and returns DB // Connect connects to database and returns DB
func Connect(dsn string) (*DB, error) { func Connect(dsn string) (*DB, error) {
// Charmbracelet's "log" as a slog handler // Charmbracelet's "log" as a slog handler

View File

@@ -0,0 +1,27 @@
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
}

View File

@@ -8,7 +8,10 @@ import (
) )
// GetAllPosts returns a list of all posts // GetAllPosts returns a list of all posts
func GetAllPosts(c *echo.Context, s *services.StrapiService, pageSize, page int) error { func GetAllPosts(c *echo.Context, s *services.StrapiService) error {
pageSize := GetIntParam(c, "pageSize", 10)
page := GetIntParam(c, "page", 1)
posts, err := s.GetAllPosts(c.Request().Context(), pageSize, page) posts, err := s.GetAllPosts(c.Request().Context(), pageSize, page)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, err) return c.JSON(http.StatusInternalServerError, err)
@@ -18,7 +21,10 @@ func GetAllPosts(c *echo.Context, s *services.StrapiService, pageSize, page int)
} }
// GetFeaturedPosts returns a list of featured posts // GetFeaturedPosts returns a list of featured posts
func GetFeaturedPosts(c *echo.Context, s *services.StrapiService, pageSize, page int) error { func GetFeaturedPosts(c *echo.Context, s *services.StrapiService) error {
pageSize := GetIntParam(c, "pageSize", 10)
page := GetIntParam(c, "page", 1)
posts, err := s.GetFeaturedPosts(c.Request().Context(), pageSize, page) posts, err := s.GetFeaturedPosts(c.Request().Context(), pageSize, page)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, err) return c.JSON(http.StatusInternalServerError, err)
@@ -38,7 +44,10 @@ func GetPost(c *echo.Context, s *services.StrapiService, slug string) error {
} }
// GetPostSummaries returns post summaries // GetPostSummaries returns post summaries
func GetPostSummaries(c *echo.Context, s *services.StrapiService, pageSize, page int) error { func GetPostSummaries(c *echo.Context, s *services.StrapiService) error {
pageSize := GetIntParam(c, "pageSize", 10)
page := GetIntParam(c, "page", 1)
posts, err := s.GetPostSummaries(c.Request().Context(), pageSize, page) posts, err := s.GetPostSummaries(c.Request().Context(), pageSize, page)
if err != nil { if err != nil {
return c.JSON(http.StatusInternalServerError, err) return c.JSON(http.StatusInternalServerError, err)

View File

@@ -0,0 +1,23 @@
package logger
import (
"log/slog"
"os"
"github.com/charmbracelet/log"
)
// NewCharmSlog returns a standard *slog.Logger powered by Charmbracelet
func NewCharmSlog() *slog.Logger {
// 1. Initialize Charmbracelet
options := log.Options{
ReportTimestamp: true,
ReportCaller: true,
Level: log.DebugLevel,
}
handler := log.NewWithOptions(os.Stderr, options)
// 2. Return as *slog.Logger
// Charmbracelet's Logger implements the slog.Handler interface
return slog.New(handler)
}

View File

@@ -1,15 +1,15 @@
package middleware package middleware
import ( import (
"time" // "time"
"github.com/charmbracelet/log" // "github.com/charmbracelet/log"
"github.com/labstack/echo/v5" "github.com/labstack/echo/v5"
) )
func ServerHandler(next echo.HandlerFunc) echo.HandlerFunc { func ServerHandler(next echo.HandlerFunc) echo.HandlerFunc {
return func(c *echo.Context) error { return func(c *echo.Context) error {
start := time.Now() // start := time.Now()
// Process the request // Process the request
err := next(c) err := next(c)
@@ -17,16 +17,16 @@ func ServerHandler(next echo.HandlerFunc) echo.HandlerFunc {
c.Logger().Error(err.Error()) c.Logger().Error(err.Error())
} }
stop := time.Now() // stop := time.Now()
req := c.Request() // req := c.Request()
// Log using charmbracelet // // Log using charmbracelet
log.Info("Request handled", // log.Info("Request handlers",
"method", req.Method, // "method", req.Method,
"path", req.URL.Path, // "path", req.URL.Path,
"latency", stop.Sub(start), // "latency", stop.Sub(start),
"ip", c.RealIP(), // "ip", c.RealIP(),
) // )
return nil return nil
} }

View File

@@ -1,36 +1,53 @@
package routes package routes
import ( import (
"blog/internal/cache"
"blog/internal/echo/handlers" "blog/internal/echo/handlers"
"blog/internal/echo/middleware" "blog/internal/echo/middleware"
"blog/internal/services" "blog/internal/services"
"strconv" "net/http"
"github.com/charmbracelet/log"
"github.com/labstack/echo/v5" "github.com/labstack/echo/v5"
) )
// Helper method to get int param or default value // Souces is a struct that has the sources for any data that the routes need
func getIntParam(c *echo.Context, name string, defaultValue int) int { type Sources struct {
value, err := strconv.Atoi(c.ParamOr(name, strconv.Itoa(defaultValue))) StrapiService *services.StrapiService
if err != nil { Caches []cache.Cache
return defaultValue
} }
return value func SetupRoutes(e *echo.Echo, sources Sources) {
if sources.StrapiService == nil {
log.Fatal("Error", "error", "strapi service is required")
} }
func SetupRoutes(e *echo.Echo, s *services.StrapiService) {
// Global middleware // Global middleware
e.Use(middleware.ServerHandler) e.Use(middleware.ServerHandler)
// Post routes // Routes
posts := e.Group("/posts") // Routing group strapiRoutes(e, sources.StrapiService)
// GET /posts/all // Special routes
posts.GET("/all", func(c *echo.Context) error { e.GET("/api", func(c *echo.Context) error {
pageSize := getIntParam(c, "pageSize", 10) // Load all routes
page := getIntParam(c, "page", 1) // routes, _ := json.MarshalIndent(e.Router().Routes(), "", "")
return c.JSON(http.StatusOK, e.Router().Routes())
return handlers.GetAllPosts(c, s, pageSize, page) })
}
// Setup Strapi routes
func strapiRoutes(e *echo.Echo, s *services.StrapiService) {
// Post routes
posts := e.Group("/api/posts") // Routing group
// GET /api/posts/all
posts.GET("/all", func(c *echo.Context) error {
return handlers.GetAllPosts(c, s)
})
// GET /api/posts/featured
posts.GET("/featured", func(c *echo.Context) error {
return handlers.GetFeaturedPosts(c, s)
}) })
} }

View File

@@ -1,2 +1 @@
package email package email

View File

@@ -1,2 +1 @@
package markdown package markdown

View File

@@ -1,3 +1 @@
package models package models

View File

@@ -1,2 +1 @@
package repositories package repositories

View File

@@ -1,2 +1 @@
package repositories package repositories

View File

@@ -3,4 +3,3 @@ package rss
import ( import (
"encoding/xml" "encoding/xml"
) )

View File

@@ -1,2 +1 @@
package services package services

View File

@@ -1,2 +1 @@
package services package services