feat: added ability to set custom cache

This commit is contained in:
anuraghazra 2020-07-20 21:43:51 +05:30
parent dd2c7ed278
commit fdf445d734
5 changed files with 134 additions and 34 deletions

View File

@ -1,5 +1,10 @@
require("dotenv").config(); require("dotenv").config();
const { renderError, parseBoolean } = require("../src/utils"); const {
renderError,
parseBoolean,
clampValue,
CONSTANTS,
} = require("../src/utils");
const fetchStats = require("../src/fetchStats"); const fetchStats = require("../src/fetchStats");
const renderStatsCard = require("../src/renderStatsCard"); const renderStatsCard = require("../src/renderStatsCard");
@ -17,10 +22,10 @@ module.exports = async (req, res) => {
text_color, text_color,
bg_color, bg_color,
theme, theme,
cache_seconds,
} = req.query; } = req.query;
let stats; let stats;
res.setHeader("Cache-Control", "public, max-age=1800");
res.setHeader("Content-Type", "image/svg+xml"); res.setHeader("Content-Type", "image/svg+xml");
try { try {
@ -29,6 +34,14 @@ module.exports = async (req, res) => {
return res.send(renderError(err.message)); return res.send(renderError(err.message));
} }
const cacheSeconds = clampValue(
parseInt(cache_seconds || CONSTANTS.THIRTY_MINUTES, 10),
CONSTANTS.THIRTY_MINUTES,
CONSTANTS.ONE_DAY
);
res.setHeader("Cache-Control", `public, max-age=${cacheSeconds}`);
res.send( res.send(
renderStatsCard(stats, { renderStatsCard(stats, {
hide: JSON.parse(hide || "[]"), hide: JSON.parse(hide || "[]"),

View File

@ -1,5 +1,10 @@
require("dotenv").config(); require("dotenv").config();
const { renderError, parseBoolean } = require("../src/utils"); const {
renderError,
parseBoolean,
clampValue,
CONSTANTS,
} = require("../src/utils");
const fetchRepo = require("../src/fetchRepo"); const fetchRepo = require("../src/fetchRepo");
const renderRepoCard = require("../src/renderRepoCard"); const renderRepoCard = require("../src/renderRepoCard");
@ -13,11 +18,11 @@ module.exports = async (req, res) => {
bg_color, bg_color,
theme, theme,
show_owner, show_owner,
cache_seconds,
} = req.query; } = req.query;
let repoData; let repoData;
res.setHeader("Cache-Control", "public, max-age=1800");
res.setHeader("Content-Type", "image/svg+xml"); res.setHeader("Content-Type", "image/svg+xml");
try { try {
@ -27,6 +32,27 @@ module.exports = async (req, res) => {
return res.send(renderError(err.message)); return res.send(renderError(err.message));
} }
let cacheSeconds = clampValue(
parseInt(cache_seconds || CONSTANTS.THIRTY_MINUTES, 10),
CONSTANTS.THIRTY_MINUTES,
CONSTANTS.ONE_DAY
);
/*
if star count & fork count is over 1k then we are kFormating the text
and if both are zero we are not showing the stats
so we can just make the cache longer, since there is no need to frequent updates
*/
const stars = repoData.stargazers.totalCount;
const forks = repoData.forkCount;
const isBothOver1K = stars > 1000 && forks > 1000;
const isBothUnder1 = stars < 1 && forks < 1;
if (!cache_seconds && (isBothOver1K || isBothUnder1)) {
cacheSeconds = CONSTANTS.TWO_HOURS;
}
res.setHeader("Cache-Control", `public, max-age=${cacheSeconds}`);
res.send( res.send(
renderRepoCard(repoData, { renderRepoCard(repoData, {
title_color, title_color,

View File

@ -75,7 +75,7 @@ const renderRepoCard = (repo, options = {}) => {
`; `;
const svgForks = const svgForks =
totalForks > 0 && forkCount > 0 &&
` `
<svg class="icon" y="-12" viewBox="0 0 16 16" version="1.1" width="16" height="16"> <svg class="icon" y="-12" viewBox="0 0 16 16" version="1.1" width="16" height="16">
${icons.fork} ${icons.fork}

View File

@ -44,6 +44,10 @@ function parseBoolean(value) {
} }
} }
function clampValue(number, min, max) {
return Math.max(min, Math.min(number, max));
}
function fallbackColor(color, fallbackColor) { function fallbackColor(color, fallbackColor) {
return (isValidHexColor(color) && `#${color}`) || fallbackColor; return (isValidHexColor(color) && `#${color}`) || fallbackColor;
} }
@ -112,6 +116,12 @@ function getCardColors({
return { titleColor, iconColor, textColor, bgColor }; return { titleColor, iconColor, textColor, bgColor };
} }
const CONSTANTS = {
THIRTY_MINUTES: 1800,
TWO_HOURS: 7200,
ONE_DAY: 86400,
};
module.exports = { module.exports = {
renderError, renderError,
kFormatter, kFormatter,
@ -122,4 +132,6 @@ module.exports = {
fallbackColor, fallbackColor,
FlexLayout, FlexLayout,
getCardColors, getCardColors,
clampValue,
CONSTANTS,
}; };

View File

@ -3,7 +3,7 @@ const axios = require("axios");
const MockAdapter = require("axios-mock-adapter"); const MockAdapter = require("axios-mock-adapter");
const api = require("../api/index"); const api = require("../api/index");
const renderStatsCard = require("../src/renderStatsCard"); const renderStatsCard = require("../src/renderStatsCard");
const { renderError } = require("../src/utils"); const { renderError, CONSTANTS } = require("../src/utils");
const calculateRank = require("../src/calculateRank"); const calculateRank = require("../src/calculateRank");
const stats = { const stats = {
@ -55,22 +55,29 @@ const error = {
const mock = new MockAdapter(axios); const mock = new MockAdapter(axios);
const faker = (query, data) => {
const req = {
query: {
username: "anuraghazra",
...query,
},
};
const res = {
setHeader: jest.fn(),
send: jest.fn(),
};
mock.onPost("https://api.github.com/graphql").reply(200, data);
return { req, res };
};
afterEach(() => { afterEach(() => {
mock.reset(); mock.reset();
}); });
describe("Test /api/", () => { describe("Test /api/", () => {
it("should test the request", async () => { it("should test the request", async () => {
const req = { const { req, res } = faker({}, data);
query: {
username: "anuraghazra",
},
};
const res = {
setHeader: jest.fn(),
send: jest.fn(),
};
mock.onPost("https://api.github.com/graphql").reply(200, data);
await api(req, res); await api(req, res);
@ -79,16 +86,7 @@ describe("Test /api/", () => {
}); });
it("should render error card on error", async () => { it("should render error card on error", async () => {
const req = { const { req, res } = faker({}, error);
query: {
username: "anuraghazra",
},
};
const res = {
setHeader: jest.fn(),
send: jest.fn(),
};
mock.onPost("https://api.github.com/graphql").reply(200, error);
await api(req, res); await api(req, res);
@ -97,8 +95,8 @@ describe("Test /api/", () => {
}); });
it("should get the query options", async () => { it("should get the query options", async () => {
const req = { const { req, res } = faker(
query: { {
username: "anuraghazra", username: "anuraghazra",
hide: `["issues","prs","contribs"]`, hide: `["issues","prs","contribs"]`,
show_icons: true, show_icons: true,
@ -109,12 +107,8 @@ describe("Test /api/", () => {
text_color: "fff", text_color: "fff",
bg_color: "fff", bg_color: "fff",
}, },
}; data
const res = { );
setHeader: jest.fn(),
send: jest.fn(),
};
mock.onPost("https://api.github.com/graphql").reply(200, data);
await api(req, res); await api(req, res);
@ -132,4 +126,59 @@ describe("Test /api/", () => {
}) })
); );
}); });
it("should have proper cache", async () => {
const { req, res } = faker({}, data);
mock.onPost("https://api.github.com/graphql").reply(200, data);
await api(req, res);
expect(res.setHeader.mock.calls).toEqual([
["Content-Type", "image/svg+xml"],
["Cache-Control", `public, max-age=${CONSTANTS.THIRTY_MINUTES}`],
]);
});
it("should set proper cache", async () => {
const { req, res } = faker({ cache_seconds: 2000 }, data);
await api(req, res);
expect(res.setHeader.mock.calls).toEqual([
["Content-Type", "image/svg+xml"],
["Cache-Control", `public, max-age=${2000}`],
]);
});
it("should set proper cache with clamped values", async () => {
{
let { req, res } = faker({ cache_seconds: 200000 }, data);
await api(req, res);
expect(res.setHeader.mock.calls).toEqual([
["Content-Type", "image/svg+xml"],
["Cache-Control", `public, max-age=${CONSTANTS.ONE_DAY}`],
]);
}
// note i'm using block scoped vars
{
let { req, res } = faker({ cache_seconds: 0 }, data);
await api(req, res);
expect(res.setHeader.mock.calls).toEqual([
["Content-Type", "image/svg+xml"],
["Cache-Control", `public, max-age=${CONSTANTS.THIRTY_MINUTES}`],
]);
}
{
let { req, res } = faker({ cache_seconds: -10000 }, data);
await api(req, res);
expect(res.setHeader.mock.calls).toEqual([
["Content-Type", "image/svg+xml"],
["Cache-Control", `public, max-age=${CONSTANTS.THIRTY_MINUTES}`],
]);
}
});
}); });