104 lines
3.1 KiB
Go
104 lines
3.1 KiB
Go
// Package handlers wires templates, auth, and database queries for the
|
|
// WhereWoof GOAT front-end.
|
|
package handlers
|
|
|
|
import (
|
|
"html/template"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"wherewoof/frontend/internal/auth"
|
|
"wherewoof/frontend/internal/db"
|
|
)
|
|
|
|
// Templates maps a page key to its parsed template set (base + page + partials).
|
|
// Each page is parsed as its own set so the shared "content" block name
|
|
// doesn't collide across pages.
|
|
type Templates map[string]*template.Template
|
|
|
|
// App holds dependencies shared by all handlers.
|
|
type App struct {
|
|
Queries *db.Queries
|
|
Tpl Templates
|
|
}
|
|
|
|
// New returns an App with the given query layer and template sets.
|
|
func New(queries *db.Queries, tpl Templates) *App {
|
|
return &App{Queries: queries, Tpl: tpl}
|
|
}
|
|
|
|
// PageData is the root data passed to the base layout.
|
|
type PageData struct {
|
|
CurrentUser *db.User
|
|
Title string
|
|
Error string
|
|
Data any
|
|
}
|
|
|
|
func titleCase(s string) string {
|
|
if s == "" {
|
|
return s
|
|
}
|
|
return strings.ToUpper(s[:1]) + s[1:]
|
|
}
|
|
|
|
// LoadTemplates parses every page's template set from templates/.
|
|
func LoadTemplates() (Templates, error) {
|
|
const dir = "templates"
|
|
base := dir + "/base.html"
|
|
pages := map[string][]string{
|
|
"index": {dir + "/index.html"},
|
|
"register": {dir + "/register.html"},
|
|
"login": {dir + "/login.html"},
|
|
"account": {dir + "/account.html", dir + "/account-panel.html", dir + "/tag-list.html"},
|
|
"edit": {dir + "/tag-edit.html"},
|
|
"public": {dir + "/tag-public.html"},
|
|
"notfound": {dir + "/not-found.html"},
|
|
}
|
|
funcs := template.FuncMap{"title": titleCase}
|
|
tpl := make(Templates, len(pages))
|
|
for name, files := range pages {
|
|
paths := append([]string{base}, files...)
|
|
t, err := template.New(name).Funcs(funcs).ParseFiles(paths...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tpl[name] = t
|
|
}
|
|
return tpl, nil
|
|
}
|
|
|
|
// render executes the page's base layout with a PageData populated from the
|
|
// authenticated user (if any).
|
|
func (a *App) render(w http.ResponseWriter, r *http.Request, page, title string, data any, errMsg string) {
|
|
pd := PageData{Title: title, Data: data, Error: errMsg}
|
|
if uid, ok := auth.GetUserID(r); ok {
|
|
if u, err := a.Queries.GetUserByID(r.Context(), uid); err == nil {
|
|
pd.CurrentUser = &u
|
|
}
|
|
}
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
if err := a.Tpl[page].ExecuteTemplate(w, "base", pd); err != nil {
|
|
http.Error(w, "template error: "+err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
// renderPartial executes a named partial (e.g. "account-panel") for HTMX swaps.
|
|
func (a *App) renderPartial(w http.ResponseWriter, page, partial string, data any) {
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
if err := a.Tpl[page].ExecuteTemplate(w, partial, data); err != nil {
|
|
http.Error(w, "template error: "+err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
// RequireAuth redirects unauthenticated requests to /login.
|
|
func (a *App) RequireAuth(next http.HandlerFunc) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := auth.GetUserID(r); !ok {
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
next(w, r)
|
|
}
|
|
}
|