diff --git a/api/gist.js b/api/gist.js index 7c5f9e68..d6713eaf 100644 --- a/api/gist.js +++ b/api/gist.js @@ -1,10 +1,11 @@ // @ts-check -import { CONSTANTS, renderError, parseBoolean } from "../src/common/utils.js"; +import { renderError, parseBoolean } from "../src/common/utils.js"; import { isLocaleAvailable } from "../src/translations.js"; import { renderGistCard } from "../src/cards/gist.js"; import { fetchGist } from "../src/fetchers/gist.js"; import { + CACHE_TTL, resolveCacheSeconds, setCacheHeaders, setErrorCacheHeaders, @@ -65,9 +66,9 @@ export default async (req, res) => { const gistData = await fetchGist(id); const cacheSeconds = resolveCacheSeconds({ requested: parseInt(cache_seconds, 10), - def: CONSTANTS.TWO_DAY, - min: CONSTANTS.TWO_DAY, - max: CONSTANTS.SIX_DAY, + def: CACHE_TTL.GIST_CARD.DEFAULT, + min: CACHE_TTL.GIST_CARD.MIN, + max: CACHE_TTL.GIST_CARD.MAX, }); setCacheHeaders(res, cacheSeconds); diff --git a/api/index.js b/api/index.js index 37a29841..d5e55fbb 100644 --- a/api/index.js +++ b/api/index.js @@ -3,16 +3,12 @@ import { renderStatsCard } from "../src/cards/stats.js"; import { guardAccess } from "../src/common/access.js"; import { + CACHE_TTL, resolveCacheSeconds, setCacheHeaders, setErrorCacheHeaders, } from "../src/common/cache.js"; -import { - CONSTANTS, - parseArray, - parseBoolean, - renderError, -} from "../src/common/utils.js"; +import { parseArray, parseBoolean, renderError } from "../src/common/utils.js"; import { fetchStats } from "../src/fetchers/stats.js"; import { isLocaleAvailable } from "../src/translations.js"; @@ -94,9 +90,9 @@ export default async (req, res) => { ); const cacheSeconds = resolveCacheSeconds({ requested: parseInt(cache_seconds, 10), - def: CONSTANTS.CARD_CACHE_SECONDS, - min: CONSTANTS.TWELVE_HOURS, - max: CONSTANTS.TWO_DAY, + def: CACHE_TTL.STATS_CARD.DEFAULT, + min: CACHE_TTL.STATS_CARD.MIN, + max: CACHE_TTL.STATS_CARD.MAX, }); setCacheHeaders(res, cacheSeconds); diff --git a/api/pin.js b/api/pin.js index 2183c8f1..e1f46b32 100644 --- a/api/pin.js +++ b/api/pin.js @@ -3,11 +3,12 @@ import { renderRepoCard } from "../src/cards/repo.js"; import { guardAccess } from "../src/common/access.js"; import { + CACHE_TTL, resolveCacheSeconds, setCacheHeaders, setErrorCacheHeaders, } from "../src/common/cache.js"; -import { CONSTANTS, parseBoolean, renderError } from "../src/common/utils.js"; +import { parseBoolean, renderError } from "../src/common/utils.js"; import { fetchRepo } from "../src/fetchers/repo.js"; import { isLocaleAvailable } from "../src/translations.js"; @@ -67,9 +68,9 @@ export default async (req, res) => { const repoData = await fetchRepo(username, repo); const cacheSeconds = resolveCacheSeconds({ requested: parseInt(cache_seconds, 10), - def: CONSTANTS.PIN_CARD_CACHE_SECONDS, - min: CONSTANTS.ONE_DAY, - max: CONSTANTS.TEN_DAY, + def: CACHE_TTL.PIN_CARD.DEFAULT, + min: CACHE_TTL.PIN_CARD.MIN, + max: CACHE_TTL.PIN_CARD.MAX, }); setCacheHeaders(res, cacheSeconds); diff --git a/api/top-langs.js b/api/top-langs.js index 3e903428..df286b48 100644 --- a/api/top-langs.js +++ b/api/top-langs.js @@ -3,16 +3,12 @@ import { renderTopLanguages } from "../src/cards/top-languages.js"; import { guardAccess } from "../src/common/access.js"; import { + CACHE_TTL, resolveCacheSeconds, setCacheHeaders, setErrorCacheHeaders, } from "../src/common/cache.js"; -import { - CONSTANTS, - parseArray, - parseBoolean, - renderError, -} from "../src/common/utils.js"; +import { parseArray, parseBoolean, renderError } from "../src/common/utils.js"; import { fetchTopLanguages } from "../src/fetchers/top-languages.js"; import { isLocaleAvailable } from "../src/translations.js"; @@ -124,9 +120,9 @@ export default async (req, res) => { ); const cacheSeconds = resolveCacheSeconds({ requested: parseInt(cache_seconds, 10), - def: CONSTANTS.TOP_LANGS_CACHE_SECONDS, - min: CONSTANTS.TWO_DAY, - max: CONSTANTS.TEN_DAY, + def: CACHE_TTL.TOP_LANGS_CARD.DEFAULT, + min: CACHE_TTL.TOP_LANGS_CARD.MIN, + max: CACHE_TTL.TOP_LANGS_CARD.MAX, }); setCacheHeaders(res, cacheSeconds); diff --git a/api/wakatime.js b/api/wakatime.js index 7b5571eb..b8642649 100644 --- a/api/wakatime.js +++ b/api/wakatime.js @@ -1,15 +1,11 @@ // @ts-check import { renderWakatimeCard } from "../src/cards/wakatime.js"; -import { - CONSTANTS, - parseArray, - parseBoolean, - renderError, -} from "../src/common/utils.js"; +import { parseArray, parseBoolean, renderError } from "../src/common/utils.js"; import { fetchWakatimeStats } from "../src/fetchers/wakatime.js"; import { isLocaleAvailable } from "../src/translations.js"; import { + CACHE_TTL, resolveCacheSeconds, setCacheHeaders, setErrorCacheHeaders, @@ -80,9 +76,9 @@ export default async (req, res) => { const stats = await fetchWakatimeStats({ username, api_domain }); const cacheSeconds = resolveCacheSeconds({ requested: parseInt(cache_seconds, 10), - def: CONSTANTS.CARD_CACHE_SECONDS, - min: CONSTANTS.SIX_HOURS, - max: CONSTANTS.TWO_DAY, + def: CACHE_TTL.WAKATIME_CARD.DEFAULT, + min: CACHE_TTL.WAKATIME_CARD.MIN, + max: CACHE_TTL.WAKATIME_CARD.MAX, }); setCacheHeaders(res, cacheSeconds); diff --git a/src/common/cache.js b/src/common/cache.js index 32aa48ce..3e88e3c9 100644 --- a/src/common/cache.js +++ b/src/common/cache.js @@ -1,6 +1,64 @@ // @ts-check -import { clampValue, CONSTANTS } from "./utils.js"; +import { clampValue } from "./utils.js"; + +const MIN = 60; +const HOUR = 60 * MIN; +const DAY = 24 * HOUR; + +/** + * Common durations in seconds. + */ +const DURATIONS = { + ONE_MINUTE: MIN, + FIVE_MINUTES: 5 * MIN, + TEN_MINUTES: 10 * MIN, + FIFTEEN_MINUTES: 15 * MIN, + THIRTY_MINUTES: 30 * MIN, + + TWO_HOURS: 2 * HOUR, + FOUR_HOURS: 4 * HOUR, + SIX_HOURS: 6 * HOUR, + EIGHT_HOURS: 8 * HOUR, + TWELVE_HOURS: 12 * HOUR, + + ONE_DAY: DAY, + TWO_DAY: 2 * DAY, + SIX_DAY: 6 * DAY, + TEN_DAY: 10 * DAY, +}; + +/** + * Common cache TTL values in seconds. + */ +const CACHE_TTL = { + STATS_CARD: { + DEFAULT: DURATIONS.ONE_DAY, + MIN: DURATIONS.TWELVE_HOURS, + MAX: DURATIONS.TWO_DAY, + }, + TOP_LANGS_CARD: { + DEFAULT: DURATIONS.SIX_DAY, + MIN: DURATIONS.TWO_DAY, + MAX: DURATIONS.TEN_DAY, + }, + PIN_CARD: { + DEFAULT: DURATIONS.TEN_DAY, + MIN: DURATIONS.ONE_DAY, + MAX: DURATIONS.TEN_DAY, + }, + GIST_CARD: { + DEFAULT: DURATIONS.TWO_DAY, + MIN: DURATIONS.ONE_DAY, + MAX: DURATIONS.TEN_DAY, + }, + WAKATIME_CARD: { + DEFAULT: DURATIONS.ONE_DAY, + MIN: DURATIONS.TWELVE_HOURS, + MAX: DURATIONS.TWO_DAY, + }, + ERROR: DURATIONS.TEN_MINUTES, +}; /** * Resolves the cache seconds based on the requested, default, min, and max values. @@ -56,7 +114,7 @@ const setCacheHeaders = (res, cacheSeconds) => { "Cache-Control", `max-age=${cacheSeconds}, ` + `s-maxage=${cacheSeconds}, ` + - `stale-while-revalidate=${CONSTANTS.ONE_DAY}`, + `stale-while-revalidate=${DURATIONS.ONE_DAY}`, ); }; @@ -80,10 +138,16 @@ const setErrorCacheHeaders = (res) => { // Use lower cache period for errors. res.setHeader( "Cache-Control", - `max-age=${CONSTANTS.ERROR_CACHE_SECONDS}, ` + - `s-maxage=${CONSTANTS.ERROR_CACHE_SECONDS}, ` + - `stale-while-revalidate=${CONSTANTS.ONE_DAY}`, + `max-age=${CACHE_TTL.ERROR}, ` + + `s-maxage=${CACHE_TTL.ERROR}, ` + + `stale-while-revalidate=${DURATIONS.ONE_DAY}`, ); }; -export { resolveCacheSeconds, setCacheHeaders, setErrorCacheHeaders }; +export { + resolveCacheSeconds, + setCacheHeaders, + setErrorCacheHeaders, + DURATIONS, + CACHE_TTL, +}; diff --git a/src/common/index.js b/src/common/index.js index 2e7e9cb2..39bbd6d6 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -1,3 +1,5 @@ +// @ts-check + export { blacklist } from "./blacklist.js"; export { Card } from "./Card.js"; export { createProgressNode } from "./createProgressNode.js"; @@ -20,7 +22,6 @@ export { getCardColors, wrapTextMultiline, logger, - CONSTANTS, CustomError, MissingParamError, measureText, diff --git a/src/common/utils.js b/src/common/utils.js index 3dac650b..8b2fe01d 100644 --- a/src/common/utils.js +++ b/src/common/utils.js @@ -452,34 +452,6 @@ const noop = () => {}; const logger = process.env.NODE_ENV === "test" ? { log: noop, error: noop } : console; -const MIN = 60; -const HOUR = 60 * MIN; -const DAY = 24 * HOUR; - -const CONSTANTS = { - ONE_MINUTE: MIN, - FIVE_MINUTES: 5 * MIN, - TEN_MINUTES: 10 * MIN, - FIFTEEN_MINUTES: 15 * MIN, - THIRTY_MINUTES: 30 * MIN, - - TWO_HOURS: 2 * HOUR, - FOUR_HOURS: 4 * HOUR, - SIX_HOURS: 6 * HOUR, - EIGHT_HOURS: 8 * HOUR, - TWELVE_HOURS: 12 * HOUR, - - ONE_DAY: DAY, - TWO_DAY: 2 * DAY, - SIX_DAY: 6 * DAY, - TEN_DAY: 10 * DAY, - - CARD_CACHE_SECONDS: DAY, - TOP_LANGS_CACHE_SECONDS: 6 * DAY, - PIN_CARD_CACHE_SECONDS: 10 * DAY, - ERROR_CACHE_SECONDS: 10 * MIN, -}; - /** * Missing query parameter class. */ @@ -648,7 +620,6 @@ export { getCardColors, wrapTextMultiline, logger, - CONSTANTS, CustomError, MissingParamError, measureText, diff --git a/tests/api.test.js b/tests/api.test.js index caaa1c5a..e54764a3 100644 --- a/tests/api.test.js +++ b/tests/api.test.js @@ -13,7 +13,8 @@ import MockAdapter from "axios-mock-adapter"; import api from "../api/index.js"; import { calculateRank } from "../src/calculateRank.js"; import { renderStatsCard } from "../src/cards/stats.js"; -import { CONSTANTS, renderError } from "../src/common/utils.js"; +import { renderError } from "../src/common/utils.js"; +import { CACHE_TTL, DURATIONS } from "../src/common/cache.js"; /** * @type {import("../src/fetchers/stats").StatsData} @@ -196,15 +197,15 @@ describe("Test /api/", () => { ["Content-Type", "image/svg+xml"], [ "Cache-Control", - `max-age=${CONSTANTS.CARD_CACHE_SECONDS}, ` + - `s-maxage=${CONSTANTS.CARD_CACHE_SECONDS}, ` + - `stale-while-revalidate=${CONSTANTS.ONE_DAY}`, + `max-age=${CACHE_TTL.STATS_CARD.DEFAULT}, ` + + `s-maxage=${CACHE_TTL.STATS_CARD.DEFAULT}, ` + + `stale-while-revalidate=${DURATIONS.ONE_DAY}`, ], ]); }); it("should set proper cache", async () => { - const cache_seconds = CONSTANTS.TWELVE_HOURS; + const cache_seconds = DURATIONS.TWELVE_HOURS; const { req, res } = faker({ cache_seconds }, data_stats); await api(req, res); @@ -214,7 +215,7 @@ describe("Test /api/", () => { "Cache-Control", `max-age=${cache_seconds}, ` + `s-maxage=${cache_seconds}, ` + - `stale-while-revalidate=${CONSTANTS.ONE_DAY}`, + `stale-while-revalidate=${DURATIONS.ONE_DAY}`, ], ]); }); @@ -227,15 +228,16 @@ describe("Test /api/", () => { ["Content-Type", "image/svg+xml"], [ "Cache-Control", - `max-age=${CONSTANTS.ERROR_CACHE_SECONDS}, ` + - `s-maxage=${CONSTANTS.ERROR_CACHE_SECONDS}, ` + - `stale-while-revalidate=${CONSTANTS.ONE_DAY}`, + `max-age=${CACHE_TTL.ERROR}, ` + + `s-maxage=${CACHE_TTL.ERROR}, ` + + `stale-while-revalidate=${DURATIONS.ONE_DAY}`, ], ]); }); it("should properly set cache using CACHE_SECONDS env variable", async () => { - process.env.CACHE_SECONDS = "10000"; + const cacheSeconds = "10000"; + process.env.CACHE_SECONDS = cacheSeconds; const { req, res } = faker({}, data_stats); await api(req, res); @@ -244,7 +246,9 @@ describe("Test /api/", () => { ["Content-Type", "image/svg+xml"], [ "Cache-Control", - `max-age=10000, s-maxage=10000, stale-while-revalidate=${CONSTANTS.ONE_DAY}`, + `max-age=${cacheSeconds}, ` + + `s-maxage=${cacheSeconds}, ` + + `stale-while-revalidate=${DURATIONS.ONE_DAY}`, ], ]); }); @@ -275,9 +279,9 @@ describe("Test /api/", () => { ["Content-Type", "image/svg+xml"], [ "Cache-Control", - `max-age=${CONSTANTS.TWO_DAY}, ` + - `s-maxage=${CONSTANTS.TWO_DAY}, ` + - `stale-while-revalidate=${CONSTANTS.ONE_DAY}`, + `max-age=${CACHE_TTL.STATS_CARD.MAX}, ` + + `s-maxage=${CACHE_TTL.STATS_CARD.MAX}, ` + + `stale-while-revalidate=${DURATIONS.ONE_DAY}`, ], ]); } @@ -291,9 +295,9 @@ describe("Test /api/", () => { ["Content-Type", "image/svg+xml"], [ "Cache-Control", - `max-age=${CONSTANTS.TWELVE_HOURS}, ` + - `s-maxage=${CONSTANTS.TWELVE_HOURS}, ` + - `stale-while-revalidate=${CONSTANTS.ONE_DAY}`, + `max-age=${CACHE_TTL.STATS_CARD.MIN}, ` + + `s-maxage=${CACHE_TTL.STATS_CARD.MIN}, ` + + `stale-while-revalidate=${DURATIONS.ONE_DAY}`, ], ]); } @@ -306,9 +310,9 @@ describe("Test /api/", () => { ["Content-Type", "image/svg+xml"], [ "Cache-Control", - `max-age=${CONSTANTS.TWELVE_HOURS}, ` + - `s-maxage=${CONSTANTS.TWELVE_HOURS}, ` + - `stale-while-revalidate=${CONSTANTS.ONE_DAY}`, + `max-age=${CACHE_TTL.STATS_CARD.MIN}, ` + + `s-maxage=${CACHE_TTL.STATS_CARD.MIN}, ` + + `stale-while-revalidate=${DURATIONS.ONE_DAY}`, ], ]); } diff --git a/tests/gist.test.js b/tests/gist.test.js index e136c2b2..1f3b6000 100644 --- a/tests/gist.test.js +++ b/tests/gist.test.js @@ -6,7 +6,8 @@ import axios from "axios"; import MockAdapter from "axios-mock-adapter"; import gist from "../api/gist.js"; import { renderGistCard } from "../src/cards/gist.js"; -import { CONSTANTS, renderError } from "../src/common/utils.js"; +import { renderError } from "../src/common/utils.js"; +import { CACHE_TTL, DURATIONS } from "../src/common/cache.js"; const gist_data = { data: { @@ -192,9 +193,9 @@ describe("Test /api/gist", () => { expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); expect(res.setHeader).toBeCalledWith( "Cache-Control", - `max-age=${CONSTANTS.TWO_DAY}, ` + - `s-maxage=${CONSTANTS.TWO_DAY}, ` + - `stale-while-revalidate=${CONSTANTS.ONE_DAY}`, + `max-age=${CACHE_TTL.GIST_CARD.DEFAULT}, ` + + `s-maxage=${CACHE_TTL.GIST_CARD.DEFAULT}, ` + + `stale-while-revalidate=${DURATIONS.ONE_DAY}`, ); }); }); diff --git a/tests/pin.test.js b/tests/pin.test.js index 035f4fb5..91c756dd 100644 --- a/tests/pin.test.js +++ b/tests/pin.test.js @@ -6,7 +6,8 @@ import axios from "axios"; import MockAdapter from "axios-mock-adapter"; import pin from "../api/pin.js"; import { renderRepoCard } from "../src/cards/repo.js"; -import { CONSTANTS, renderError } from "../src/common/utils.js"; +import { renderError } from "../src/common/utils.js"; +import { CACHE_TTL, DURATIONS } from "../src/common/cache.js"; const data_repo = { repository: { @@ -233,9 +234,9 @@ describe("Test /api/pin", () => { expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); expect(res.setHeader).toBeCalledWith( "Cache-Control", - `max-age=${CONSTANTS.PIN_CARD_CACHE_SECONDS}, ` + - `s-maxage=${CONSTANTS.PIN_CARD_CACHE_SECONDS}, ` + - `stale-while-revalidate=${CONSTANTS.ONE_DAY}`, + `max-age=${CACHE_TTL.PIN_CARD.DEFAULT}, ` + + `s-maxage=${CACHE_TTL.PIN_CARD.DEFAULT}, ` + + `stale-while-revalidate=${DURATIONS.ONE_DAY}`, ); }); }); diff --git a/tests/top-langs.test.js b/tests/top-langs.test.js index 62ad3494..41ee95e1 100644 --- a/tests/top-langs.test.js +++ b/tests/top-langs.test.js @@ -6,7 +6,8 @@ import axios from "axios"; import MockAdapter from "axios-mock-adapter"; import topLangs from "../api/top-langs.js"; import { renderTopLanguages } from "../src/cards/top-languages.js"; -import { CONSTANTS, renderError } from "../src/common/utils.js"; +import { renderError } from "../src/common/utils.js"; +import { CACHE_TTL, DURATIONS } from "../src/common/cache.js"; const data_langs = { data: { @@ -238,9 +239,9 @@ describe("Test /api/top-langs", () => { expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); expect(res.setHeader).toBeCalledWith( "Cache-Control", - `max-age=${CONSTANTS.TOP_LANGS_CACHE_SECONDS}, ` + - `s-maxage=${CONSTANTS.TOP_LANGS_CACHE_SECONDS}, ` + - `stale-while-revalidate=${CONSTANTS.ONE_DAY}`, + `max-age=${CACHE_TTL.TOP_LANGS_CARD.DEFAULT}, ` + + `s-maxage=${CACHE_TTL.TOP_LANGS_CARD.DEFAULT}, ` + + `stale-while-revalidate=${DURATIONS.ONE_DAY}`, ); }); }); diff --git a/tests/wakatime.test.js b/tests/wakatime.test.js index ce9843df..2b269c27 100644 --- a/tests/wakatime.test.js +++ b/tests/wakatime.test.js @@ -4,6 +4,7 @@ import axios from "axios"; import MockAdapter from "axios-mock-adapter"; import wakatime from "../api/wakatime.js"; import { renderWakatimeCard } from "../src/cards/wakatime.js"; +import { CACHE_TTL, DURATIONS } from "../src/common/cache.js"; const wakaTimeData = { data: { @@ -119,4 +120,25 @@ describe("Test /api/wakatime", () => { expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); expect(res.send).toBeCalledWith(renderWakatimeCard(wakaTimeData.data, {})); }); + + it("should have proper cache", async () => { + const username = "anuraghazra"; + const req = { query: { username } }; + const res = { setHeader: jest.fn(), send: jest.fn() }; + mock + .onGet( + `https://wakatime.com/api/v1/users/${username}/stats?is_including_today=true`, + ) + .reply(200, wakaTimeData); + + await wakatime(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); + expect(res.setHeader).toBeCalledWith( + "Cache-Control", + `max-age=${CACHE_TTL.WAKATIME_CARD.DEFAULT}, ` + + `s-maxage=${CACHE_TTL.WAKATIME_CARD.DEFAULT}, ` + + `stale-while-revalidate=${DURATIONS.ONE_DAY}`, + ); + }); });