Skip to content

Commit 3123e68

Browse files
committed
fix: prevent totalCommitsFetch error result from being cached
This commit makes sure that when the `https://api.github.com/search/commits?q=author:anuraghazra` API fails the result is not cached.
1 parent 98f9045 commit 3123e68

File tree

3 files changed

+73
-31
lines changed

3 files changed

+73
-31
lines changed

api/index.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -50,20 +50,25 @@ export default async (req, res) => {
5050
}
5151

5252
try {
53-
const stats = await fetchStats(
53+
const statsResp = await fetchStats(
5454
username,
5555
parseBoolean(count_private),
5656
parseBoolean(include_all_commits),
5757
parseArray(exclude_repo),
5858
);
59+
const stats = statsResp.stats;
5960

6061
const cacheSeconds = clampValue(
6162
parseInt(cache_seconds || CONSTANTS.FOUR_HOURS, 10),
6263
CONSTANTS.FOUR_HOURS,
6364
CONSTANTS.ONE_DAY,
6465
);
6566

66-
res.setHeader("Cache-Control", `public, max-age=${cacheSeconds}`);
67+
if (statsResp.success) {
68+
res.setHeader("Cache-Control", `public, max-age=${cacheSeconds}`);
69+
} else {
70+
res.setHeader("Cache-Control", `no-cache, no-store, must-revalidate`); // Don't cache unsuccessful responses.
71+
}
6772

6873
return res.send(
6974
renderStatsCard(stats, {

src/fetchers/stats-fetcher.js

+36-24
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ const fetcher = (variables, token) => {
6767
*
6868
* @param {import('axios').AxiosRequestHeaders} variables Fetcher variables.
6969
* @param {string} token Github token.
70-
* @returns {Promise<import('../common/types').StatsFetcherResponse>} Repositories fetcher response.
70+
* @returns {Promise<import('../common/types').RepositoriesFetcherResponse>} Repositories fetcher response.
7171
*/
7272
const repositoriesFetcher = (variables, token) => {
7373
return request(
@@ -98,46 +98,54 @@ const repositoriesFetcher = (variables, token) => {
9898
);
9999
};
100100

101+
/**
102+
* Fetch all commits for a given username.
103+
*
104+
* @param {import('axios').AxiosRequestHeaders} variables Fetcher variables.
105+
* @param {string} token Github token.
106+
* @returns {Promise<import('../common/types').TotalCommitsFetcherResponse>} Total commits fetcher response.
107+
*
108+
* @see https://developer.github.com/v3/search/#search-commits.
109+
*/
110+
const totalCommitsFetcher = (variables, token) => {
111+
return axios({
112+
method: "get",
113+
url: `https://api.github.com/search/commits?q=author:${variables.login}`,
114+
headers: {
115+
"Content-Type": "application/json",
116+
Accept: "application/vnd.github.cloak-preview",
117+
Authorization: `token ${token}`,
118+
},
119+
});
120+
};
121+
101122
/**
102123
* Fetch all the commits for all the repositories of a given username.
103124
*
104125
* @param {*} username Github username.
105-
* @returns {Promise<number>} Total commits.
126+
* @returns {Promise<Object>} Object containing the total number of commits and a success boolean.
106127
*
107128
* @description Done like this because the Github API does not provide a way to fetch all the commits. See
108129
* #92#issuecomment-661026467 and #211 for more information.
109130
*/
110-
const totalCommitsFetcher = async (username) => {
131+
const fetchTotalCommits = async (username) => {
111132
if (!githubUsernameRegex.test(username)) {
112133
logger.log("Invalid username");
113-
return 0;
134+
return { totalCommits: 0, success: false };
114135
}
115136

116-
// https://developer.github.com/v3/search/#search-commits
117-
const fetchTotalCommits = (variables, token) => {
118-
return axios({
119-
method: "get",
120-
url: `https://api.github.com/search/commits?q=author:${variables.login}`,
121-
headers: {
122-
"Content-Type": "application/json",
123-
Accept: "application/vnd.github.cloak-preview",
124-
Authorization: `token ${token}`,
125-
},
126-
});
127-
};
128-
129137
try {
130-
let res = await retryer(fetchTotalCommits, { login: username });
138+
let res = await retryer(totalCommitsFetcher, { login: username });
131139
let total_count = res.data.total_count;
132140
if (!!total_count && !isNaN(total_count)) {
133-
return res.data.total_count;
141+
return { totalCommits: res.data.total_count, success: true };
134142
}
135143
} catch (err) {
136144
logger.log(err);
137145
}
138146
// just return 0 if there is something wrong so that
139147
// we don't break the whole app
140-
return 0;
148+
return { totalCommits: 0, success: false };
141149
};
142150

143151
/**
@@ -186,7 +194,7 @@ const totalStarsFetcher = async (username, repoToHide) => {
186194
* @param {string} username Github username.
187195
* @param {boolean} count_private Include private contributions.
188196
* @param {boolean} include_all_commits Include all commits.
189-
* @returns {Promise<import("./types").StatsData>} Stats data.
197+
* @returns {Promise<Object>} Object containing the stats data a success boolean that can be used to handle the caching behavior.
190198
*/
191199
async function fetchStats(
192200
username,
@@ -247,9 +255,13 @@ async function fetchStats(
247255
stats.totalCommits = user.contributionsCollection.totalCommitContributions;
248256

249257
// if include_all_commits then just get that,
250-
// since totalCommitsFetcher already sends totalCommits no need to +=
258+
// NOTE: Since fetchTotalCommits already sends totalCommits no need to +=.
259+
let totalCommitsResp;
260+
let success = true;
251261
if (include_all_commits) {
252-
stats.totalCommits = await totalCommitsFetcher(username);
262+
totalCommitsResp = await fetchTotalCommits(username);
263+
stats.totalCommits = totalCommitsResp.totalCommits;
264+
success = totalCommitsResp.success;
253265
}
254266

255267
// if count_private then add private commits to totalCommits so far.
@@ -274,7 +286,7 @@ async function fetchStats(
274286
issues: stats.totalIssues,
275287
});
276288

277-
return stats;
289+
return { stats, success };
278290
}
279291

280292
export { fetchStats };

tests/fetchStats.test.js

+30-5
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ afterEach(() => {
108108

109109
describe("Test fetchStats", () => {
110110
it("should fetch correct stats", async () => {
111-
let stats = await fetchStats("anuraghazra");
111+
let { stats } = await fetchStats("anuraghazra");
112112
const rank = calculateRank({
113113
totalCommits: 100,
114114
totalRepos: 5,
@@ -140,7 +140,7 @@ describe("Test fetchStats", () => {
140140
.onPost("https://api.github.com/graphql")
141141
.replyOnce(200, repositoriesWithZeroStarsData);
142142

143-
let stats = await fetchStats("anuraghazra");
143+
let { stats } = await fetchStats("anuraghazra");
144144
const rank = calculateRank({
145145
totalCommits: 100,
146146
totalRepos: 5,
@@ -172,7 +172,7 @@ describe("Test fetchStats", () => {
172172
});
173173

174174
it("should fetch and add private contributions", async () => {
175-
let stats = await fetchStats("anuraghazra", true);
175+
let { stats } = await fetchStats("anuraghazra", true);
176176
const rank = calculateRank({
177177
totalCommits: 150,
178178
totalRepos: 5,
@@ -201,7 +201,7 @@ describe("Test fetchStats", () => {
201201
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
202202
.reply(200, { total_count: 1000 });
203203

204-
let stats = await fetchStats("anuraghazra", true, true);
204+
let { stats } = await fetchStats("anuraghazra", true, true);
205205
const rank = calculateRank({
206206
totalCommits: 1050,
207207
totalRepos: 5,
@@ -225,12 +225,37 @@ describe("Test fetchStats", () => {
225225
});
226226
});
227227

228+
it("should return `true` success boolean when 'include_all_commits' is `false`", async () => {
229+
let { success } = await fetchStats("anuraghazra", true, false);
230+
expect(success).toStrictEqual(true);
231+
});
232+
233+
it("should return `true` success boolean when 'include_all_commits' is `true` and total commits were fetched successfully", async () => {
234+
mock
235+
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
236+
.reply(200, { total_count: 1000 });
237+
238+
let { success } = await fetchStats("anuraghazra", true, true);
239+
expect(success).toStrictEqual(true);
240+
});
241+
242+
it("should return `false` success boolean when 'include_all_commits' is `true` and total commits could not be fetched", async () => {
243+
mock
244+
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
245+
.reply(404, "Could not fetch total commits");
246+
247+
let { success } = await fetchStats("anuraghazra", true, true);
248+
expect(success).toStrictEqual(false);
249+
});
250+
228251
it("should exclude stars of the `test-repo-1` repository", async () => {
229252
mock
230253
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
231254
.reply(200, { total_count: 1000 });
232255

233-
let stats = await fetchStats("anuraghazra", true, true, ["test-repo-1"]);
256+
let { stats } = await fetchStats("anuraghazra", true, true, [
257+
"test-repo-1",
258+
]);
234259
const rank = calculateRank({
235260
totalCommits: 1050,
236261
totalRepos: 5,

0 commit comments

Comments
 (0)