Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9328a57

Browse files
authoredJul 15, 2020
Merge pull request #58 from anuraghazra/fix-ratelimit
fix: increase github rate limit with multiple PATs - special thanks to @filiptronicek @ApurvShah007 @garvit-joshi for helping out :D
2 parents 2efb399 + 429d65a commit 9328a57

8 files changed

+150
-30
lines changed
 

‎api/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ module.exports = async (req, res) => {
1818
} = req.query;
1919
let stats;
2020

21+
res.setHeader("Cache-Control", "public, max-age=1800");
2122
res.setHeader("Content-Type", "image/svg+xml");
23+
2224
try {
2325
stats = await fetchStats(username);
2426
} catch (err) {

‎api/pin.js

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ module.exports = async (req, res) => {
1414
} = req.query;
1515

1616
let repoData;
17+
18+
res.setHeader("Cache-Control", "public, max-age=1800");
1719
res.setHeader("Content-Type", "image/svg+xml");
1820

1921
try {

‎src/fetchRepo.js

+18-11
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
const { request } = require("./utils");
2+
const retryer = require("./retryer");
23

3-
async function fetchRepo(username, reponame) {
4-
if (!username || !reponame) {
5-
throw new Error("Invalid username or reponame");
6-
}
7-
8-
const res = await request({
9-
query: `
4+
const fetcher = (variables, token) => {
5+
return request(
6+
{
7+
query: `
108
fragment RepoInfo on Repository {
119
name
1210
stargazers {
@@ -33,11 +31,20 @@ async function fetchRepo(username, reponame) {
3331
}
3432
}
3533
`,
36-
variables: {
37-
login: username,
38-
repo: reponame,
34+
variables,
3935
},
40-
});
36+
{
37+
Authorization: `bearer ${token}`,
38+
}
39+
);
40+
};
41+
42+
async function fetchRepo(username, reponame) {
43+
if (!username || !reponame) {
44+
throw new Error("Invalid username or reponame");
45+
}
46+
47+
let res = await retryer(fetcher, { login: username, repo: reponame });
4148

4249
const data = res.data.data;
4350

‎src/fetchStats.js

+25-15
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
const { request } = require("./utils");
2+
const retryer = require("./retryer");
23
const calculateRank = require("./calculateRank");
34
require("dotenv").config();
45

5-
async function fetchStats(username) {
6-
if (!username) throw Error("Invalid username");
7-
8-
const res = await request({
9-
query: `
6+
const fetcher = (variables, token) => {
7+
return request(
8+
{
9+
query: `
1010
query userInfo($login: String!) {
1111
user(login: $login) {
1212
name
1313
login
14-
repositoriesContributedTo(first: 100, contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) {
15-
totalCount
16-
}
1714
contributionsCollection {
1815
totalCommitContributions
1916
}
20-
pullRequests(first: 100) {
17+
repositoriesContributedTo(first: 1, contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) {
18+
totalCount
19+
}
20+
pullRequests(first: 1) {
2121
totalCount
2222
}
23-
issues(first: 100) {
23+
issues(first: 1) {
2424
totalCount
2525
}
2626
followers {
@@ -36,9 +36,17 @@ async function fetchStats(username) {
3636
}
3737
}
3838
}
39-
`,
40-
variables: { login: username },
41-
});
39+
`,
40+
variables,
41+
},
42+
{
43+
Authorization: `bearer ${token}`,
44+
}
45+
);
46+
};
47+
48+
async function fetchStats(username) {
49+
if (!username) throw Error("Invalid username");
4250

4351
const stats = {
4452
name: "",
@@ -47,12 +55,14 @@ async function fetchStats(username) {
4755
totalIssues: 0,
4856
totalStars: 0,
4957
contributedTo: 0,
50-
rank: "C",
58+
rank: { level: "C", score: 0 },
5159
};
5260

61+
let res = await retryer(fetcher, { login: username });
62+
5363
if (res.data.errors) {
5464
console.log(res.data.errors);
55-
throw Error("Could not fetch user");
65+
throw Error(res.data.errors[0].message || "Could not fetch user");
5666
}
5767

5868
const user = res.data.data.user;

‎src/retryer.js

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
const retryer = async (fetcher, variables, retries = 0) => {
2+
if (retries > 7) {
3+
throw new Error("Maximum retries exceeded");
4+
}
5+
try {
6+
console.log(`Trying PAT_${retries + 1}`);
7+
8+
// try to fetch with the first token since RETRIES is 0 index i'm adding +1
9+
let response = await fetcher(
10+
variables,
11+
process.env[`PAT_${retries + 1}`],
12+
retries
13+
);
14+
15+
// prettier-ignore
16+
const isRateExceeded = response.data.errors && response.data.errors[0].type === "RATE_LIMITED";
17+
18+
// if rate limit is hit increase the RETRIES and recursively call the retryer
19+
// with username, and current RETRIES
20+
if (isRateExceeded) {
21+
console.log(`PAT_${retries + 1} Failed`);
22+
retries++;
23+
// directly return from the function
24+
return retryer(fetcher, variables, retries);
25+
}
26+
27+
// finally return the response
28+
return response;
29+
} catch (err) {
30+
// prettier-ignore
31+
// also checking for bad credentials if any tokens gets invalidated
32+
const isBadCredential = err.response.data && err.response.data.message === "Bad credentials";
33+
34+
if (isBadCredential) {
35+
console.log(`PAT_${retries + 1} Failed`);
36+
retries++;
37+
// directly return from the function
38+
return retryer(fetcher, variables, retries);
39+
}
40+
}
41+
};
42+
43+
module.exports = retryer;

‎src/utils.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ function isValidHexColor(hexColor) {
3333
).test(hexColor);
3434
}
3535

36-
function request(data) {
36+
function request(data, headers) {
3737
return new Promise((resolve, reject) => {
3838
axios({
3939
url: "https://api.github.com/graphql",
4040
method: "post",
4141
headers: {
42-
Authorization: `bearer ${process.env.GITHUB_TOKEN}`,
42+
...headers,
4343
},
4444
data,
4545
})
@@ -48,4 +48,10 @@ function request(data) {
4848
});
4949
}
5050

51-
module.exports = { renderError, kFormatter, encodeHTML, isValidHexColor, request };
51+
module.exports = {
52+
renderError,
53+
kFormatter,
54+
encodeHTML,
55+
isValidHexColor,
56+
request,
57+
};

‎tests/fetchStats.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ describe("Test fetchStats", () => {
7474
mock.onPost("https://api.github.com/graphql").reply(200, error);
7575

7676
await expect(fetchStats("anuraghazra")).rejects.toThrow(
77-
"Could not fetch user"
77+
"Could not resolve to a User with the login of 'noname'."
7878
);
7979
});
8080
});

‎tests/retryer.test.js

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
require("@testing-library/jest-dom");
2+
const retryer = require("../src/retryer");
3+
4+
const fetcher = jest.fn((variables, token) => {
5+
console.log(variables, token);
6+
return new Promise((res, rej) => res({ data: "ok" }));
7+
});
8+
9+
const fetcherFail = jest.fn(() => {
10+
return new Promise((res, rej) =>
11+
res({ data: { errors: [{ type: "RATE_LIMITED" }] } })
12+
);
13+
});
14+
15+
const fetcherFailOnSecondTry = jest.fn((_vars, _token, retries) => {
16+
return new Promise((res, rej) => {
17+
// faking rate limit
18+
if (retries < 1) {
19+
return res({ data: { errors: [{ type: "RATE_LIMITED" }] } });
20+
}
21+
return res({ data: "ok" });
22+
});
23+
});
24+
25+
describe("Test Retryer", () => {
26+
it("retryer should return value and have zero retries on first try", async () => {
27+
let res = await retryer(fetcher, {});
28+
29+
expect(fetcher).toBeCalledTimes(1);
30+
expect(res).toStrictEqual({ data: "ok" });
31+
});
32+
33+
it("retryer should return value and have 2 retries", async () => {
34+
let res = await retryer(fetcherFailOnSecondTry, {});
35+
36+
expect(fetcherFailOnSecondTry).toBeCalledTimes(2);
37+
expect(res).toStrictEqual({ data: "ok" });
38+
});
39+
40+
it("retryer should throw error if maximum retries reached", async () => {
41+
let res;
42+
43+
try {
44+
res = await retryer(fetcherFail, {});
45+
} catch (err) {
46+
expect(fetcherFail).toBeCalledTimes(8);
47+
expect(err.message).toBe("Maximum retries exceeded");
48+
}
49+
});
50+
});

0 commit comments

Comments
 (0)
Failed to load comments.