diff --git a/js/_hole-common.tsx b/js/_hole-common.tsx index a59dd8d17..097beefcb 100644 --- a/js/_hole-common.tsx +++ b/js/_hole-common.tsx @@ -25,10 +25,14 @@ async function getHoleLangNotesContent(lang: string): Promise { } const renamedHoles: Record = { - 'eight-queens': 'n-queens', + 'billiard': 'billiards', + 'eight-queens': 'n-queens', 'factorial-factorisation-ascii': 'factorial-factorisation', - 'grid-packing': 'css-grid', - 'billiard': 'billiards', + 'grid-packing': 'css-grid', +}; + +const renamedLangs: Record = { + perl6: 'raku', }; export function init(_tabLayout: boolean, setSolution: any, setCodeForLangAndSolution: any, updateReadonlyPanels: any, getEditor: () => any) { @@ -68,9 +72,14 @@ export function init(_tabLayout: boolean, setSolution: any, setCodeForLangAndSol for (const [key, value] of Object.entries(localStorage)) { if (key.startsWith('code_')) { - const hole = key.split('_')[1]; - if (hole in renamedHoles) { - localStorage.setItem(key.replace(hole, renamedHoles[hole]), value); + const [prefix, hole, lang, scoring] = key.split('_'); + + const newHole = renamedHoles[hole] ?? hole; + const newLang = renamedLangs[lang] ?? lang; + + const newKey = [prefix, newHole, newLang, scoring].join('_'); + if (key !== newKey) { + localStorage.setItem(newKey, value); localStorage.removeItem(key); } } @@ -877,4 +886,4 @@ export function ctrlEnter(func: Function) { return func(); } }; -} \ No newline at end of file +} diff --git a/middleware/redir_holes_langs.go b/middleware/redir_holes_langs.go new file mode 100644 index 000000000..958d1b937 --- /dev/null +++ b/middleware/redir_holes_langs.go @@ -0,0 +1,42 @@ +package middleware + +import ( + "net/http" + "strings" +) + +// RedirHolesLangs redirects old values for {hole} and {lang}. +func RedirHolesLangs(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + newPath := r.URL.Path + + // FIXME Consider using chi.RouteContext(r.Context()).RoutePattern() + // and filling in hole/lang, more robust than strings.Replace(). + + switch r.PathValue("hole") { + case "billiard": + newPath = strings.Replace(newPath, "/billiard", "/billiards", 1) + case "eight-queens": + newPath = strings.Replace(newPath, "/eight-queens", "/n-queens", 1) + case "factorial-factorisation-ascii": + newPath = strings.Replace(newPath, "/factorial-factorisation-ascii", + "/factorial-factorisation", 1) + case "grid-packing": + newPath = strings.Replace(newPath, "/grid-packing", "/css-grid", 1) + } + + if r.PathValue("lang") == "perl6" { + newPath = strings.Replace(newPath, "/perl6", "/raku", 1) + } + + if newPath == r.URL.Path { + next.ServeHTTP(w, r) + } else { + if r.URL.RawQuery != "" { + newPath += "?" + r.URL.RawQuery + } + + http.Redirect(w, r, newPath, http.StatusPermanentRedirect) + } + }) +} diff --git a/routes/routes.go b/routes/routes.go index a31dd5ba6..525fd20fe 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -38,7 +38,7 @@ func Router(db *sqlx.DB) http.Handler { r.Get("/users/{name}", userGET) // HTML routes that need middleware.Golfer. - r.With(middleware.Golfer).Group(func(r chi.Router) { + r.With(middleware.RedirHolesLangs, middleware.Golfer).Group(func(r chi.Router) { r.Get("/", homeGET) r.Get("/{hole}", holeGET) r.Get("/about", aboutGET) @@ -55,24 +55,29 @@ func Router(db *sqlx.DB) http.Handler { r.Get("/cheevos/{cheevo}", apiCheevoGET) r.Get("/golfers/{golfer}", apiGolferGET) r.Get("/holes", apiHolesGET) - r.Get("/holes/{hole}", apiHoleGET) r.Get("/langs", apiLangsGET) - r.Get("/langs/{lang}", apiLangGET) - r.Get( - "/mini-rankings/{hole}/{lang}/{scoring:bytes|chars}/{view:top|me|following}", - apiMiniRankingsGET, - ) r.Get("/panic", apiPanicGET) r.Get("/solutions-log", apiSolutionsLogGET) r.Get("/suggestions/golfers", apiSuggestionsGolfersGET) r.Get("/wiki/*", apiWikiPageGET) - // API routes that require a logged-in golfer. - r.With(middleware.GolferArea).Group(func(r chi.Router) { - r.Get("/notes", apiNotesGET) - r.Delete("/notes/{hole}/{lang}", apiNoteDELETE) - r.Get("/notes/{hole}/{lang}", apiNoteGET) - r.Put("/notes/{hole}/{lang}", apiNotePUT) + // API routes that use {hole} or {lang}. + r.With(middleware.RedirHolesLangs).Group(func(r chi.Router) { + r.Get("/holes/{hole}", apiHoleGET) + r.Get("/langs/{lang}", apiLangGET) + r.Get( + "/mini-rankings/{hole}/{lang}/{scoring:bytes|chars}"+ + "/{view:top|me|following}", + apiMiniRankingsGET, + ) + + // API routes that require a logged-in golfer. + r.With(middleware.GolferArea).Group(func(r chi.Router) { + r.Get("/notes", apiNotesGET) + r.Delete("/notes/{hole}/{lang}", apiNoteDELETE) + r.Get("/notes/{hole}/{lang}", apiNoteGET) + r.Put("/notes/{hole}/{lang}", apiNotePUT) + }) }) }) r.Get("/callback", callbackGET) @@ -114,20 +119,24 @@ func Router(db *sqlx.DB) http.Handler { r.Get("/medals", redir("/rankings/medals/all/all/all")) r.Get("/solutions", redir("/rankings/misc/solutions")) - r.Get("/holes/{hole}/{lang}/{scoring}", rankingsHolesGET) - r.Get("/recent-holes/{lang}/{scoring}", rankingsHolesGET) + r.With(middleware.RedirHolesLangs).Group(func(r chi.Router) { + r.Get("/holes/{hole}/{lang}/{scoring}", rankingsHolesGET) + r.Get("/recent-holes/{lang}/{scoring}", rankingsHolesGET) - r.Get("/cheevos/{cheevo}", rankingsCheevosGET) - r.Get("/medals/{hole}/{lang}/{scoring}", rankingsMedalsGET) - r.Get("/langs/{lang}/{scoring}", rankingsLangsGET) - r.Get("/misc/{type}", rankingsMiscGET) + r.Get("/cheevos/{cheevo}", rankingsCheevosGET) + r.Get("/medals/{hole}/{lang}/{scoring}", rankingsMedalsGET) + r.Get("/langs/{lang}/{scoring}", rankingsLangsGET) + r.Get("/misc/{type}", rankingsMiscGET) + }) }) r.Route("/recent", func(r chi.Router) { r.Get("/", redir("/recent/solutions/all/all/bytes")) - r.Get("/{lang}", recentGET) - r.Get("/golfers", recentGolfersGET) - r.Get("/solutions/{hole}/{lang}/{scoring}", recentSolutionsGET) + + r.With(middleware.RedirHolesLangs).Group(func(r chi.Router) { + r.Get("/{lang}", recentGET) + r.Get("/solutions/{hole}/{lang}/{scoring}", recentSolutionsGET) + }) }) r.Get("/scores/{hole}/{lang}", scoresGET) r.Get("/scores/{hole}/{lang}/all", scoresAllGET) diff --git a/routes/scores.go b/routes/scores.go index 040519525..d2a07bbc8 100644 --- a/routes/scores.go +++ b/routes/scores.go @@ -45,14 +45,9 @@ func scoresGET(w http.ResponseWriter, r *http.Request) { if holeID == "all-holes" { holeID = "all" } - - switch langID { - case "all-langs": + if langID == "all-holes" { langID = "all" - case "perl6": - langID = "raku" } - if scoring == "" { scoring = "bytes" } diff --git a/t/redirects.t b/t/redirects.t index 54f2239b3..887dd9f39 100644 --- a/t/redirects.t +++ b/t/redirects.t @@ -1,9 +1,27 @@ -use t; +use HTTP::Tiny; +use Test; -constant $path = '/foo?bar=baz'; +my constant $query = '?foo=bar'; -# TODO -# is $ua->get( $_ . PATH )->{url}, 'https://code.golf' . PATH, $_ -# for ; +for < + GET /api/holes/billiard /api/holes/billiards + GET /api/langs/perl6 /api/langs/raku + DELETE /api/notes/billiard/perl6 /api/notes/billiards/raku + GET /api/notes/billiard/perl6 /api/notes/billiards/raku + PUT /api/notes/billiard/perl6 /api/notes/billiards/raku + GET /billiard /billiards + GET /rankings/recent-holes/perl6/bytes /rankings/recent-holes/raku/bytes + GET /recent/perl6 /recent/raku + GET /recent/solutions/billiard/perl6/bytes /recent/solutions/billiards/raku/bytes + GET /scores/billiard/perl6 /scores/billiards/raku +> -> $method, $start, $end { + state $ua = HTTP::Tiny.new :!max-redirect; + + my $location = $ua.request( + $method, "https://app:443$start$query", + ); + + is $location, $end ~ $query, "$method $start → $end"; +} done-testing;