Files
paliad/internal/handlers/redirects.go
m aef40bb425 feat(t-paliad-073): audit polish-2 DEFER list cleanup
Six findings from docs/audit-polish-2-2026-04-29.md DEFER list:

- F-23: hide STATUS column on /deadlines + /projects when every visible
  row shares the same status. Toggled at render time via a CSS class on
  the table; the column re-appears the moment filters re-introduce
  variety.
- F-32: agenda urgency pill now renders only when it disagrees with
  the day-bucket heading (e.g. an Überfällig deadline that lands in
  HEUTE through a filter quirk). Common case drops the redundant tag.
- F-38: bottom-nav agenda badge already counted overdue+today (the
  brief's option (b)); added a localized title + aria-label so the
  count's semantics ("X überfällig + Y heute fällig") is no longer
  ambiguous.
- F-40: glossary filter chips no longer mix EN+DE — DE shows
  "Streitsachen / Erteilungsverfahren / Allgemein", EN keeps
  "Litigation / Prosecution / General". Same i18n keys cover the
  Suggest-modal category dropdown.
- F-48: /projects/{id}/sub-projects now 301-redirects to the canonical
  /children URL via the existing redirects.go mechanism. Added a small
  redirects_test.go to lock the alias in.
- F-49: dropped the meta-circular 2026-04-22 "Neuigkeiten / What's New"
  changelog entry that referenced "this changelog" itself.

go build/vet/test clean, bun run build clean.

F-25 (mobile tables → card layout) is redesign-class and is scoped at
the bottom of the PR description as t-paliad-074, not implemented here.
2026-04-30 02:29:09 +02:00

64 lines
2.4 KiB
Go

package handlers
import (
"net/http"
"strings"
)
// registerLegacyRedirects wires every historical German URL path to its
// English successor as a 301 Moved Permanently. One entry per legacy prefix;
// sub-paths are preserved verbatim in the redirect target (e.g.
// /akten/abc/deadlines → /projects/abc/deadlines).
//
// Only GET is redirected — old POST/PATCH/DELETE API endpoints are not
// mirrored. API clients must update to the English /api/* paths.
func registerLegacyRedirects(mux *http.ServeMux) {
// Prefix pairs: when the request path starts with key, it's rewritten to
// value + whatever followed the key. Order does not matter because each
// prefix is registered as its own pattern on the mux.
prefixes := map[string]string{
"/akten": "/projects",
"/projekte": "/projects",
"/fristen": "/deadlines",
"/termine": "/appointments",
"/notizen": "/notes",
"/einstellungen": "/settings",
"/checklisten": "/checklists",
"/dezernate": "/admin/partner-units",
"/departments": "/admin/partner-units",
"/parteien": "/parties",
"/gerichte": "/courts",
"/glossar": "/glossary",
// Memorable aliases — sidebar uses the canonical path but users
// type these from memory and would otherwise hit the 404 chrome.
"/whatsnew": "/changelog",
}
for oldPrefix, newPrefix := range prefixes {
mux.Handle("GET "+oldPrefix, redirectPrefix(oldPrefix, newPrefix))
mux.Handle("GET "+oldPrefix+"/", redirectPrefix(oldPrefix, newPrefix))
}
// In-path aliases: redirect parameterised legacy URLs whose alias sits
// between path segments rather than at the prefix. The canonical project
// children tab is /projects/{id}/children; bookmarks to the older
// /sub-projects suffix would otherwise hit the 404 chrome.
mux.Handle("GET /projects/{id}/sub-projects", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
target := "/projects/" + r.PathValue("id") + "/children"
if r.URL.RawQuery != "" {
target += "?" + r.URL.RawQuery
}
http.Redirect(w, r, target, http.StatusMovedPermanently)
}))
}
func redirectPrefix(oldPrefix, newPrefix string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tail := strings.TrimPrefix(r.URL.Path, oldPrefix)
target := newPrefix + tail
if r.URL.RawQuery != "" {
target += "?" + r.URL.RawQuery
}
http.Redirect(w, r, target, http.StatusMovedPermanently)
})
}