develop #14
7 changed files with 704 additions and 114 deletions
2
.env-dev
2
.env-dev
|
|
@ -14,6 +14,8 @@ App_HTTPS_HOSTS=example.com, www.example.com
|
|||
App_REDIRECT_HTTP_TO_HTTPS=false
|
||||
App_CERT_FILE_PATH=tls/server.crt
|
||||
App_KEY_FILE_PATH=tls/server.key
|
||||
LOG_STDOUT_ENABLE=true
|
||||
LOG_LEVEL=debug
|
||||
|
||||
#######################################
|
||||
###### TEMPLATES ######
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"math/rand/v2"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.smarteching.com/goffee/core"
|
||||
"git.smarteching.com/goffee/core/template/components"
|
||||
|
|
@ -861,3 +862,140 @@ func getPaginatedPageViews(values [][]components.ContentTableTD, limit int, offs
|
|||
return values[offset:end] // Slice the array
|
||||
|
||||
}
|
||||
|
||||
// Custom Templates functions
|
||||
func TemplatesFunctions(c *core.Context) *core.Response {
|
||||
|
||||
// check if template engine is enabled
|
||||
TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE")
|
||||
if TemplateEnableStr == "" {
|
||||
TemplateEnableStr = "false"
|
||||
}
|
||||
TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr)
|
||||
|
||||
if TemplateEnable {
|
||||
loggr := c.GetLogger()
|
||||
|
||||
loggr.Debug("D e b u g")
|
||||
loggr.Info("I n f o")
|
||||
loggr.Warning("W a r n i n g")
|
||||
loggr.Error("E R R O R")
|
||||
|
||||
|
||||
tmplData := SamplePageData()
|
||||
return c.Response.Template("custom_templates_functions.html", tmplData)
|
||||
|
||||
} else {
|
||||
|
||||
message := "{\"message\": \"Error, template not enabled\"}"
|
||||
return c.Response.Json(message)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Author represents the writer of an article to show the custom functions
|
||||
type Author struct {
|
||||
Name string
|
||||
Avatar string
|
||||
Bio string
|
||||
}
|
||||
|
||||
// Article represents a blog post or news entry used to test template helpers to show the custom functions
|
||||
type Article struct {
|
||||
Title string
|
||||
Slug string
|
||||
Excerpt string
|
||||
Body string
|
||||
Tags []string
|
||||
PublishedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Author Author
|
||||
Views int
|
||||
Price float64
|
||||
Featured bool
|
||||
Subtitle string // intentionally left empty on some entries to test coalesce/defaultVal
|
||||
}
|
||||
|
||||
// PageData is the top-level context passed to the HTML template to show the custom functions
|
||||
type PageData struct {
|
||||
SiteTitle string
|
||||
Articles []Article
|
||||
}
|
||||
|
||||
// SamplePageData returns a populated PageData ready to be rendered by the template.
|
||||
func SamplePageData() PageData {
|
||||
return PageData{
|
||||
SiteTitle: "go/template lab",
|
||||
Articles: []Article{
|
||||
{
|
||||
Title: "getting started with go templates",
|
||||
Slug: "getting-started-go-templates",
|
||||
Excerpt: "Go's html/template package is both powerful and safe by default. In this article we explore how to extend it with custom FuncMap helpers that bring it closer to the expressiveness of Liquid or Twig, without sacrificing any of the security guarantees.",
|
||||
Tags: []string{"go", "templates", "web", "backend"},
|
||||
PublishedAt: time.Now().Add(-3 * 24 * time.Hour),
|
||||
UpdatedAt: time.Now().Add(-1 * 24 * time.Hour),
|
||||
Views: 14200,
|
||||
Price: 0,
|
||||
Featured: true,
|
||||
Subtitle: "",
|
||||
Author: Author{
|
||||
Name: "marina voss",
|
||||
Avatar: "MV",
|
||||
Bio: "Senior backend engineer focused on developer tooling and observability.",
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "building a blog engine in go",
|
||||
Slug: "blog-engine-go",
|
||||
Excerpt: "We walk through building a minimal but complete blog engine using only the Go standard library: routing with net/http, persistence with database/sql, and rendering with html/template.",
|
||||
Tags: []string{"go", "blog", "sqlite"},
|
||||
PublishedAt: time.Now().Add(-10 * 24 * time.Hour),
|
||||
UpdatedAt: time.Now().Add(-10 * 24 * time.Hour),
|
||||
Views: 8750,
|
||||
Price: 9.99,
|
||||
Featured: false,
|
||||
Subtitle: "A zero-dependency approach",
|
||||
Author: Author{
|
||||
Name: "rafael okonkwo",
|
||||
Avatar: "RO",
|
||||
Bio: "Full-stack engineer and open-source contributor. Writes about Go and distributed systems.",
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "concurrency patterns you should know",
|
||||
Slug: "concurrency-patterns-go",
|
||||
Excerpt: "Goroutines are cheap, but misusing them is expensive. This deep-dive covers fan-out/fan-in, pipelines, semaphores, and error group patterns with real production examples.",
|
||||
Tags: []string{"go", "concurrency", "goroutines", "advanced"},
|
||||
PublishedAt: time.Now().Add(-45 * 24 * time.Hour),
|
||||
UpdatedAt: time.Now().Add(-40 * 24 * time.Hour),
|
||||
Views: 31400,
|
||||
Price: 14.99,
|
||||
Featured: true,
|
||||
Subtitle: "",
|
||||
Author: Author{
|
||||
Name: "selin çelik",
|
||||
Avatar: "SÇ",
|
||||
Bio: "Systems programmer. Previously at Cloudflare. Loves writing about the internals of things.",
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "understanding go interfaces",
|
||||
Slug: "understanding-go-interfaces",
|
||||
Excerpt: "Interfaces in Go are implicit and structural, which makes them both elegant and occasionally surprising. We look at how the runtime dispatches method calls and how to design composable interfaces.",
|
||||
Tags: []string{"go", "interfaces", "design"},
|
||||
PublishedAt: time.Now().Add(-2 * time.Hour),
|
||||
UpdatedAt: time.Now().Add(-1 * time.Hour),
|
||||
Views: 420,
|
||||
Price: 0,
|
||||
Featured: false,
|
||||
Subtitle: "Implicit, structural, and powerful",
|
||||
Author: Author{
|
||||
Name: "marina voss",
|
||||
Avatar: "MV",
|
||||
Bio: "Senior backend engineer focused on developer tooling and observability.",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
47
go.mod
47
go.mod
|
|
@ -7,57 +7,58 @@ replace (
|
|||
git.smarteching.com/goffee/cup/models => ./models
|
||||
)
|
||||
|
||||
go 1.24.1
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
git.smarteching.com/goffee/core v1.9.5
|
||||
git.smarteching.com/goffee/core v1.9.6
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hibiken/asynq v0.25.1
|
||||
github.com/hibiken/asynq v0.26.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
gorm.io/gorm v1.30.0
|
||||
gorm.io/gorm v1.31.1
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
filippo.io/edwards25519 v1.2.0 // indirect
|
||||
git.smarteching.com/zeni/go-chart/v2 v2.1.4 // indirect
|
||||
git.smarteching.com/zeni/go-charts/v2 v2.6.11 // indirect
|
||||
github.com/SparkPost/gosparkpost v0.2.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.2 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.5 // indirect
|
||||
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect
|
||||
github.com/go-sql-driver/mysql v1.9.3 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.3 // indirect
|
||||
github.com/go-sql-driver/mysql v1.10.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/harranali/mailing v1.2.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.5 // indirect
|
||||
github.com/jackc/pgx/v5 v5.9.2 // indirect
|
||||
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/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailgun/errors v0.4.0 // indirect
|
||||
github.com/mailgun/errors v0.5.0 // indirect
|
||||
github.com/mailgun/mailgun-go/v4 v4.23.0 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.28 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.44 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/redis/go-redis/v9 v9.11.0 // indirect
|
||||
github.com/redis/go-redis/v9 v9.19.0 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/sendgrid/rest v2.6.9+incompatible // indirect
|
||||
github.com/sendgrid/sendgrid-go v3.16.1+incompatible // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/spf13/cast v1.9.2 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/image v0.29.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.50.0 // indirect
|
||||
golang.org/x/image v0.39.0 // indirect
|
||||
golang.org/x/net v0.53.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gorm.io/driver/mysql v1.6.0 // indirect
|
||||
gorm.io/driver/postgres v1.6.0 // indirect
|
||||
gorm.io/driver/sqlite v1.6.0 // indirect
|
||||
|
|
|
|||
145
go.sum
145
go.sum
|
|
@ -1,11 +1,11 @@
|
|||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
git.smarteching.com/goffee/core v1.9.2 h1:SpLAhsTxssPItgkBYLN3UxMH5s+q8qVtbvmRom3WKh8=
|
||||
git.smarteching.com/goffee/core v1.9.2/go.mod h1:L9a+kL1RVHRHzp+DzCS1apwVLyZAvGE6B94UlyIMhIg=
|
||||
git.smarteching.com/goffee/core v1.9.5 h1:rq6vI4WSUMGQNzJvhNWmtY2ycC7UszEvXpQ7uUR8sZY=
|
||||
git.smarteching.com/goffee/core v1.9.5/go.mod h1:ifiBgTOR4zCMzdGsabNrEO792EHny2o149NGe3TSlms=
|
||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
git.smarteching.com/goffee/core v1.9.6 h1:GY1EXqbmBEWZAVrl3q22Izb6aXhQzFVQBv2hWhK/So8=
|
||||
git.smarteching.com/goffee/core v1.9.6/go.mod h1:ifiBgTOR4zCMzdGsabNrEO792EHny2o149NGe3TSlms=
|
||||
git.smarteching.com/zeni/go-chart/v2 v2.1.4 h1:pF06+F6eqJLIG8uMiTVPR5TygPGMjM/FHMzTxmu5V/Q=
|
||||
git.smarteching.com/zeni/go-chart/v2 v2.1.4/go.mod h1:b3ueW9h3pGGXyhkormZAvilHaG4+mQti+bMNPdQBeOQ=
|
||||
git.smarteching.com/zeni/go-charts/v2 v2.6.11 h1:9udzlv3uxGXszpplfkL5IaTUrgkNj++KwhbaN1vVEqI=
|
||||
git.smarteching.com/zeni/go-charts/v2 v2.6.11/go.mod h1:3OpRPSXg7Qx4zcgsmwsC9ZFB9/wAkGSbnXf1wIbHYCg=
|
||||
github.com/SparkPost/gosparkpost v0.2.0 h1:yzhHQT7cE+rqzd5tANNC74j+2x3lrPznqPJrxC1yR8s=
|
||||
github.com/SparkPost/gosparkpost v0.2.0/go.mod h1:S9WKcGeou7cbPpx0kTIgo8Q69WZvUmVeVzbD+djalJ4=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
|
|
@ -23,46 +23,37 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
||||
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE=
|
||||
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-sql-driver/mysql v1.9.0 h1:Y0zIbQXhQKmQgTp44Y1dp3wTXcn804QoTptLZT1vtvo=
|
||||
github.com/go-sql-driver/mysql v1.9.0/go.mod h1:pDetrLJeA3oMujJuvXc8RJoasr589B6A9fwzD3QMrqw=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
|
||||
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
|
||||
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/harranali/mailing v1.2.0 h1:ihIyJwB8hyRVcdk+v465wk1PHMrSrgJqo/kMd+gZClY=
|
||||
github.com/harranali/mailing v1.2.0/go.mod h1:4a5N3yG98pZKluMpmcYlTtll7bisvOfGQEMIng3VQk4=
|
||||
github.com/hibiken/asynq v0.25.1 h1:phj028N0nm15n8O2ims+IvJ2gz4k2auvermngh9JhTw=
|
||||
github.com/hibiken/asynq v0.25.1/go.mod h1:pazWNOLBu0FEynQRBvHA26qdIKRSmfdIfUm4HdsLmXg=
|
||||
github.com/hibiken/asynq v0.26.0 h1:1Zxr92MlDnb1Zt/QR5g2vSCqUS03i95lUfqx5X7/wrw=
|
||||
github.com/hibiken/asynq v0.26.0/go.mod h1:Qk4e57bTnWDoyJ67VkchuV6VzSM9IQW2nPvAGuDyw58=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
|
||||
github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
|
||||
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
|
||||
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
|
||||
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||
|
|
@ -77,21 +68,21 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
|||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/mailgun/errors v0.4.0 h1:6LFBvod6VIW83CMIOT9sYNp28TCX0NejFPP4dSX++i8=
|
||||
github.com/mailgun/errors v0.4.0/go.mod h1:xGBaaKdEdQT0/FhwvoXv4oBaqqmVZz9P1XEnvD/onc0=
|
||||
github.com/mailgun/errors v0.5.0 h1:pLQo8uhAdORsjN69mGixSr0pGs46z/BW/FQXd8HG1VM=
|
||||
github.com/mailgun/errors v0.5.0/go.mod h1:+2nrgY77E0vDkG4ErehpcpbSkMLkseJzKbrva89WeSs=
|
||||
github.com/mailgun/mailgun-go/v4 v4.23.0 h1:jPEMJzzin2s7lvehcfv/0UkyBu18GvcURPr2+xtZRbk=
|
||||
github.com/mailgun/mailgun-go/v4 v4.23.0/go.mod h1:imTtizoFtpfZqPqGP8vltVBB6q9yWcv6llBhfFeElZU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
|
||||
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8=
|
||||
github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
|
@ -103,10 +94,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/redis/go-redis/v9 v9.7.1 h1:4LhKRCIduqXqtvCUlaq9c8bdHOkICjDMrr1+Zb3osAc=
|
||||
github.com/redis/go-redis/v9 v9.7.1/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||
github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs=
|
||||
github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/redis/go-redis/v9 v9.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k=
|
||||
github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
|
|
@ -114,81 +103,55 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f
|
|||
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
|
||||
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
|
||||
github.com/sendgrid/sendgrid-go v3.16.0+incompatible h1:i8eE6IMkiCy7vusSdacHHSBUpXyTcTXy/Rl9N9aZ/Qw=
|
||||
github.com/sendgrid/sendgrid-go v3.16.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
|
||||
github.com/sendgrid/sendgrid-go v3.16.1+incompatible h1:zWhTmB0Y8XCDzeWIm2/BIt1GjJohAA0p6hVEaDtHWWs=
|
||||
github.com/sendgrid/sendgrid-go v3.16.1+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
||||
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||
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.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
|
||||
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
|
||||
golang.org/x/image v0.29.0 h1:HcdsyR4Gsuys/Axh0rDEmlBmB68rW1U9BUdB3UVHsas=
|
||||
golang.org/x/image v0.29.0/go.mod h1:RVJROnf3SLK8d26OW91j4FrIHGbsJ8QnbEocVTOWQDA=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/image v0.39.0 h1:skVYidAEVKgn8lZ602XO75asgXBgLj9G/FE3RbuPFww=
|
||||
golang.org/x/image v0.39.0/go.mod h1:sIbmppfU+xFLPIG0FoVUTvyBMmgng1/XAMhQ2ft0hpA=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
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.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
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.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
|
||||
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
|
||||
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
|
||||
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
|
||||
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
|
|
|
|||
13
main.go
13
main.go
|
|
@ -58,6 +58,19 @@ func main() {
|
|||
app.SetGormConfig(config.GetGormConfig())
|
||||
app.SetCacheConfig(config.GetCacheConfig())
|
||||
app.Bootstrap()
|
||||
// Set log level from environment variable (debug, info, warning, error)
|
||||
if levelStr := env.GetVar("LOG_LEVEL"); levelStr != "" {
|
||||
switch levelStr {
|
||||
case "debug":
|
||||
logger.ResolveLogger().SetLevel(logger.DEBUG)
|
||||
case "info":
|
||||
logger.ResolveLogger().SetLevel(logger.INFO)
|
||||
case "warning":
|
||||
logger.ResolveLogger().SetLevel(logger.WARNING)
|
||||
case "error":
|
||||
logger.ResolveLogger().SetLevel(logger.ERROR)
|
||||
}
|
||||
}
|
||||
app.RegisterTemplates(resources)
|
||||
registerGlobalHooks()
|
||||
registerRoutes()
|
||||
|
|
|
|||
|
|
@ -60,4 +60,6 @@ func registerRoutes() {
|
|||
|
||||
controller.Get("/appsession", controllers.AppSession)
|
||||
controller.Post("/appsession", controllers.AppSession)
|
||||
|
||||
controller.Get("/templatesfunc", controllers.TemplatesFunctions)
|
||||
}
|
||||
|
|
|
|||
471
storage/templates/custom_templates_functions.html
Normal file
471
storage/templates/custom_templates_functions.html
Normal file
|
|
@ -0,0 +1,471 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{{.SiteTitle}} — template lab</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,700;1,400&family=JetBrains+Mono:wght@400;500&family=DM+Sans:wght@300;400;500&display=swap" rel="stylesheet" />
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--bg: #0d0d0f;
|
||||
--surface: #141416;
|
||||
--border: #222226;
|
||||
--muted: #3a3a40;
|
||||
--subtle: #6b6b75;
|
||||
--body: #c8c8d0;
|
||||
--heading: #f0f0f4;
|
||||
--accent: #e8c547;
|
||||
--accent2: #5b8cff;
|
||||
--danger: #ff6b6b;
|
||||
--mono: 'JetBrains Mono', monospace;
|
||||
--serif: 'Playfair Display', Georgia, serif;
|
||||
--sans: 'DM Sans', sans-serif;
|
||||
}
|
||||
|
||||
html { background: var(--bg); color: var(--body); font-family: var(--sans); font-size: 16px; line-height: 1.6; }
|
||||
|
||||
body { max-width: 1100px; margin: 0 auto; padding: 0 2rem 6rem; }
|
||||
|
||||
/* ── Header ── */
|
||||
header {
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 3rem 0 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
gap: 1rem;
|
||||
}
|
||||
.site-title {
|
||||
font-family: var(--mono);
|
||||
font-size: .75rem;
|
||||
letter-spacing: .2em;
|
||||
text-transform: uppercase;
|
||||
color: var(--accent);
|
||||
}
|
||||
.site-headline {
|
||||
font-family: var(--serif);
|
||||
font-size: clamp(2rem, 5vw, 3.5rem);
|
||||
color: var(--heading);
|
||||
line-height: 1.1;
|
||||
margin-top: .4rem;
|
||||
}
|
||||
.site-headline em { color: var(--accent); font-style: italic; }
|
||||
.helper-count {
|
||||
font-family: var(--mono);
|
||||
font-size: .7rem;
|
||||
color: var(--subtle);
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ── Section label ── */
|
||||
.section-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: 4rem 0 1.5rem;
|
||||
}
|
||||
.section-label span {
|
||||
font-family: var(--mono);
|
||||
font-size: .65rem;
|
||||
letter-spacing: .15em;
|
||||
text-transform: uppercase;
|
||||
color: var(--accent);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.section-label::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--border);
|
||||
}
|
||||
|
||||
/* ── Helper badge ── */
|
||||
.badge {
|
||||
display: inline-block;
|
||||
font-family: var(--mono);
|
||||
font-size: .6rem;
|
||||
letter-spacing: .08em;
|
||||
padding: .15em .5em;
|
||||
border-radius: 3px;
|
||||
border: 1px solid var(--muted);
|
||||
color: var(--subtle);
|
||||
vertical-align: middle;
|
||||
margin-left: .4rem;
|
||||
}
|
||||
.badge-accent { border-color: var(--accent); color: var(--accent); }
|
||||
.badge-blue { border-color: var(--accent2); color: var(--accent2); }
|
||||
|
||||
/* ── Demo block ── */
|
||||
.demo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 1px;
|
||||
background: var(--border);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.demo-cell {
|
||||
background: var(--surface);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
.demo-cell h3 {
|
||||
font-family: var(--mono);
|
||||
font-size: .7rem;
|
||||
letter-spacing: .1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--accent);
|
||||
margin-bottom: .75rem;
|
||||
}
|
||||
.demo-cell .result {
|
||||
font-size: 1rem;
|
||||
color: var(--heading);
|
||||
}
|
||||
.demo-cell .note {
|
||||
margin-top: .5rem;
|
||||
font-size: .75rem;
|
||||
color: var(--subtle);
|
||||
font-family: var(--mono);
|
||||
}
|
||||
|
||||
/* ── Article cards ── */
|
||||
.article-list { display: flex; flex-direction: column; gap: 1px; background: var(--border); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
|
||||
|
||||
.article-card { background: var(--surface); padding: 2rem; display: grid; grid-template-columns: 1fr auto; gap: 1rem; align-items: start; transition: background .15s; }
|
||||
.article-card:hover { background: #18181c; }
|
||||
|
||||
.card-meta { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; margin-bottom: .75rem; }
|
||||
.tag {
|
||||
font-family: var(--mono);
|
||||
font-size: .6rem;
|
||||
letter-spacing: .08em;
|
||||
text-transform: uppercase;
|
||||
padding: .2em .55em;
|
||||
background: var(--muted);
|
||||
border-radius: 3px;
|
||||
color: var(--body);
|
||||
}
|
||||
.tag-featured { background: color-mix(in srgb, var(--accent) 15%, transparent); color: var(--accent); }
|
||||
|
||||
.card-title { font-family: var(--serif); font-size: 1.35rem; color: var(--heading); line-height: 1.25; margin-bottom: .5rem; }
|
||||
.card-subtitle { font-size: .85rem; color: var(--accent2); margin-bottom: .6rem; font-style: italic; }
|
||||
.card-excerpt { font-size: .875rem; color: var(--body); line-height: 1.65; }
|
||||
|
||||
.card-author { display: flex; align-items: center; gap: .6rem; margin-top: 1.25rem; }
|
||||
.avatar {
|
||||
width: 32px; height: 32px; border-radius: 50%;
|
||||
background: var(--muted);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-family: var(--mono); font-size: .55rem; color: var(--accent);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.author-name { font-size: .8rem; color: var(--heading); }
|
||||
.author-date { font-size: .72rem; color: var(--subtle); font-family: var(--mono); }
|
||||
|
||||
.card-aside { text-align: right; }
|
||||
.views { font-family: var(--mono); font-size: .75rem; color: var(--subtle); white-space: nowrap; }
|
||||
.views strong { color: var(--body); display: block; font-size: 1rem; }
|
||||
.price-badge {
|
||||
display: inline-block; margin-top: .75rem;
|
||||
font-family: var(--mono); font-size: .7rem;
|
||||
padding: .3em .7em; border-radius: 4px;
|
||||
background: color-mix(in srgb, var(--accent2) 15%, transparent);
|
||||
color: var(--accent2); border: 1px solid var(--accent2);
|
||||
}
|
||||
.price-free {
|
||||
background: color-mix(in srgb, #4caf50 12%, transparent);
|
||||
color: #6fcf97; border-color: #4caf50;
|
||||
}
|
||||
|
||||
/* ── Logic demo table ── */
|
||||
.logic-table { width: 100%; border-collapse: collapse; font-size: .85rem; }
|
||||
.logic-table th {
|
||||
font-family: var(--mono); font-size: .65rem; letter-spacing: .1em; text-transform: uppercase;
|
||||
color: var(--subtle); text-align: left; padding: .6rem 1rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.logic-table td { padding: .75rem 1rem; border-bottom: 1px solid var(--border); color: var(--body); }
|
||||
.logic-table tr:last-child td { border-bottom: none; }
|
||||
.logic-table tr:hover td { background: var(--surface); }
|
||||
.val { font-family: var(--mono); font-size: .8rem; color: var(--accent); }
|
||||
|
||||
/* ── Footer ── */
|
||||
footer {
|
||||
margin-top: 5rem; padding-top: 2rem; border-top: 1px solid var(--border);
|
||||
font-family: var(--mono); font-size: .65rem; color: var(--muted);
|
||||
display: flex; justify-content: space-between; flex-wrap: wrap; gap: .5rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ════════════════════════════════════════════
|
||||
HEADER
|
||||
════════════════════════════════════════════ -->
|
||||
<header>
|
||||
<div>
|
||||
<p class="site-title">{{.SiteTitle}}</p>
|
||||
<h1 class="site-headline">Template <em>helpers</em><br>in action</h1>
|
||||
</div>
|
||||
<p class="helper-count">22 helpers registered<br>across 6 groups</p>
|
||||
</header>
|
||||
|
||||
|
||||
<!-- ════════════════════════════════════════════
|
||||
STRING HELPERS
|
||||
════════════════════════════════════════════ -->
|
||||
<div class="section-label"><span>String helpers</span></div>
|
||||
|
||||
{{$first := first .Articles}}
|
||||
|
||||
<div class="demo-grid">
|
||||
|
||||
<div class="demo-cell">
|
||||
<h3>capitalize <span class="badge">capitalize</span></h3>
|
||||
<p class="result">{{capitalize $first.Title}}</p>
|
||||
<p class="note">input: "{{$first.Title}}"</p>
|
||||
</div>
|
||||
|
||||
<div class="demo-cell">
|
||||
<h3>truncate <span class="badge">truncate</span></h3>
|
||||
<p class="result">{{$first.Excerpt | truncate 80}}</p>
|
||||
<p class="note">capped at 80 chars</p>
|
||||
</div>
|
||||
|
||||
<div class="demo-cell">
|
||||
<h3>prepend <span class="badge">prepend</span></h3>
|
||||
<p class="result">{{prepend $first.Slug "/articles/"}}</p>
|
||||
<p class="note">prepended "/articles/" to slug</p>
|
||||
</div>
|
||||
|
||||
<div class="demo-cell">
|
||||
<h3>strAppend <span class="badge">strAppend</span></h3>
|
||||
<p class="result">{{strAppend $first.Slug ".html"}}</p>
|
||||
<p class="note">appended ".html" to slug</p>
|
||||
</div>
|
||||
|
||||
<div class="demo-cell">
|
||||
<h3>split → join <span class="badge">split</span> <span class="badge">join</span></h3>
|
||||
{{$parts := split "go,templates,funcmap" ","}}
|
||||
<p class="result">{{join $parts " · "}}</p>
|
||||
<p class="note">split on "," then joined with " · "</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ════════════════════════════════════════════
|
||||
NUMBER & DATE HELPERS
|
||||
════════════════════════════════════════════ -->
|
||||
<div class="section-label"><span>Number & Date helpers</span></div>
|
||||
|
||||
<div class="demo-grid">
|
||||
|
||||
<div class="demo-cell">
|
||||
<h3>fmtNumber — int <span class="badge">fmtNumber</span></h3>
|
||||
<p class="result">{{fmtNumber $first.Views}}</p>
|
||||
<p class="note">raw value: {{$first.Views}}</p>
|
||||
</div>
|
||||
|
||||
{{$paid := index .Articles 2}}
|
||||
<div class="demo-cell">
|
||||
<h3>fmtNumber — float <span class="badge">fmtNumber</span></h3>
|
||||
<p class="result">$ {{fmtNumber $paid.Price}}</p>
|
||||
<p class="note">raw value: {{$paid.Price}}</p>
|
||||
</div>
|
||||
|
||||
<div class="demo-cell">
|
||||
<h3>fmtDate "short" <span class="badge">fmtDate</span></h3>
|
||||
<p class="result">{{fmtDate $first.PublishedAt "short"}}</p>
|
||||
<p class="note">layout: "02 Jan 2006"</p>
|
||||
</div>
|
||||
|
||||
<div class="demo-cell">
|
||||
<h3>fmtDate "long" <span class="badge">fmtDate</span></h3>
|
||||
<p class="result">{{fmtDate $first.PublishedAt "long"}}</p>
|
||||
<p class="note">layout: "02 January 2006"</p>
|
||||
</div>
|
||||
|
||||
<div class="demo-cell">
|
||||
<h3>fmtDate "iso" <span class="badge">fmtDate</span></h3>
|
||||
<p class="result">{{fmtDate $first.PublishedAt "iso"}}</p>
|
||||
<p class="note">layout: "2006-01-02"</p>
|
||||
</div>
|
||||
|
||||
<div class="demo-cell">
|
||||
<h3>timeAgo <span class="badge">timeAgo</span></h3>
|
||||
{{range .Articles}}
|
||||
<p class="result" style="margin-bottom:.3rem">{{timeAgo .PublishedAt}} <span class="note" style="display:inline">— {{fmtDate .PublishedAt "short"}}</span></p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ════════════════════════════════════════════
|
||||
COLLECTION HELPERS
|
||||
════════════════════════════════════════════ -->
|
||||
<div class="section-label"><span>Collection helpers</span></div>
|
||||
|
||||
<div class="demo-grid">
|
||||
|
||||
<div class="demo-cell">
|
||||
<h3>first <span class="badge">first</span></h3>
|
||||
{{with first .Articles}}
|
||||
<p class="result">{{capitalize .Title}}</p>
|
||||
<p class="note">first article in the list</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="demo-cell">
|
||||
<h3>last <span class="badge">last</span></h3>
|
||||
{{with last .Articles}}
|
||||
<p class="result">{{capitalize .Title}}</p>
|
||||
<p class="note">last article in the list</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="demo-cell">
|
||||
<h3>sliceOf 0–2 <span class="badge">sliceOf</span></h3>
|
||||
{{range sliceOf .Articles 0 2}}
|
||||
<p class="result" style="margin-bottom:.25rem">— {{capitalize .Title}}</p>
|
||||
{{end}}
|
||||
<p class="note">only first 2 of {{len .Articles}} articles</p>
|
||||
</div>
|
||||
|
||||
<div class="demo-cell">
|
||||
<h3>contains — slice <span class="badge">contains</span></h3>
|
||||
{{if contains $first.Tags "go"}}
|
||||
<p class="result" style="color:var(--accent)">✓ has tag "go"</p>
|
||||
{{else}}
|
||||
<p class="result" style="color:var(--danger)">✗ missing tag "go"</p>
|
||||
{{end}}
|
||||
<p class="note">tags: {{join $first.Tags ", "}}</p>
|
||||
</div>
|
||||
|
||||
<div class="demo-cell">
|
||||
<h3>contains — string <span class="badge">contains</span></h3>
|
||||
{{if contains $first.Excerpt "FuncMap"}}
|
||||
<p class="result" style="color:var(--accent)">✓ excerpt mentions "FuncMap"</p>
|
||||
{{else}}
|
||||
<p class="result" style="color:var(--danger)">✗ not found</p>
|
||||
{{end}}
|
||||
<p class="note">substring search on .Excerpt</p>
|
||||
</div>
|
||||
|
||||
<div class="demo-cell">
|
||||
<h3>join <span class="badge">join</span></h3>
|
||||
<p class="result">{{join $first.Tags " / "}}</p>
|
||||
<p class="note">tags joined with " / "</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ════════════════════════════════════════════
|
||||
LOGIC HELPERS
|
||||
════════════════════════════════════════════ -->
|
||||
<div class="section-label"><span>Logic helpers</span></div>
|
||||
|
||||
<table class="logic-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Helper</th>
|
||||
<th>Article</th>
|
||||
<th>Input</th>
|
||||
<th>Output</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Articles}}
|
||||
<tr>
|
||||
<td><span class="badge badge-accent">defaultVal</span></td>
|
||||
<td>{{.Title | capitalize | truncate 30}}</td>
|
||||
<td class="val">.Subtitle ({{if .Subtitle}}"{{.Subtitle}}"{{else}}empty{{end}})</td>
|
||||
<td class="val">{{defaultVal "No subtitle" .Subtitle}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="badge badge-blue">ternary</span></td>
|
||||
<td>{{capitalize .Title | truncate 30}}</td>
|
||||
<td class="val">.Featured = {{.Featured}}</td>
|
||||
<td class="val">{{ternary "⭐ featured" "regular" .Featured}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="badge badge-accent">coalesce</span></td>
|
||||
<td>{{capitalize .Title | truncate 30}}</td>
|
||||
<td class="val">.Subtitle → .Title</td>
|
||||
<td class="val">{{coalesce .Subtitle .Title}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<!-- ════════════════════════════════════════════
|
||||
FULL ARTICLE CARDS (all helpers combined)
|
||||
════════════════════════════════════════════ -->
|
||||
<div class="section-label"><span>Full article cards — all helpers combined</span></div>
|
||||
|
||||
<div class="article-list">
|
||||
{{range .Articles}}
|
||||
<div class="article-card">
|
||||
<div>
|
||||
<div class="card-meta">
|
||||
{{if .Featured}}<span class="tag tag-featured">featured</span>{{end}}
|
||||
{{range .Tags}}<span class="tag">{{.}}</span>{{end}}
|
||||
</div>
|
||||
|
||||
<h2 class="card-title">{{capitalize .Title}}</h2>
|
||||
|
||||
{{with coalesce .Subtitle ""}}
|
||||
<p class="card-subtitle">{{.}}</p>
|
||||
{{end}}
|
||||
|
||||
<p class="card-excerpt">{{.Excerpt | truncate 160}}</p>
|
||||
|
||||
<a style="font-family:var(--mono);font-size:.7rem;color:var(--accent2);text-decoration:none;margin-top:.75rem;display:inline-block;"
|
||||
href="{{prepend .Slug "/articles/"}}">
|
||||
{{prepend .Slug "/articles/"}} →
|
||||
</a>
|
||||
|
||||
<div class="card-author">
|
||||
<div class="avatar">{{.Author.Avatar}}</div>
|
||||
<div>
|
||||
<div class="author-name">{{capitalize .Author.Name}}</div>
|
||||
<div class="author-date">
|
||||
{{fmtDate .PublishedAt "short"}} · {{timeAgo .PublishedAt}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-aside">
|
||||
<div class="views">
|
||||
<strong>{{fmtNumber .Views}}</strong>
|
||||
views
|
||||
</div>
|
||||
{{if gt .Price 0.0}}
|
||||
<span class="price-badge">$ {{fmtNumber .Price}}</span>
|
||||
{{else}}
|
||||
<span class="price-badge price-free">free</span>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ════════════════════════════════════════════
|
||||
FOOTER
|
||||
════════════════════════════════════════════ -->
|
||||
<footer>
|
||||
<span>{{.SiteTitle}} / template sandbox</span>
|
||||
<span>{{len .Articles}} articles · {{fmtDate (first .Articles).PublishedAt "iso"}} → {{fmtDate (last .Articles).PublishedAt "iso"}}</span>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue