From a3c4565034f7f8403304e4fb14360f5a1cd4a33a Mon Sep 17 00:00:00 2001 From: makearmy Date: Sat, 27 Sep 2025 17:59:52 -0400 Subject: [PATCH] middleware update --- middleware.ts | 87 +++++++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 37 deletions(-) diff --git a/middleware.ts b/middleware.ts index 9c46dd19..77bb59c3 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,10 +1,18 @@ // middleware.ts import { NextResponse, NextRequest } from "next/server"; -const PUBLIC_PATHS = new Set([ - "/auth/sign-in", - "/auth/sign-up", -]); +const PUBLIC_PATHS = new Set(["/auth/sign-in", "/auth/sign-up"]); + +/** Helper: are we about to redirect to the same URL? */ +function isSameUrl(req: NextRequest, mapped: MapResult) { + const dest = new URL(req.url); + dest.pathname = mapped.pathname; + if (mapped.query) { + // start from existing params so we preserve any others + for (const [k, v] of Object.entries(mapped.query)) dest.searchParams.set(k, v); + } + return dest.href === req.url; +} export function middleware(req: NextRequest) { const url = req.nextUrl.clone(); @@ -12,9 +20,9 @@ export function middleware(req: NextRequest) { // ── 1) Legacy → Portal / Canonical mapping (runs before auth gating) const mapped = legacyMap(pathname); - if (mapped) { + if (mapped && !isSameUrl(req, mapped)) { + // Build destination on the same URL object to keep host/proto url.pathname = mapped.pathname; - // keep existing query, add/override provided params (e.g., ?t=fiber) if (mapped.query) { for (const [k, v] of Object.entries(mapped.query)) { url.searchParams.set(k, v); @@ -47,45 +55,52 @@ export function middleware(req: NextRequest) { type MapResult = { pathname: string; query?: Record }; function legacyMap(pathname: string): MapResult | null { - // 1) DETAIL PAGES: legacy [id] → existing canonical [id] pages - // (keeps working now; we can later switch these to open inside /portal once wrappers exist) + // If we’re already inside the portal, don’t try to remap again. + if (pathname.startsWith("/portal")) return null; + + // 1) DETAIL PAGES: map legacy detail URLs straight into the portal with ?id= const detailRules: Array<[RegExp, (m: RegExpExecArray) => MapResult]> = [ - [/^\/fiber-settings\/([^/]+)\/?$/i, (m) => ({ pathname: `/settings/fiber/${m[1]}` })], - [/^\/uv-settings\/([^/]+)\/?$/i, (m) => ({ pathname: `/settings/uv/${m[1]}` })], - [/^\/co2-galvo-settings\/([^/]+)\/?$/i, (m) => ({ pathname: `/settings/co2-galvo/${m[1]}` })], - [/^\/co2-gantry-settings\/([^/]+)\/?$/i, (m) => ({ pathname: `/settings/co2-gantry/${m[1]}` })], - [/^\/co2gantry-settings\/([^/]+)\/?$/i, (m) => ({ pathname: `/settings/co2-gantry/${m[1]}` })], // old alias - [/^\/materials\/([^/]+)\/?$/i, (m) => ({ pathname: `/materials/materials/${m[1]}` })], - [/^\/materials-coatings\/([^/]+)\/?$/i, (m) => ({ pathname: `/materials/materials-coatings/${m[1]}` })], - // Lasers / Projects detail already live under their canonical routes - // (keep as-is; no redirect needed). If you still want to map legacy, uncomment: - // [/^\/lasers\/([^/]+)\/?$/i, (m) => ({ pathname: `/lasers/${m[1]}` })], - // [/^\/projects\/([^/]+)\/?$/i, (m) => ({ pathname: `/projects/${m[1]}` })], + // Laser settings + [/^\/fiber-settings\/([^/]+)\/?$/i, (m) => ({ pathname: "/portal/laser-settings", query: { t: "fiber", id: m[1] } })], + [/^\/uv-settings\/([^/]+)\/?$/i, (m) => ({ pathname: "/portal/laser-settings", query: { t: "uv", id: m[1] } })], + [/^\/co2-galvo-settings\/([^/]+)\/?$/i, (m) => ({ pathname: "/portal/laser-settings", query: { t: "co2-galvo", id: m[1] } })], + [/^\/co2-gantry-settings\/([^/]+)\/?$/i, (m) => ({ pathname: "/portal/laser-settings", query: { t: "co2-gantry", id: m[1] } })], + [/^\/co2gantry-settings\/([^/]+)\/?$/i, (m) => ({ pathname: "/portal/laser-settings", query: { t: "co2-gantry", id: m[1] } })], // old alias + + // Materials + [/^\/materials\/([^/]+)\/?$/i, (m) => ({ pathname: "/portal/materials", query: { t: "materials", id: m[1] } })], + [/^\/materials-coatings\/([^/]+)\/?$/i, (m) => ({ pathname: "/portal/materials", query: { t: "materials-coatings", id: m[1] } })], + + // Lasers / Projects detail + [/^\/lasers\/([^/]+)\/?$/i, (m) => ({ pathname: "/portal/laser-sources", query: { id: m[1] } })], + [/^\/projects\/([^/]+)\/?$/i, (m) => ({ pathname: "/portal/projects", query: { id: m[1] } })], ]; for (const [re, to] of detailRules) { const m = re.exec(pathname); if (m) return to(m); } - // 2) LIST PAGES: legacy lists → portal lists (with tab param) or portal sections - // Accept optional trailing slash variants. + // 2) LIST PAGES: legacy lists → portal lists (with tab param) or sections const listRules: Array<[RegExp, MapResult]> = [ - [/^\/fiber-settings\/?$/i, { pathname: "/portal/laser-settings", query: { t: "fiber" } }], - [/^\/uv-settings\/?$/i, { pathname: "/portal/laser-settings", query: { t: "uv" } }], - [/^\/co2-galvo-settings\/?$/i, { pathname: "/portal/laser-settings", query: { t: "co2-galvo" } }], - [/^\/co2-ganry-settings\/?$/i, { pathname: "/portal/laser-settings", query: { t: "co2-gantry" } }], // just in case of typos - [/^\/co2-gantry-settings\/?$/i, { pathname: "/portal/laser-settings", query: { t: "co2-gantry" } }], - [/^\/co2gantry-settings\/?$/i, { pathname: "/portal/laser-settings", query: { t: "co2-gantry" } }], // old alias + // Laser settings lists + [/^\/fiber-settings\/?$/i, { pathname: "/portal/laser-settings", query: { t: "fiber" } }], + [/^\/uv-settings\/?$/i, { pathname: "/portal/laser-settings", query: { t: "uv" } }], + [/^\/co2-galvo-settings\/?$/i, { pathname: "/portal/laser-settings", query: { t: "co2-galvo" } }], + [/^\/co2-ganry-settings\/?$/i, { pathname: "/portal/laser-settings", query: { t: "co2-gantry" } }], // typo catch + [/^\/co2-gantry-settings\/?$/i, { pathname: "/portal/laser-settings", query: { t: "co2-gantry" } }], + [/^\/co2gantry-settings\/?$/i, { pathname: "/portal/laser-settings", query: { t: "co2-gantry" } }], // old alias - [/^\/materials\/?$/i, { pathname: "/portal/materials", query: { t: "materials" } }], - [/^\/materials\/materials\/?$/i, { pathname: "/portal/materials", query: { t: "materials" } }], + // Materials lists + [/^\/materials\/?$/i, { pathname: "/portal/materials", query: { t: "materials" } }], + [/^\/materials\/materials\/?$/i, { pathname: "/portal/materials", query: { t: "materials" } }], [/^\/materials\/materials-coatings\/?$/i, { pathname: "/portal/materials", query: { t: "materials-coatings" } }], - [/^\/materials-coatings\/?$/i, { pathname: "/portal/materials", query: { t: "materials-coatings" } }], + [/^\/materials-coatings\/?$/i, { pathname: "/portal/materials", query: { t: "materials-coatings" } }], - [/^\/lasers\/?$/i, { pathname: "/portal/laser-sources" }], - [/^\/projects\/?$/i, { pathname: "/portal/projects" }], - [/^\/my\/rigs\/?$/i, { pathname: "/portal/rigs", query: { t: "my" } }], + // Other lists + [/^\/lasers\/?$/i, { pathname: "/portal/laser-sources" }], + [/^\/projects\/?$/i, { pathname: "/portal/projects" }], + [/^\/my\/rigs\/?$/i, { pathname: "/portal/rigs", query: { t: "my" } }], ]; for (const [re, dest] of listRules) { if (re.test(pathname)) return dest; @@ -107,7 +122,7 @@ function isPublicPath(pathname: string): boolean { pathname === "/sitemap.xml" ) return true; - // API routes aren't gated here; each route should enforce auth as needed + // API routes aren’t gated here; each route should enforce auth as needed if (pathname.startsWith("/api/")) return true; // Everything else is protected @@ -115,7 +130,5 @@ function isPublicPath(pathname: string): boolean { } export const config = { - matcher: [ - "/((?!_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml|images|static).*)", - ], + matcher: ["/((?!_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml|images|static).*)"], };