- Normalize: https, www or bare host, double slashes and index files, trailing slash add/remove, optional ASCII lowercasing (defaults change nothing but scheme, slashes and index files) - strict URLs through signpost MatchPath: a path that names a record without being its address 301s to the record's address - resolve runs normalisation, redirects, strict URLs, then SEO; the location is the final address, so a visitor makes one hop - P1 warns when a record's canonical URL redirects
45 lines
1.7 KiB
Go
45 lines
1.7 KiB
Go
// 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=<path or absolute 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)
|
|
}
|
|
}
|