Add caching to feed endpoint #45

Merged
olaven merged 1 commits from cache-responses into main 2025-09-29 19:57:42 +00:00
3 changed files with 67 additions and 5 deletions

View File

@ -1,5 +1,5 @@
import { assertEquals, assertNotEquals } from "$std/assert/mod.ts"; import { assertEquals, assertNotEquals } from "$std/assert/mod.ts";
import { responseJSON, responseXML } from "./utils.ts"; import { responseJSON, responseXML, withExpiry } from "./utils.ts";
Deno.test("JSON response is stringified", async () => { Deno.test("JSON response is stringified", async () => {
const body = { message: "Hello, World!" }; const body = { message: "Hello, World!" };
@ -14,3 +14,41 @@ Deno.test("XML resopnse is _not_ stringified", async () => {
assertEquals(responseBody, body); assertEquals(responseBody, body);
assertNotEquals(responseBody, JSON.stringify(body)); assertNotEquals(responseBody, JSON.stringify(body));
}); });
Deno.test(
"Response with cache control returns a response with the correct header",
() => {
const response = responseJSON({ message: "Hello, World!" }, 200);
const cachedResponse = withExpiry(response, 3600);
assertEquals(cachedResponse.headers.get("Cache-Control"), "max-age=3600");
},
);
Deno.test(
"Response with cache control returns the same body as the original",
async () => {
const body = { message: "Hello, World!" };
const response = responseJSON(body, 200);
const cachedResponse = withExpiry(response, 3600);
assertEquals(await cachedResponse.text(), await response.text());
},
);
Deno.test("Response with cache control does not modify the original", () => {
const response = responseJSON({ message: "Hello, World!" }, 200);
withExpiry(response, 3600);
assertEquals(response.headers.get("Cache-Control"), null);
});
Deno.test(
"Response with cache control returns the correct number of seconds",
() => {
const response = responseJSON({ message: "Hello, World!" }, 200);
const ttlInSeconds = 1234;
const cachedResponse = withExpiry(response, ttlInSeconds);
assertEquals(
cachedResponse.headers.get("Cache-Control"),
`max-age=${ttlInSeconds}`,
);
},
);

View File

@ -24,6 +24,12 @@ export function responseXML(body: string, status: Status) {
return response(body, status, "xml"); return response(body, status, "xml");
} }
export const withExpiry = <T>(response: Response, ttlInSeconds: number) => {
const clonedResponse = response.clone();
clonedResponse.headers.set("Cache-Control", `max-age=${ttlInSeconds}`);
return clonedResponse;
};
function response(body: string, status: number, type: "json" | "xml") { function response(body: string, status: number, type: "json" | "xml") {
return new Response(body, { return new Response(body, {
status, status,

View File

@ -1,18 +1,36 @@
import { FreshContext, STATUS_CODE } from "$fresh/server.ts"; import { FreshContext, STATUS_CODE } from "$fresh/server.ts";
import { caching } from "../../../lib/caching.ts"; import { caching } from "../../../lib/caching.ts";
import { rss } from "../../../lib/rss.ts"; import { rss } from "../../../lib/rss.ts";
import { responseJSON, responseXML } from "../../../lib/utils.ts"; import { responseJSON, responseXML, withExpiry } from "../../../lib/utils.ts";
export const handler = async (_req: Request, ctx: FreshContext): Promise<Response> => { const FEED_CACHE_KEY = "feeds-cache";
export const handler = async (
request: Request,
ctx: FreshContext,
): Promise<Response> => {
const seriesId = ctx.params.seriesId; const seriesId = ctx.params.seriesId;
const cache = await caches.open(FEED_CACHE_KEY);
const cachedResponse = await cache.match(request);
if (cachedResponse) {
console.log(`Cache hit for feed ${seriesId}`);
return cachedResponse;
}
const series = await caching.getSeries({ id: seriesId }); const series = await caching.getSeries({ id: seriesId });
if (!series) { if (!series) {
return responseJSON({ message: "Series not found" }, STATUS_CODE.NotFound); return responseJSON({ message: "Series not found" }, STATUS_CODE.NotFound);
} }
const feed = rss.assembleFeed(series); const feed = rss.assembleFeed(series);
const xml = responseXML(feed, STATUS_CODE.OK); const response = withExpiry(
responseXML(feed, STATUS_CODE.OK),
// 2 hours
60 * 60 * 2,
);
return xml; await cache.put(request, response.clone());
return response;
}; };