From 27fed0cb1610b3616def9f204700196f43fb6333 Mon Sep 17 00:00:00 2001 From: Wessie Date: Mon, 29 Apr 2024 16:14:41 +0100 Subject: [PATCH] website/api/v1: implement request route templates: added HasField method that indicates if the first argument has a field named after the second. util: implement RedirectToServer that uses http.ServerContextKey to get the *http.Server used for the request and passes its arguments back into its srv.Handler.ServeHTTP method. templates: theme middleware now uses util.RedirectToServer util: implement ChangeRequestMethod --- templates/functions.go | 8 +++++ templates/middleware.go | 8 ++--- util/util.go | 63 ++++++++++++++++++++++++++++++++++++---- website/api/v1/router.go | 1 + 4 files changed, 70 insertions(+), 10 deletions(-) diff --git a/templates/functions.go b/templates/functions.go index 87f87f6e..6920f4b6 100644 --- a/templates/functions.go +++ b/templates/functions.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "html/template" + "reflect" "time" radio "github.com/R-a-dio/valkyrie" @@ -30,6 +31,13 @@ var fnMap = map[string]any{ "Sub": func(a, b int64) int64 { return a - b }, "CalculateSubmissionCooldown": radio.CalculateSubmissionCooldown, "AllUserPermissions": radio.AllUserPermissions, + "HasField": HasField, +} + +func HasField(v any, name string) bool { + rv := reflect.ValueOf(v) + rv = reflect.Indirect(rv) + return rv.FieldByName(name).IsValid() } func PrintJSON(v any) (template.HTML, error) { diff --git a/templates/middleware.go b/templates/middleware.go index ca727691..50628b59 100644 --- a/templates/middleware.go +++ b/templates/middleware.go @@ -95,13 +95,11 @@ func SetThemeHandler(cookieName string, resolve func(string) string) http.Handle // and change the theme so the new page actually uses our new theme set r = r.WithContext(SetTheme(r.Context(), theme, true)) - srv := r.Context().Value(http.ServerContextKey) - if srv == nil { - hlog.FromRequest(r).Error().Msg("SetThemeHandler used with no server") + err := util.RedirectToServer(w, r) + if err != nil { + hlog.FromRequest(r).Error().Err(err).Msg("SetThemeHandler") w.WriteHeader(http.StatusOK) return } - - srv.(*http.Server).Handler.ServeHTTP(w, r) }) } diff --git a/util/util.go b/util/util.go index 78d1995e..b6b6b4c8 100644 --- a/util/util.go +++ b/util/util.go @@ -11,6 +11,7 @@ import ( "sync/atomic" "time" + "github.com/R-a-dio/valkyrie/errors" "github.com/R-a-dio/valkyrie/util/eventstream" "github.com/go-chi/chi/v5" "github.com/rs/zerolog" @@ -33,13 +34,21 @@ func IsHTMX(r *http.Request) bool { } func RedirectBack(r *http.Request) *http.Request { - current, err := url.Parse(r.Header.Get("Hx-Current-Url")) - if err == nil { - r.URL = current - } else { - current, err = url.Parse(r.Referer()) + var changed bool + + if hxHeader := r.Header.Get("Hx-Current-Url"); hxHeader != "" { + current, err := url.Parse(hxHeader) if err == nil { r.URL = current + changed = true + } + } + + if !changed { + current, err := url.Parse(r.Referer()) + if err == nil { + r.URL = current + changed = true } } @@ -56,6 +65,50 @@ func RedirectBack(r *http.Request) *http.Request { return r } +func ChangeRequestMethod(r *http.Request, method string) *http.Request { + r.Method = method + + rCtx := r.Context().Value(chi.RouteCtxKey) + if rCtx != nil { + if chiCtx, ok := rCtx.(*chi.Context); ok { + chiCtx.RouteMethod = method + } + } + + return r +} + +type alreadyRedirectedKey struct{} + +// RedirectToServer looks up the http.Server associated with this request +// and calls ServeHTTP again +func RedirectToServer(w http.ResponseWriter, r *http.Request) error { + const op errors.Op = "util.RedirectToServer" + ctx := r.Context() + + alreadyRedirected := ctx.Value(alreadyRedirectedKey{}) + if alreadyRedirected != nil { + return errors.E(op, "request was already redirected once") + } + + srv := ctx.Value(http.ServerContextKey) + if srv == nil { + return errors.E(op, "no server context key found") + } + + httpSrv, ok := srv.(*http.Server) + if !ok { + return errors.E(op, "server context key did not contain *http.Server") + } + + // add a context value so we know we've redirected internally + ctx = context.WithValue(r.Context(), alreadyRedirectedKey{}, struct{}{}) + + // and then send them off to be handled again + httpSrv.Handler.ServeHTTP(w, r.WithContext(ctx)) + return nil +} + func AbsolutePath(dir string, path string) string { if filepath.IsAbs(path) { return path diff --git a/website/api/v1/router.go b/website/api/v1/router.go index 9435794f..318562c7 100644 --- a/website/api/v1/router.go +++ b/website/api/v1/router.go @@ -62,4 +62,5 @@ func (a *API) Route(r chi.Router) { r.Get("/sse", a.sse.ServeHTTP) r.Get("/search", a.SearchHTML) r.Get("/song", a.GetSong) + r.Post("/request", a.PostRequest) }