From 7b057d636abe62f33e83c3309840b7790f4b8f11 Mon Sep 17 00:00:00 2001 From: Alezco Date: Mon, 11 Jan 2021 15:33:43 +0100 Subject: [PATCH] feat(cache): add caching to statistics --- src/app.ts | 31 ++++++++++++++++++++++------- src/caching/caching-service.test.ts | 14 +++++++++++++ src/caching/caching-service.ts | 28 ++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 src/caching/caching-service.test.ts create mode 100644 src/caching/caching-service.ts diff --git a/src/app.ts b/src/app.ts index bac947f..b8aedca 100644 --- a/src/app.ts +++ b/src/app.ts @@ -3,12 +3,15 @@ import express from "express"; import { flatten } from "lodash/fp"; import packageJson from "../package.json"; +import { createCache } from "./caching/caching-service"; import { corsOrigins, port } from "./config"; import { getGitHubData } from "./github/github-service"; import { matomoConfig } from "./matomo/matomo-config"; import { getMultiSiteMatomoData } from "./matomo/matomo-service"; import { getYoutubeData } from "./youtube/youtube-service"; +const CACHE_TTL = 10 * 60 * 1000; + const app = express(); app.use( @@ -25,14 +28,28 @@ app.get("/healthz", (req, res) => { res.send("OK"); }); +const statsCache = createCache( + async () => + Promise.all([ + getMultiSiteMatomoData(matomoConfig), + getYoutubeData(), + getGitHubData(), + ]).then(flatten), + CACHE_TTL +); + app.get("/statistics", (req, res) => { - void Promise.all([ - getMultiSiteMatomoData(matomoConfig), - getYoutubeData(), - getGitHubData(), - ]) - .then(flatten) - .then((data) => res.json(data)); + res.json({ + lastFetchTimestamp: statsCache.getLastFetchTimestamp(), + result: statsCache.read(), + }); +}); + +app.post("/refresh", (req, res) => { + void statsCache + .refresh() + .then(() => res.status(204).send()) + .catch(() => res.status(500).send("Error refreshing cache")); }); app.listen(port, () => { diff --git a/src/caching/caching-service.test.ts b/src/caching/caching-service.test.ts new file mode 100644 index 0000000..56467b7 --- /dev/null +++ b/src/caching/caching-service.test.ts @@ -0,0 +1,14 @@ +import { createCache } from "./caching-service"; + +describe("caching-utils", () => { + describe("createCache", () => { + it("reads cache with fetch data value", async () => { + const CACHE_TTL = 10 * 60 * 1000; + const cacheValue = ["foo", "bar"]; + + const { read, refresh } = createCache(() => cacheValue, CACHE_TTL); + await refresh(); + expect(read()).toEqual(cacheValue); + }); + }); +}); diff --git a/src/caching/caching-service.ts b/src/caching/caching-service.ts new file mode 100644 index 0000000..74abcbc --- /dev/null +++ b/src/caching/caching-service.ts @@ -0,0 +1,28 @@ +type CacheObject = { + read: () => T | null; + refresh: () => Promise; + getLastFetchTimestamp: () => number | null; +}; + +type FetchData = () => Promise | T; + +export const createCache = ( + fetchData: FetchData, + ttl: number +): CacheObject => { + let cacheData: null | T = null; + let lastFetchTimestamp = 0; + let refreshTimeout: NodeJS.Timeout | null = null; + const refresh = async () => { + refreshTimeout?.unref(); + cacheData = await fetchData(); + lastFetchTimestamp = Date.now(); + refreshTimeout = setTimeout(() => void refresh(), ttl); + }; + void refresh(); + return { + getLastFetchTimestamp: () => lastFetchTimestamp, + read: () => cacheData, + refresh, + }; +};