feat: redirects screen

- golem15.seo.redirects list and form (UI-SPEC C8): chain rows marked,
  status, match, origin and chain filters, the navigation item lands here
- a field-less extension on the controller refuses a loop with
  redirects.loop on target, answers the chain notice and records history
- the status dropdown posts text, read through RedirectStatus
This commit is contained in:
2026-10-27 11:30:56 +01:00
parent 34a65b846e
commit 163b0aaf98
15 changed files with 677 additions and 24 deletions

View File

@@ -452,7 +452,7 @@ type Chain struct {
// Follow follows the set from path and rawQuery, at most max hops. site is
// the site URL: an absolute target on it is followed like a path.
func (s *RedirectSet) Follow(site, path, rawQuery string, max int) (Chain, error) {
ch := Chain{Status: models.Status301}
ch := Chain{Status: int(models.Status301)}
cur, query := path, rawQuery
seen := map[string]bool{joinQuery(cur, query): true}
used := map[uint]bool{}
@@ -472,7 +472,7 @@ func (s *RedirectSet) Follow(site, path, rawQuery string, max int) (Chain, error
used[hit.Rule.ID] = true
ch.Hops = append(ch.Hops, hit)
if hit.Rule.Status == models.Status302 {
ch.Status = models.Status302
ch.Status = int(models.Status302)
}
if hit.Rule.Status == models.Status410 {
ch.Gone = true

View File

@@ -582,7 +582,7 @@ func (r *Resolver) redirectAnswer(self string, ch Chain) Result {
logRedirectCycle(self, ch)
res.Action, res.Status = ActionNotFound, 404
case ch.Gone:
res.Action, res.Status = ActionGone, models.Status410
res.Action, res.Status = ActionGone, int(models.Status410)
default:
res.Action, res.Status, res.Location = ActionRedirect, ch.Status, AbsoluteURL(r.Service.SiteURL(), ch.Final)
}