// Package web implements VibeSonic's server-rendered Bootstrap 5 UI: // setup, authentication, library browsing, playback, and admin pages. package web import ( "database/sql" "log/slog" "net/http" "git.noroutes.net/NoRoutes/vibetunes/internal/auth" "git.noroutes.net/NoRoutes/vibetunes/internal/config" "git.noroutes.net/NoRoutes/vibetunes/internal/coverart" "git.noroutes.net/NoRoutes/vibetunes/internal/scanner" ) type Web struct { DB *sql.DB Config config.Config Sessions *auth.Sessions CSRF *auth.CSRF Middleware *auth.Middleware Scanner *scanner.Scanner CoverArt *coverart.Store Log *slog.Logger } // page renders a template with the standard envelope (current user, CSRF // token, active nav item) filled in. func (h *Web) page(w http.ResponseWriter, r *http.Request, name, active, title string, data any) { render(w, name, PageData{ Active: active, Title: title, User: auth.UserFromContext(r.Context()), CSRFToken: h.CSRF.Token(w, r), Data: data, }) } // safeRedirectPath returns next if it's a same-site relative path, or // "/" otherwise. Guards against open redirects: a bare "/foo" is fine, // but "//evil.com" or "https://evil.com" are protocol-relative/absolute // URLs a browser would follow off-site. func safeRedirectPath(next string) string { if next == "" || next[0] != '/' || len(next) > 1 && next[1] == '/' { return "/" } return next } func (h *Web) flash(w http.ResponseWriter, r *http.Request, name, active, title, flash, kind string, data any) { render(w, name, PageData{ Active: active, Title: title, User: auth.UserFromContext(r.Context()), CSRFToken: h.CSRF.Token(w, r), Flash: flash, FlashKind: kind, Data: data, }) }