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,18 +1,18 @@
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 {
Host string Host string
Port int Port int
Name string Name string
User string User string
Password string Password string
SSLMode string SSLMode string
TimeZone string TimeZone string
} }
@@ -29,7 +29,7 @@ func (c *DatabaseConfig) LoadConfig() {
c.SSLMode = env.GetString("DB_SSL_MODE", "disable") c.SSLMode = env.GetString("DB_SSL_MODE", "disable")
c.TimeZone = env.GetString("DB_TIME_ZONE", "America/New_York") 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 { func (c *DatabaseConfig) GetDSN() string {

View File

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

View File

@@ -1,19 +1,19 @@
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 {
Host string Host string
Port int Port int
StrapiEndpoint string StrapiEndpoint string
RedisHost string RedisHost string
RedisPort int RedisPort int
StrapiToken string StrapiToken string
CacheTTL int CacheTTL int
EchoMode string EchoMode string
} }
func NewServerConfig() *ServerConfig { func NewServerConfig() *ServerConfig {

View File

@@ -18,14 +18,13 @@ 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
charmHandler := log.NewWithOptions(os.Stdout, log.Options{ charmHandler := log.NewWithOptions(os.Stdout, log.Options{
ReportCaller: false, ReportCaller: false,
ReportTimestamp: true, ReportTimestamp: true,
Prefix: "GORM", Prefix: "GORM",
}) })
slogger := slog.New(charmHandler) slogger := slog.New(charmHandler)
@@ -34,12 +33,12 @@ func Connect(dsn string) (*DB, error) {
gormLogger := logger.New( gormLogger := logger.New(
slog.NewLogLogger(slogger.Handler(), slog.LevelDebug), slog.NewLogLogger(slogger.Handler(), slog.LevelDebug),
logger.Config{ logger.Config{
SlowThreshold: time.Millisecond * 200, SlowThreshold: time.Millisecond * 200,
LogLevel: logger.Info, LogLevel: logger.Info,
IgnoreRecordNotFoundError: false, IgnoreRecordNotFoundError: false,
Colorful: true, Colorful: true,
}, },
) )
// Connect to database // Connect to database
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ 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 // 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, s *services.StrapiService) { func SetupRoutes(e *echo.Echo, sources Sources) {
if sources.StrapiService == nil {
log.Fatal("Error", "error", "strapi service is required")
}
// 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

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

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