Changed the templating engine to a custom one. First bugged working example

This commit is contained in:
Jonas Hahn
2025-08-23 10:43:06 +02:00
parent 19e4834a9e
commit c9a3196ccb
15 changed files with 215 additions and 125 deletions

17
.air.conf Normal file
View File

@@ -0,0 +1,17 @@
root = "."
tmp_dir = "tmp"
[build]
cmd = "go build -o ./tmp/main ./src/main.go"
bin = "tmp/main"
full_bin = "tmp/main"
include_ext = ["go", "tpl", "tmpl", "html", "css", "js"]
exclude_dir = ["tmp", "vendor"]
[log]
time = true
[colors]
main = "yellow"
watcher = "cyan"
build = "green"

88
assets/styles.css Normal file
View File

@@ -0,0 +1,88 @@
body {
font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, "Helvetica Neue", Arial, sans-serif;
margin: 0;
padding: 0;
background: #f7f7f9;
color: #111
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 16px
}
.card {
background: #fff;
border-radius: 12px;
padding: 16px;
margin: 12px 0;
box-shadow: 0 2px 6px rgba(0, 0, 0, .06)
}
.row {
display: flex;
gap: 8px;
flex-wrap: wrap
}
.btn {
display: inline-block;
padding: 10px 14px;
border-radius: 10px;
border: 1px solid #ddd;
background: #fafafa;
text-decoration: none
}
.btn-primary {
background: #111;
color: #fff;
border-color: #111
}
.input {
width: 100%;
padding: 10px;
border-radius: 10px;
border: 1px solid #ddd
}
.label {
font-size: 12px;
color: #444;
margin-bottom: 6px;
display: block
}
.header {
padding: 12px 16px;
background: #fff;
position: sticky;
top: 0;
border-bottom: 1px solid #eee
}
.nav a {
margin-right: 12px;
text-decoration: none;
color: #111
}
.small {
font-size: 12px;
color: #666
}
.list li {
padding: 8px 0;
border-bottom: 1px solid #f0f0f0
}
.badge {
display: inline-block;
padding: 2px 8px;
border-radius: 999px;
background: #eee;
font-size: 12px
}

View File

