Changed the templating engine to a custom one. First bugged working example
This commit is contained in:
17
.air.conf
Normal file
17
.air.conf
Normal 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
88
assets/styles.css
Normal 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
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
94
src/templates.go
Normal 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") // Go’s 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
|
||||||
|
}
|
||||||
17
src/utils.go
17
src/utils.go
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
1
tmp/build-errors.log
Normal 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
|
||||||
Reference in New Issue
Block a user