// Package api holds the SEO plugin's JSON endpoints under /_seo/api/v1. package api import ( "errors" "net/http" "git.golem15.com/golem15/sm-seo-plugin/classes" "git.golem15.com/golem15/summercms/modules/diary" "git.golem15.com/golem15/summercms/modules/wire" ) var log = diary.For("seo") // Resolve serves GET /_seo/api/v1/resolve?url=: the // redirect decision and the effective SEO of a public URL for the // frontend (D-20a, D-103). It answers 200 with the resolve answer: action // redirect with status (301 or 302) and an absolute location, reached in // one hop through normalisation, redirects and strict URLs; gone with 410; // ok with the page's SEO; or not_found with 404, the status the page // should answer with. It answers 422 when url is missing or not a path or // an http(s) URL. Absolute URLs come from the configured site URL, never // from the request's Host. The frontend enforces the decision (Phase 33). func Resolve(service func() *classes.Service) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { svc := service() if svc == nil { wire.WriteJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "SEO is not available"}) return } res, err := svc.Resolver().Resolve(r.Context(), r.URL.Query().Get("url")) if errors.Is(err, classes.ErrBadURL) { wire.WriteJSON(w, http.StatusUnprocessableEntity, map[string]string{"error": err.Error()}) return } if err != nil { log.Error("golem15.seo: resolve", "error", err) wire.WriteJSON(w, http.StatusInternalServerError, map[string]string{"error": "Internal Server Error"}) return } w.Header().Set("X-Robots-Tag", "noindex") wire.WriteJSON(w, http.StatusOK, res) } }