@@ -10,8 +10,6 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
type ctxData map[string]any
func getIndex(c *gin.Context) { func getIndex(c *gin.Context) {
if getSessionUser(c) != nil { if getSessionUser(c) != nil {
c.Redirect(302, "/enter") c.Redirect(302, "/enter")
@@ -21,7 +19,7 @@ func getIndex(c *gin.Context) {
} }
func getLogin(c *gin.Context) { func getLogin(c *gin.Context) {
render(c, "login", ctxData{}) tm.Render(c, "login", gin.H{})
} }
func postLogin(c *gin.Context) { func postLogin(c *gin.Context) {
@@ -54,7 +52,7 @@ func postLogin(c *gin.Context) {
link := strings.TrimRight(baseURL, "/") + "/magic?token=" + token link := strings.TrimRight(baseURL, "/") + "/magic?token=" + token
log.Printf("[MAGIC LINK] %s for %s (valid until %s)\n", link, email, lt.ExpiresAt.Format(time.RFC3339)) log.Printf("[MAGIC LINK] %s for %s (valid until %s)\n", link, email, lt.ExpiresAt.Format(time.RFC3339))
render(c, "sent", ctxData{"Email": email}) tm.Render(c, "sent", gin.H{"Email": email})
} }
func getMagic(c *gin.Context) { func getMagic(c *gin.Context) {
@@ -98,7 +96,7 @@ func getEnter(c *gin.Context) {
if table != nil { if table != nil {
post = "/t/" + table.Slug + "/enter" post = "/t/" + table.Slug + "/enter"
} }
render(c, "enter", ctxData{"PostAction": post, "Table": table}) tm.Render(c, "enter", gin.H{"PostAction": post, "Table": table})
} }
func postEnter(c *gin.Context) { func postEnter(c *gin.Context) {
@@ -199,7 +197,7 @@ func getHistory(c *gin.Context) {
} }
rows = append(rows, GRow{Game: g, PlayersA: a, PlayersB: b}) rows = append(rows, GRow{Game: g, PlayersA: a, PlayersB: b})
} }
render(c, "history", ctxData{"Games": rows}) tm.Render(c, "history", gin.H{"Games": rows})
} }
func getLeaderboard(c *gin.Context) { func getLeaderboard(c *gin.Context) {
@@ -265,7 +263,7 @@ func getLeaderboard(c *gin.Context) {
rows = rows[:100] rows = rows[:100]
} }
render(c, "leaderboard", ctxData{"Rows": rows}) tm.Render(c, "leaderboard", gin.H{"Rows": rows})
} }
func getUserView(c *gin.Context) { func getUserView(c *gin.Context) {
@@ -313,7 +311,7 @@ func getUserView(c *gin.Context) {
own = true own = true
} }
render(c, "user", ctxData{"Viewed": u, "Stats": st, "Own": own}) tm.Render(c, "user", gin.H{"Viewed": u, "Stats": st, "Own": own})
} }
func getMe(c *gin.Context) { func getMe(c *gin.Context) {
@@ -322,7 +320,7 @@ func getMe(c *gin.Context) {
c.Redirect(302, "/login") c.Redirect(302, "/login")
return return
} }
render(c, "user", ctxData{"Viewed": cu, "Stats": struct{ Games, Wins, Losses int }{0, 0, 0}, "Own": true}) tm.Render(c, "user", gin.H{"Viewed": cu, "Stats": struct{ Games, Wins, Losses int }{0, 0, 0}, "Own": true})
} }
func postMe(c *gin.Context) { func postMe(c *gin.Context) {

View File

@@ -1,10 +1,8 @@
package main package main
import ( import (
"html/template"
"log" "log"
"os" "os"
"time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gorm.io/driver/postgres" "gorm.io/driver/postgres"
@@ -24,11 +22,7 @@ const (
PORT = "18765" PORT = "18765"
) )
// ===================== Templates ===================== var logger = log.Default()
var tpl = template.Must(template.New("").Funcs(template.FuncMap{
"fmtTime": func(t time.Time) string { return t.Local().Format("1000-01-01 10:10") },
}).ParseGlob("templates/*.html"))
// ===================== Main ===================== // ===================== Main =====================

94
src/templates.go Normal file
View File

@@ -0,0 +1,94 @@
package main
import (
"fmt"
"html/template"
"log"
"os"
"path/filepath"
"time"
"github.com/gin-gonic/gin"
)
var tm *TemplateManager
func init() {
tm = NewTemplateManager("templates", "base", "html")
}
type TemplateManager struct {
templates map[string]*template.Template
funcs template.FuncMap
base string
ext string
dir string
}
// NewTemplateManager initializes the manager and loads templates
func NewTemplateManager(dir string, base string, ext string) *TemplateManager {
tm := &TemplateManager{
templates: make(map[string]*template.Template),
funcs: template.FuncMap{
"fmtTime": func(t time.Time) string {
return t.Local().Format("2006-01-02 15:04") // Gos reference time
},
},
base: base,
dir: dir,
ext: ext,
}
tm.LoadTemplates()
return tm
}
// LoadTemplates parses the base template with each view template in the directory
func (tm *TemplateManager) LoadTemplates() {
pattern := filepath.Join(tm.dir, "*."+tm.ext)
files, err := filepath.Glob(pattern)
if err != nil {
panic(err)
}
base := filepath.Join(tm.dir, tm.base+"."+tm.ext)
for _, file := range files {
if filepath.Base(file) == tm.base {
continue
}
name := stripAfterDot(filepath.Base(file))
logger.Println(name)
// Parse base + view template together
tpl, err := template.New(name).
Funcs(tm.funcs).
ParseFiles(base, file)
if err != nil {
panic(err)
}
tm.templates[name] = tpl
}
}
// Render executes a template by name into the given context
func (tm *TemplateManager) Render(c *gin.Context, name string, data gin.H) error {
print("\nRendering template:", name, "\n")
u := getSessionUser(c)
data["CurrentUser"] = u
tpl, ok := tm.templates[name]
if !ok {
return os.ErrNotExist
}
fmt.Print(tm.templates[name])
if err := tpl.ExecuteTemplate(c.Writer, tm.base, data); err != nil {
log.Println("tpl error:", err)
c.Status(500)
return err
}
return nil
}

View File

@@ -4,11 +4,8 @@ import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"errors" "errors"
"log"
"strings" "strings"
"time" "time"
"github.com/gin-gonic/gin"
) )
func mustRandToken(n int) string { func mustRandToken(n int) string {
@@ -118,15 +115,9 @@ func fmtSscanf(s, _ string, a *int) (int, error) {
return 1, nil return 1, nil
} }
func render(c *gin.Context, name string, data ctxData) { func stripAfterDot(s string) string {
u := getSessionUser(c) if idx := strings.Index(s, "."); idx != -1 {
if data == nil { return s[:idx]
data = ctxData{}
}
log.Println("tpl error:", name)
data["CurrentUser"] = u
if err := tpl.ExecuteTemplate(c.Writer, name, data); err != nil {
log.Println("tpl error:", err)
c.Status(500)
} }
return s
} }

View File

@@ -11,96 +11,7 @@
<link rel="apple-touch-icon" href="/assets/favicon.ico"> <link rel="apple-touch-icon" href="/assets/favicon.ico">
<link rel="shortcut icon" href="/assets/favicon.ico"> <link rel="shortcut icon" href="/assets/favicon.ico">
<style> <link rel="stylesheet" href="/assets/styles.css">
body {
font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, "Helvetica Neue", Arial, sans-serif;
margin: 0;
padding: 0;
background: #f7f7f9;
color: #111
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 16px
}
.card {
background: #fff;
border-radius: 12px;
padding: 16px;
margin: 12px 0;
box-shadow: 0 2px 6px rgba(0, 0, 0, .06)
}
.row {
display: flex;
gap: 8px;
flex-wrap: wrap
}
.btn {
display: inline-block;
padding: 10px 14px;
border-radius: 10px;
border: 1px solid #ddd;
background: #fafafa;
text-decoration: none
}
.btn-primary {
background: #111;
color: #fff;
border-color: #111
}
.input {
width: 100%;
padding: 10px;
border-radius: 10px;
border: 1px solid #ddd
}
.label {
font-size: 12px;
color: #444;
margin-bottom: 6px;
display: block
}
.header {
padding: 12px 16px;
background: #fff;
position: sticky;
top: 0;
border-bottom: 1px solid #eee
}
.nav a {
margin-right: 12px;
text-decoration: none;
color: #111
}
.small {
font-size: 12px;
color: #666
}
.list li {
padding: 8px 0;
border-bottom: 1px solid #f0f0f0
}
.badge {
display: inline-block;
padding: 2px 8px;
border-radius: 999px;
background: #eee;
font-size: 12px
}
</style>
</head> </head>
<body> <body>
@@ -122,6 +33,7 @@
</div> </div>
</div> </div>
<div class="container"> <div class="container">
<!-- This is shorthand for a define along with a template -->
{{block "content" .}}{{end}} {{block "content" .}}{{end}}
</div> </div>
</body> </body>

View File

@@ -1,4 +1,4 @@
{{define "enter"}}{{template "base" .}}{{end}} {{define "title"}}Enter scores - QRank{{end}}
{{define "content"}} {{define "content"}}
<div class="card"> <div class="card">
<h2>Enter a game</h2> <h2>Enter a game</h2>

View File

@@ -1,4 +1,3 @@
{{define "history"}}{{template "base" .}}{{end}}
{{define "content"}} {{define "content"}}
<div class="card"> <div class="card">
<h2>Global history</h2> <h2>Global history</h2>

View File

@@ -1,4 +1,3 @@
{{define "leaderboard"}}{{template "base" .}}{{end}}
{{define "content"}} {{define "content"}}
<div class="card"> <div class="card">
<h2>Leaderboard (by wins)</h2> <h2>Leaderboard (by wins)</h2>

View File

@@ -1,4 +1,3 @@
{{define "login"}}{{template "base" .}}{{end}}
{{define "content"}} {{define "content"}}
<div class="card"> <div class="card">
<h2>Sign in with your email</h2> <h2>Sign in with your email</h2>

View File

@@ -1,4 +1,3 @@
{{define "sent"}}{{template "base" .}}{{end}}
{{define "content"}} {{define "content"}}
<div class="card"> <div class="card">
<h2>Check the server logs</h2> <h2>Check the server logs</h2>

View File

@@ -1,4 +1,3 @@
{{define "user"}}{{template "base" .}}{{end}}
{{define "content"}} {{define "content"}}
<div class="card"> <div class="card">
<h2>@{{.Viewed.Username}}</h2> <h2>@{{.Viewed.Username}}</h2>

1
tmp/build-errors.log Normal file
View File

@@ -0,0 +1 @@
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1

BIN
tmp/main Executable file
View File

Binary file not shown.