diff --git a/functions/_common/grapherRenderer.ts b/functions/_common/grapherRenderer.ts index 170d972bdf0..a2eac6ea581 100644 --- a/functions/_common/grapherRenderer.ts +++ b/functions/_common/grapherRenderer.ts @@ -164,8 +164,20 @@ interface FetchGrapherConfigResult { etag: string | undefined } +interface GrapherSlug { + type: "slug" + id: string +} + +interface GrapherUuid { + type: "uuid" + id: string +} + +type GrapherIdentifier = GrapherSlug | GrapherUuid + export async function fetchGrapherConfig( - slug: string, + identifier: GrapherIdentifier, env: Env, etag: string ): Promise { @@ -176,10 +188,15 @@ export async function fetchGrapherConfig( ? [env.GRAPHER_CONFIG_R2_BUCKET_PATH] : ["by-branch", env.CF_PAGES_BRANCH] + const directory = + identifier.type === "slug" + ? R2GrapherConfigDirectory.publishedGrapherBySlug + : R2GrapherConfigDirectory.byUUID + const key = excludeUndefined([ ...topLevelDirectory, - R2GrapherConfigDirectory.publishedGrapherBySlug, - `${slug}.json`, + directory, + `${identifier.id}.json`, ]).join("/") console.log("fetching grapher config from this key", key) @@ -195,8 +212,8 @@ export async function fetchGrapherConfig( const topLevelDirectory = env.GRAPHER_CONFIG_R2_BUCKET_FALLBACK_PATH const fallbackKey = excludeUndefined([ ...topLevelDirectory, - R2GrapherConfigDirectory.publishedGrapherBySlug, - `${slug}.json`, + directory, + `${identifier.id}.json`, ]).join("/") fallbackUrl = new URL( fallbackKey, @@ -232,7 +249,11 @@ async function fetchAndRenderGrapherToSvg( ) { const grapherLogger = new TimeLogger("grapher") - const grapherConfigResponse = await fetchGrapherConfig(slug, env, etag) + const grapherConfigResponse = await fetchGrapherConfig( + { type: "slug", id: slug }, + env, + undefined + ) if (grapherConfigResponse.status !== 200) { return null diff --git a/functions/grapher/[slug].ts b/functions/grapher/[slug].ts index 43855c5de54..64d3cc9a1a4 100644 --- a/functions/grapher/[slug].ts +++ b/functions/grapher/[slug].ts @@ -158,7 +158,11 @@ async function handleConfigRequest( ) } - const grapherPageResp = await fetchGrapherConfig(slug, env, etag) + const grapherPageResp = await fetchGrapherConfig( + { type: "slug", id: slug }, + env, + etag + ) if (grapherPageResp.status === 304) { return new Response(null, { status: 304 }) diff --git a/functions/grapher/by-uuid/[uuid].ts b/functions/grapher/by-uuid/[uuid].ts new file mode 100644 index 00000000000..d20af2b3caa --- /dev/null +++ b/functions/grapher/by-uuid/[uuid].ts @@ -0,0 +1,69 @@ +import { Env } from "../thumbnail/[slug].js" +import { fetchGrapherConfig } from "../../_common/grapherRenderer.js" +import { IRequestStrict, Router, error, cors } from "itty-router" +const { preflight, corsify } = cors({ + allowMethods: "GET", +}) + +const router = Router({ + before: [preflight], + finally: [corsify], +}) +router + .get( + "/grapher/by-uuid/:uuid.config.json", + async ({ params: { uuid } }, { searchParams }, env, etag) => + handleConfigRequest(uuid, searchParams, env, etag) + ) + .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 } = context + const url = new URL(request.url) + + return router + .fetch( + request, + url, + { ...env, url }, + request.headers.get("if-none-match") + ) + .catch((e) => error(500, e)) +} + +async function handleConfigRequest( + uuid: string, + searchParams: URLSearchParams, + env: Env, + etag: string | undefined +) { + const shouldCache = searchParams.get("nocache") === null + console.log("Preparing json response for uuid ", uuid) + + const grapherPageResp = await fetchGrapherConfig( + { type: "uuid", id: uuid }, + env, + etag + ) + + if (grapherPageResp.status === 304) { + return new Response(null, { status: 304 }) + } + + console.log("Grapher page response", grapherPageResp.grapherConfig.title) + + const cacheControl = shouldCache + ? "public, s-maxage=3600, max-age=0, must-revalidate" + : "public, s-maxage=0, max-age=0, must-revalidate" + + return new Response(JSON.stringify(grapherPageResp.grapherConfig), { + headers: { + "content-type": "application/json", + "Cache-Control": cacheControl, + ETag: grapherPageResp.etag, + }, + }) +}