Skip to content

Commit

Permalink
✨ add CF function for getting SLUG.config.json
Browse files Browse the repository at this point in the history
  • Loading branch information
danyx23 committed Aug 14, 2024
1 parent 958e4fa commit ee9fbaa
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 42 deletions.
57 changes: 44 additions & 13 deletions functions/_common/grapherRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,22 +135,13 @@ const extractOptions = (params: URLSearchParams): ImageOptions => {
return options as ImageOptions
}

async function fetchAndRenderGrapherToSvg({
export async function fetchGrapherConfig({
slug,
options,
searchParams,
env,
}: {
slug: string
options: ImageOptions
searchParams: URLSearchParams
env: Env
}) {
const grapherLogger = new TimeLogger("grapher")

const url = new URL(`/grapher/${slug}`, env.url)
const slugOnly = url.pathname.split("/").pop()

}): Promise<GrapherInterface | null> {
// The top level directory is either the bucket path (should be set in dev environments and production)
// or the branch name on preview staging environments
console.log("branch", env.CF_PAGES_BRANCH)
Expand All @@ -161,7 +152,7 @@ async function fetchAndRenderGrapherToSvg({
const key = excludeUndefined([
...topLevelDirectory,
R2GrapherConfigDirectory.publishedGrapherBySlug,
`${slugOnly}.json`,
`${slug}.json`,
]).join("/")

console.log("fetching grapher config from this key", key)
Expand All @@ -172,9 +163,25 @@ async function fetchAndRenderGrapherToSvg({
if (!fetchResponse) {
return null
}

const grapherConfig: GrapherInterface = await fetchResponse.json()
console.log("grapher title", grapherConfig.title)
return grapherConfig
}

async function fetchAndRenderGrapherToSvg({
slug,
options,
searchParams,
env,
}: {
slug: string
options: ImageOptions
searchParams: URLSearchParams
env: Env
}) {
const grapherLogger = new TimeLogger("grapher")

const grapherConfig = await fetchGrapherConfig({ slug, env })

const bounds = new Bounds(0, 0, options.svgWidth, options.svgHeight)
const grapher = new Grapher({
Expand Down Expand Up @@ -261,3 +268,27 @@ async function renderSvgToPng(svg: string, options: ImageOptions) {
pngLogger.log("svg2png")
return pngData
}

export async function getOptionalRedirectForSlug(
slug: string,
baseUrl: URL,
env: Env
) {
const redirects: Record<string, string> = await env.ASSETS.fetch(
new URL("/grapher/_grapherRedirects.json", baseUrl),
{ cf: { cacheTtl: 2 * 60 } }
)
.then((r): Promise<Record<string, string>> => r.json())
.catch((e) => {
console.error("Error fetching redirects", e)
return {}
})
return redirects[slug]
}

export function createRedirectResponse(redirSlug: string, currentUrl: URL) {
new Response(null, {
status: 302,
headers: { Location: `/grapher/${redirSlug}${currentUrl.search}` },
})
}
140 changes: 111 additions & 29 deletions functions/grapher/[slug].ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,62 @@
import { Env } from "./thumbnail/[slug].js"
import {
fetchGrapherConfig,
getOptionalRedirectForSlug,
createRedirectResponse,
} from "../_common/grapherRenderer.js"
import { IRequestStrict, Router, error } from "itty-router"

const router = Router<IRequestStrict, [URL, Env]>()
router
.get(
"/grapher/:slug.config.json",
async ({ params: { slug } }, { searchParams }, env) =>
handleConfigRequest(slug, searchParams, env)
)
.get(
"/grapher/:slug",
async ({ params: { slug } }, { searchParams }, env) =>
handleHtmlPageRequest(slug, searchParams, env)
)
.all("*", () => error(404, "Route not defined"))

export const onRequestGet: PagesFunction = async (context) => {
// Makes it so that if there's an error, we will just deliver the original page before the HTML rewrite.
// Only caveat is that redirects will not be taken into account for some reason; but on the other hand the worker is so simple that it's unlikely to fail.
context.passThroughOnException()
const { request, env, params } = context
const url = new URL(request.url)
const originalSlug = params.slug as string

return (
router
.fetch(request, url, { ...env, url, originalSlug }, context)
// .then((resp: Response) => {
// if (shouldCache) {
// resp.headers.set(
// "Cache-Control",
// "public, s-maxage=3600, max-age=3600"
// )
// context.waitUntil(caches.default.put(request, resp.clone()))
// } else
// resp.headers.set(
// "Cache-Control",
// "public, s-maxage=0, max-age=0, must-revalidate"
// )
// return resp
// })
.catch((e) => error(500, e))
)
}

async function handleHtmlPageRequest(
slug: string,
searchParams: URLSearchParams,
env: Env
) {
const url = env.url
// Redirects handling is performed by the worker, and is done by fetching the (baked) _grapherRedirects.json file.
// That file is a mapping from old slug to new slug.
const getOptionalRedirectForSlug = async (slug: string, baseUrl: URL) => {
const redirects: Record<string, string> = await env.ASSETS.fetch(
new URL("/grapher/_grapherRedirects.json", baseUrl),
{ cf: { cacheTtl: 2 * 60 } }
)
.then((r): Promise<Record<string, string>> => r.json())
.catch((e) => {
console.error("Error fetching redirects", e)
return {}
})
return redirects[slug]
}

const createRedirectResponse = (redirSlug: string, currentUrl: URL) =>
new Response(null, {
status: 302,
headers: { Location: `/grapher/${redirSlug}${currentUrl.search}` },
})

const { request, env, params } = context

const originalSlug = params.slug as string
const url = new URL(request.url)

/**
* REDIRECTS HANDLING:
Expand All @@ -40,11 +69,12 @@ export const onRequestGet: PagesFunction = async (context) => {

// All our grapher slugs are lowercase by convention.
// To allow incoming links that may contain uppercase characters to work, we redirect to the lowercase version.
const lowerCaseSlug = originalSlug.toLowerCase()
if (lowerCaseSlug !== originalSlug) {
const lowerCaseSlug = slug.toLowerCase()
if (lowerCaseSlug !== slug) {
const redirectSlug = await getOptionalRedirectForSlug(
lowerCaseSlug,
url
url,
env
)

return createRedirectResponse(redirectSlug ?? lowerCaseSlug, url)
Expand All @@ -61,8 +91,8 @@ export const onRequestGet: PagesFunction = async (context) => {
if (grapherPageResp.status === 404) {
// If the request is a 404, we check if there's a redirect for it.
// If there is, we redirect to the new page.
const redirectSlug = await getOptionalRedirectForSlug(originalSlug, url)
if (redirectSlug && redirectSlug !== originalSlug) {
const redirectSlug = await getOptionalRedirectForSlug(slug, url, env)
if (redirectSlug && redirectSlug !== slug) {
return createRedirectResponse(redirectSlug, url)
} else {
// Otherwise we just return the 404 page.
Expand Down Expand Up @@ -106,5 +136,57 @@ export const onRequestGet: PagesFunction = async (context) => {
},
})

return rewriter.transform(grapherPageResp)
return rewriter.transform(grapherPageResp as unknown as Response)
}

async function handleConfigRequest(
slug: string,
searchParams: URLSearchParams,
env: Env
) {
console.log("Preapring json response for ", slug)
// All our grapher slugs are lowercase by convention.
// To allow incoming links that may contain uppercase characters to work, we redirect to the lowercase version.
const lowerCaseSlug = slug.toLowerCase()
if (lowerCaseSlug !== slug) {
const redirectSlug = await getOptionalRedirectForSlug(
lowerCaseSlug,
env.url,
env
)

return createRedirectResponse(
redirectSlug ? `${redirectSlug}.config.json` : lowerCaseSlug,
env.url
)
}

const grapherPageResp = await fetchGrapherConfig({ slug, env })

console.log("Grapher page response", grapherPageResp?.title)

if (!grapherPageResp) {
// If the request is a 404, we check if there's a redirect for it.
// If there is, we redirect to the new page.
const redirectSlug = await getOptionalRedirectForSlug(
slug,
env.url,
env
)
if (redirectSlug && redirectSlug !== slug) {
return createRedirectResponse(
`${redirectSlug}.config.json`,
env.url
)
} else {
// Otherwise we just return the 404 page.
return new Response(null, { status: 404 })
}
}

return new Response(JSON.stringify(grapherPageResp), {
headers: {
"content-type": "application/json",
},
})
}

0 comments on commit ee9fbaa

Please sign in to comment.