CI / test (push) Failing after 42s
People sign up on the website and link IMVU accounts by putting a code in the account's tagline, which the hub reads from IMVU's public API (the most recent proof wins; link caps per person). IMVU's age verification or Access Pass make an account adult. T5DE's proxy signs in with a device code and keeps a WebSocket open, reporting the IMVU account in use and the community products it wears; /online shows those who opted in. Moderators get a console: who's online, people, link caps, bans, roles, and the audit log. Tests run the website and API against an embedded Postgres and a fake IMVU API. Co-Authored-By: Claude Opus 5.5 <[email protected]>
83 lines
2.4 KiB
Go
83 lines
2.4 KiB
Go
package web
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"html/template"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// pageNames are the templates with a full page each; "layout" wraps them. Fragments (for htmx) are defined inside
|
|
// page files and rendered on their own.
|
|
var pageNames = []string{"home", "signin", "signup", "account", "device", "online", "console", "console_people",
|
|
"console_person", "error"}
|
|
|
|
var funcs = template.FuncMap{
|
|
"ago": func(t time.Time) string {
|
|
d := time.Since(t)
|
|
switch {
|
|
case d < time.Minute:
|
|
return "just now"
|
|
case d < time.Hour:
|
|
return fmt.Sprintf("%d min ago", int(d.Minutes()))
|
|
case d < 48*time.Hour:
|
|
return fmt.Sprintf("%d h ago", int(d.Hours()))
|
|
}
|
|
return t.Format("2 Jan 2006")
|
|
},
|
|
"date": func(t time.Time) string { return t.Format("2 Jan 2006 15:04") },
|
|
"upper": strings.ToUpper,
|
|
}
|
|
|
|
func (s *Server) parseTemplates() error {
|
|
s.pages = map[string]*template.Template{}
|
|
for _, name := range pageNames {
|
|
t, err := template.New("layout.html").Funcs(funcs).ParseFS(templateFiles, "templates/layout.html", "templates/"+name+".html")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.pages[name] = t
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// render shows a page in the layout. data is the page's own values; the layout gets the person and CSRF token.
|
|
func (s *Server) render(q *request, name string, data map[string]any) {
|
|
if data == nil {
|
|
data = map[string]any{}
|
|
}
|
|
data["Person"] = q.person
|
|
data["CSRF"] = q.csrf
|
|
data["Flash"] = q.flash
|
|
data["Page"] = name
|
|
var buf bytes.Buffer
|
|
if err := s.pages[name].ExecuteTemplate(&buf, "layout.html", data); err != nil {
|
|
s.Log.Printf("rendering %s: %v", name, err)
|
|
http.Error(q.w, "Something went wrong on our side.", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
q.w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
q.w.Header().Set("Cache-Control", "no-store")
|
|
buf.WriteTo(q.w)
|
|
}
|
|
|
|
// fragment shows one template from a page's file, without the layout (htmx swaps it in).
|
|
func (s *Server) fragment(q *request, page, name string, data map[string]any) {
|
|
if data == nil {
|
|
data = map[string]any{}
|
|
}
|
|
data["Person"] = q.person
|
|
data["CSRF"] = q.csrf
|
|
var buf bytes.Buffer
|
|
if err := s.pages[page].ExecuteTemplate(&buf, name, data); err != nil {
|
|
s.Log.Printf("rendering %s/%s: %v", page, name, err)
|
|
http.Error(q.w, "Something went wrong on our side.", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
q.w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
q.w.Header().Set("Cache-Control", "no-store")
|
|
buf.WriteTo(q.w)
|
|
}
|