Merge pull request #117 from anuraghazra/custom-cache

feat: added ability to set custom cache
This commit is contained in:
Anurag Hazra 2020-07-20 21:56:16 +05:30 committed by GitHub
commit a5fa1a0d1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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,15 +55,11 @@ const error = {
const mock = new MockAdapter(axios); const mock = new MockAdapter(axios);
afterEach(() => { const faker = (query, data) => {
mock.reset();
});
describe("Test /api/", () => {
it("should test the request", async () => {
const req = { const req = {
query: { query: {
username: "anuraghazra", username: "anuraghazra",
...query,
}, },
}; };
const res = { const res = {
@ -72,6 +68,17 @@ describe("Test /api/", () => {
}; };
mock.onPost("https://api.github.com/graphql").reply(200, data); mock.onPost("https://api.github.com/graphql").reply(200, data);
return { req, res };
};
afterEach(() => {
mock.reset();
});
describe("Test /api/", () => {
it("should test the request", async () => {
const { req, res } = faker({}, data);
await api(req, res); await api(req, res);
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
@ -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}`],
]);
}
});
}); });