From 6bc212baa15efaa128155c65d3f60797034313df Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 29 Apr 2024 12:27:37 -0300 Subject: [PATCH] feat: adicionado novo loader de redirect --- deno.json | 6 +- loaders/RedirectsFromCsvWithEncoding.ts | 161 ++++++++++++++++++++++++ manifest.gen.ts | 4 + 3 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 loaders/RedirectsFromCsvWithEncoding.ts diff --git a/deno.json b/deno.json index 39d6fac..955ccdd 100644 --- a/deno.json +++ b/deno.json @@ -30,8 +30,8 @@ "deco-sites/lojastopmoveis/": "./", "$store/": "./", "deco-sites/std/": "https://denopkg.com/deco-sites/std@1.25.1/", - "$live/": "https://denopkg.com/deco-cx/deco@1.61.5/", - "deco/": "https://denopkg.com/deco-cx/deco@1.61.5/", + "$live/": "https://denopkg.com/deco-cx/deco@1.62.1/", + "deco/": "https://denopkg.com/deco-cx/deco@1.62.1/", "$fresh/": "https://deno.land/x/fresh@1.6.5/", "preact": "https://esm.sh/preact@10.19.2", "preact/": "https://esm.sh/preact@10.19.2/", @@ -41,7 +41,7 @@ "std/": "https://deno.land/std@0.190.0/", "partytown/": "https://denopkg.com/deco-cx/partytown@0.4.8/", "daisyui": "npm:daisyui@3.0.3", - "apps/": "https://denopkg.com/deco-cx/apps@0.36.14/" + "apps/": "https://denopkg.com/deco-cx/apps@0.36.16/" }, "lint": { "rules": { diff --git a/loaders/RedirectsFromCsvWithEncoding.ts b/loaders/RedirectsFromCsvWithEncoding.ts new file mode 100644 index 0000000..7207421 --- /dev/null +++ b/loaders/RedirectsFromCsvWithEncoding.ts @@ -0,0 +1,161 @@ +import { join } from "std/path/mod.ts"; +import { Route } from "apps/website/flags/audience.ts"; + +const REDIRECT_TYPE_ENUM = ["temporary", "permanent"]; + +/** @titleBy from */ +export interface Redirect { + from: string; + to: string; + type?: "temporary" | "permanent"; +} + +export interface Redirects { + /** + * @title The file url or path + */ + from?: string; + forcePermanentRedirects?: boolean; + redirects: Redirect[]; +} + +const getRedirectFromFile = async ( + from: string, + forcePermanentRedirects?: boolean, +) => { + let redirectsRaw: string | null = null; + try { + if (from.startsWith("http")) { + redirectsRaw = await fetch(from).then((resp) => resp.text()); + } else { + redirectsRaw = await Deno.readTextFile( + join(Deno.cwd(), join(...from.split("/"))), + ); + } + } catch (e) { + console.error(e); + } + + if (!redirectsRaw) { + return []; + } + + const redirectsFromFiles: Redirects["redirects"] = redirectsRaw + ?.split(/\r\n|\r|\n/) + .slice(1) + .map((row) => { + // this regex is necessary to handle csv with comma as part of value + const parts = row.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/); + + const typeRowIndex = parts.findIndex((part: string) => + REDIRECT_TYPE_ENUM.includes(part) + ); + + const type = + (parts[typeRowIndex] as Redirect["type"]) ?? forcePermanentRedirects + ? "permanent" + : "temporary"; + + if (typeRowIndex !== -1) { + parts.splice(typeRowIndex, 1); + } + + const last = parts[parts.length - 1]; + parts.splice(parts.length - 1, 1); + + return [ + removeTrailingSlash(parts.join(",").replaceAll('"', "")), + removeTrailingSlash(last.replaceAll('"', "")), + type, + ]; + }) + .filter(([from, to]) => from && to && from !== to) + .map(([from, to, type]) => ({ + from, + to, + type: type as Redirect["type"], + })); + + const processedRedirects = redirectsFromFiles.map(({ from, to, type }) => { + const encodedFrom = from + .replace(/[^a-zA-Z0-9\/]/g, (char) => encodeURIComponent(char)); + if (encodedFrom !== from) { + return [ + { + pathTemplate: from, + isHref: true, + handler: { + value: { + __resolveType: "website/handlers/redirect.ts", + to, + type, + }, + }, + }, + { + pathTemplate: encodedFrom, + isHref: true, + handler: { + value: { + __resolveType: "website/handlers/redirect.ts", + to, + type, + }, + }, + }, + ]; + } else { + return { + pathTemplate: from, + isHref: true, + handler: { + value: { + __resolveType: "website/handlers/redirect.ts", + to, + type, + }, + }, + }; + } + }).flat(); + + return processedRedirects; +}; + +export const removeTrailingSlash = (path: string) => + path.endsWith("/") && path.length > 1 ? path.slice(0, path.length - 1) : path; + +const routesMap = new Map>(); + +export default async function redirect({ + redirects, + from = "", + forcePermanentRedirects, +}: Redirects): Promise { + const current = routesMap.get(from); + + if (!current) { + routesMap.set( + from, + from + ? getRedirectFromFile(from, forcePermanentRedirects) + : Promise.resolve([]), + ); + } + + const redirectsFromFiles: Route[] = await routesMap.get(from)!; + + const routes: Route[] = (redirects || []).map(({ from, to, type }) => ({ + pathTemplate: from, + isHref: true, + handler: { + value: { + __resolveType: "website/handlers/redirect.ts", + to, + type, + }, + }, + })); + + return [...redirectsFromFiles, ...routes]; +} \ No newline at end of file diff --git a/manifest.gen.ts b/manifest.gen.ts index 5a33839..936500c 100644 --- a/manifest.gen.ts +++ b/manifest.gen.ts @@ -4,6 +4,7 @@ import * as $$$$$$$$$$$0 from "./apps/decohub.ts"; import * as $$$$$$$$$$$1 from "./apps/site.ts"; +import * as $$$0 from "./loaders/RedirectsFromCsvWithEncoding.ts"; import * as $$$$$$0 from "./sections/Category/CategoryBanner.tsx"; import * as $$$$$$1 from "./sections/Category/CategoryBreadcrumb.tsx"; import * as $$$$$$2 from "./sections/Category/CategoryList.tsx"; @@ -48,6 +49,9 @@ import * as $$$$$$40 from "./sections/Social/InstagramPosts.tsx"; import * as $$$$$$41 from "./sections/Social/WhatsApp.tsx"; const manifest = { + "loaders": { + "deco-sites/lojastopmoveis/loaders/RedirectsFromCsvWithEncoding.ts": $$$0, + }, "sections": { "deco-sites/lojastopmoveis/sections/Category/CategoryBanner.tsx": $$$$$$0, "deco-sites/lojastopmoveis/sections/Category/CategoryBreadcrumb.tsx":