update redirects plug for custom prefixes
Path normalisation respects custom URL prefixes: - Orders only lowercase prefix, preserving case-sensitive order numbers - Distinguishes static shop routes from dynamic prefixed routes - Uses R module for prefix detection Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ecf84b81d1
commit
e9649218fb
@ -8,14 +8,17 @@ defmodule BerrypodWeb.Plugs.Redirects do
|
|||||||
3. Custom redirect lookup from the redirects table (ETS-cached)
|
3. Custom redirect lookup from the redirects table (ETS-cached)
|
||||||
|
|
||||||
All redirects preserve query params.
|
All redirects preserve query params.
|
||||||
|
|
||||||
|
Note: Order URLs only have their prefix lowercased, not the order number,
|
||||||
|
since order numbers contain uppercase hex characters (e.g. SS-260331-5D1A).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
alias Berrypod.Redirects
|
alias Berrypod.Redirects
|
||||||
|
alias BerrypodWeb.R
|
||||||
|
|
||||||
# Only case-normalise paths under these prefixes (SEO-relevant shop routes).
|
# Static prefixes that should always have the full path lowercased (page slugs)
|
||||||
# Paths with tokens, API keys, or other case-sensitive segments are excluded.
|
@static_lowercase_prefixes ~w(/about /delivery /privacy /terms /search /cart /contact /checkout)
|
||||||
@lowercase_prefixes ~w(/products /collections /about /delivery /privacy /terms /search /cart /contact)
|
|
||||||
|
|
||||||
def init(opts), do: opts
|
def init(opts), do: opts
|
||||||
|
|
||||||
@ -28,9 +31,9 @@ defmodule BerrypodWeb.Plugs.Redirects do
|
|||||||
stripped != path ->
|
stripped != path ->
|
||||||
redirect_to(conn, stripped, 301)
|
redirect_to(conn, stripped, 301)
|
||||||
|
|
||||||
# Case mismatch on a shop path — redirect to lowercase
|
# Case mismatch on a shop path — redirect to normalised form
|
||||||
lowercase_path?(path) and String.downcase(path) != path ->
|
(normalised = normalise_path(path)) != nil and normalised != path ->
|
||||||
redirect_to(conn, String.downcase(path), 301)
|
redirect_to(conn, normalised, 301)
|
||||||
|
|
||||||
# Check redirect table (ETS-cached)
|
# Check redirect table (ETS-cached)
|
||||||
:else ->
|
:else ->
|
||||||
@ -45,9 +48,50 @@ defmodule BerrypodWeb.Plugs.Redirects do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp lowercase_path?(path) do
|
# Normalise path case. Returns the normalised path or nil if no normalisation needed.
|
||||||
Enum.any?(@lowercase_prefixes, &String.starts_with?(String.downcase(path), &1))
|
# For order routes, only the prefix is lowercased (order numbers preserve case).
|
||||||
|
# For other routes, the entire path is lowercased.
|
||||||
|
defp normalise_path(path) do
|
||||||
|
lowered = String.downcase(path)
|
||||||
|
|
||||||
|
# Check static prefixes (full path lowercase)
|
||||||
|
if Enum.any?(@static_lowercase_prefixes, &String.starts_with?(lowered, &1)) do
|
||||||
|
lowered
|
||||||
|
else
|
||||||
|
# Check dynamic route prefixes
|
||||||
|
segments = path |> String.trim_leading("/") |> String.split("/")
|
||||||
|
first_segment = List.first(segments) || ""
|
||||||
|
prefix_type = R.prefix_type_from_segment(String.downcase(first_segment))
|
||||||
|
|
||||||
|
case prefix_type do
|
||||||
|
nil ->
|
||||||
|
# Not a dynamic route
|
||||||
|
nil
|
||||||
|
|
||||||
|
:orders ->
|
||||||
|
# Order routes: only lowercase the prefix, preserve order number case
|
||||||
|
normalise_order_path(segments)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
# Products/collections: lowercase entire path (slugs are always lowercase)
|
||||||
|
lowered
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# For order routes, only lowercase the first segment (the prefix)
|
||||||
|
defp normalise_order_path([prefix | rest]) do
|
||||||
|
lowered_prefix = String.downcase(prefix)
|
||||||
|
|
||||||
|
if lowered_prefix == prefix do
|
||||||
|
# Prefix already lowercase, no change needed
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
"/" <> Enum.join([lowered_prefix | rest], "/")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp normalise_order_path([]), do: nil
|
||||||
|
|
||||||
defp redirect_to(conn, target, status_code) do
|
defp redirect_to(conn, target, status_code) do
|
||||||
location = append_query(target, conn.query_string)
|
location = append_query(target, conn.query_string)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user