Hub: accounts, IMVU linking by tagline, proxy sign-in, presence, console
CI / test (push) Failing after 42s
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]>
This commit is contained in:
1 parent
54e569bea3
commit
b9dc3f0ec1
36 files changed
+3548
-5
No files matched your search
@@ -0,0 +1,82 @@
|
||||
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)
|
||||
}
|
||||
Reference in new issue
Block a user