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

@@ -1,18 +1,18 @@
package config
import (
"github.com/charmbracelet/log"
"blog/internal/config/env"
"fmt"
"github.com/charmbracelet/log"
)
type DatabaseConfig struct {
Host string
Port int
Name string
User string
Host string
Port int
Name string
User string
Password string
SSLMode string
SSLMode string
TimeZone string
}
@@ -29,7 +29,7 @@ func (c *DatabaseConfig) LoadConfig() {
c.SSLMode = env.GetString("DB_SSL_MODE", "disable")
c.TimeZone = env.GetString("DB_TIME_ZONE", "America/New_York")
log.Info("Successfully loaded database config")
log.Info("Successfully loaded database config")
}
func (c *DatabaseConfig) GetDSN() string {

View File

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

View File

@@ -1,19 +1,19 @@
package config
import (
"github.com/charmbracelet/log"
"blog/internal/config/env"
"github.com/charmbracelet/log"
)
type ServerConfig struct {
Host string
Port int
Host string
Port int
StrapiEndpoint string
RedisHost string
RedisPort int
StrapiToken string
CacheTTL int
EchoMode string
RedisHost string
RedisPort int
StrapiToken string
CacheTTL int
EchoMode string
}
func NewServerConfig() *ServerConfig {

View File

@@ -18,14 +18,13 @@ type DB struct {
*gorm.DB
}
// Connect connects to database and returns DB
func Connect(dsn string) (*DB, error) {
// Charmbracelet's "log" as a slog handler
charmHandler := log.NewWithOptions(os.Stdout, log.Options{
ReportCaller: false,
ReportCaller: false,
ReportTimestamp: true,
Prefix: "GORM",
Prefix: "GORM",
})
slogger := slog.New(charmHandler)
@@ -34,12 +33,12 @@ func Connect(dsn string) (*DB, error) {
gormLogger := logger.New(
slog.NewLogLogger(slogger.Handler(), slog.LevelDebug),
logger.Config{
SlowThreshold: time.Millisecond * 200,
LogLevel: logger.Info,
SlowThreshold: time.Millisecond * 200,
LogLevel: logger.Info,
IgnoreRecordNotFoundError: false,
Colorful: true,
Colorful: true,
},
)
)
// Connect to database
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{

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
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)
if err != nil {
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
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)
if err != nil {
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
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)
if err != nil {
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
import (
"time"
// "time"
"github.com/charmbracelet/log"
// "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()
// start := time.Now()
// Process the request
err := next(c)
@@ -17,16 +17,16 @@ func ServerHandler(next echo.HandlerFunc) echo.HandlerFunc {
c.Logger().Error(err.Error())
}
stop := time.Now()
req := c.Request()
// 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(),
)
// // Log using charmbracelet
// log.Info("Request handlers",
// "method", req.Method,
// "path", req.URL.Path,
// "latency", stop.Sub(start),
// "ip", c.RealIP(),
// )
return nil
}

View File

@@ -1,36 +1,53 @@
package routes
import (
"blog/internal/cache"
"blog/internal/echo/handlers"
"blog/internal/echo/middleware"
"blog/internal/services"
"strconv"
"net/http"
"github.com/charmbracelet/log"
"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
// Souces is a struct that has the sources for any data that the routes need
type Sources struct {
StrapiService *services.StrapiService
Caches []cache.Cache
}
func SetupRoutes(e *echo.Echo, s *services.StrapiService) {
func SetupRoutes(e *echo.Echo, sources Sources) {
if sources.StrapiService == nil {
log.Fatal("Error", "error", "strapi service is required")
}
// Global middleware
e.Use(middleware.ServerHandler)
// Post routes
posts := e.Group("/posts") // Routing group
// Routes
strapiRoutes(e, sources.StrapiService)
// 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)
// Special routes
e.GET("/api", func(c *echo.Context) error {
// Load all routes
// routes, _ := json.MarshalIndent(e.Router().Routes(), "", "")
return c.JSON(http.StatusOK, e.Router().Routes())
})
}
// 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

View File

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

View File

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

View File

@@ -3,77 +3,77 @@ package models
import "time"
type Post struct {
ID uint `json:"id"`
Title string `json:"title"`
Slug string `json:"slug"`
Content string `json:"content"`
Excerpt string `json:"excerpt"`
FeaturedImage *Media `json:"featured_image"`
Published bool `json:"published"`
PublishedAt *time.Time `json:"published_at"`
MetaTitle string `json:"meta_title"`
MetaDesc string `json:"meta_description"`
ReadingTime int `json:"reading_time"`
Author Author `json:"author"`
Tags []Tag `json:"tags"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ID uint `json:"id"`
Title string `json:"title"`
Slug string `json:"slug"`
Content string `json:"content"`
Excerpt string `json:"excerpt"`
FeaturedImage *Media `json:"featured_image"`
Published bool `json:"published"`
PublishedAt *time.Time `json:"published_at"`
MetaTitle string `json:"meta_title"`
MetaDesc string `json:"meta_description"`
ReadingTime int `json:"reading_time"`
Author Author `json:"author"`
Tags []Tag `json:"tags"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type Tag struct {
ID uint `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
Color string `json:"color,omitempty"`
ID uint `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
Color string `json:"color,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type Author struct {
ID uint `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Bio string `json:"bio"`
Avatar *Media `json:"avatar"`
ID uint `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Bio string `json:"bio"`
Avatar *Media `json:"avatar"`
SocialLinks map[string]string `json:"social_links"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type Media struct {
ID uint `json:"id"`
Name string `json:"name"`
AlternativeText string `json:"alternativeText"`
Caption string `json:"caption"`
Width int `json:"width"`
Height int `json:"height"`
Formats MediaFormats `json:"formats"`
Hash string `json:"hash"`
Ext string `json:"ext"`
Mime string `json:"mime"`
Size float64 `json:"size"`
URL string `json:"url"`
PreviewURL string `json:"previewUrl"`
Provider string `json:"provider"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
ID uint `json:"id"`
Name string `json:"name"`
AlternativeText string `json:"alternativeText"`
Caption string `json:"caption"`
Width int `json:"width"`
Height int `json:"height"`
Formats MediaFormats `json:"formats"`
Hash string `json:"hash"`
Ext string `json:"ext"`
Mime string `json:"mime"`
Size float64 `json:"size"`
URL string `json:"url"`
PreviewURL string `json:"previewUrl"`
Provider string `json:"provider"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type MediaFormats struct {
Large *MediaFormat `json:"large,omitempty"`
Medium *MediaFormat `json:"medium,omitempty"`
Small *MediaFormat `json:"small,omitempty"`
Thumbnail *MediaFormat `json:"thumbnail,omitempty"`
Large *MediaFormat `json:"large,omitempty"`
Medium *MediaFormat `json:"medium,omitempty"`
Small *MediaFormat `json:"small,omitempty"`
Thumbnail *MediaFormat `json:"thumbnail,omitempty"`
}
type MediaFormat struct {
Name string `json:"name"`
Hash string `json:"hash"`
Ext string `json:"ext"`
Mime string `json:"mime"`
Width int `json:"width"`
Height int `json:"height"`
Size float64 `json:"size"`
Path string `json:"path"`
URL string `json:"url"`
Name string `json:"name"`
Hash string `json:"hash"`
Ext string `json:"ext"`
Mime string `json:"mime"`
Width int `json:"width"`
Height int `json:"height"`
Size float64 `json:"size"`
Path string `json:"path"`
URL string `json:"url"`
}

View File

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

View File

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

View File

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

View File

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

View File

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