mirror of
https://github.com/Aviortheking/codestats-readme.git
synced 2025-04-22 10:42:08 +00:00
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
This commit is contained in:
commit
9328a57ea6
@ -18,7 +18,9 @@ module.exports = async (req, res) => {
|
|||||||
} = 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 {
|
||||||
stats = await fetchStats(username);
|
stats = await fetchStats(username);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -14,6 +14,8 @@ module.exports = async (req, res) => {
|
|||||||
} = 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 {
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
const { request } = require("./utils");
|
const { request } = require("./utils");
|
||||||
|
const retryer = require("./retryer");
|
||||||
|
|
||||||
async function fetchRepo(username, reponame) {
|
const fetcher = (variables, token) => {
|
||||||
if (!username || !reponame) {
|
return request(
|
||||||
throw new Error("Invalid username or reponame");
|
{
|
||||||
}
|
query: `
|
||||||
|
|
||||||
const res = await request({
|
|
||||||
query: `
|
|
||||||
fragment RepoInfo on Repository {
|
fragment RepoInfo on Repository {
|
||||||
name
|
name
|
||||||
stargazers {
|
stargazers {
|
||||||
@ -33,11 +31,20 @@ async function fetchRepo(username, reponame) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
variables: {
|
variables,
|
||||||
login: username,
|
|
||||||
repo: reponame,
|
|
||||||
},
|
},
|
||||||
});
|
{
|
||||||
|
Authorization: `bearer ${token}`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
async function fetchRepo(username, reponame) {
|
||||||
|
if (!username || !reponame) {
|
||||||
|
throw new Error("Invalid username or reponame");
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = await retryer(fetcher, { login: username, repo: reponame });
|
||||||
|
|
||||||
const data = res.data.data;
|
const data = res.data.data;
|
||||||
|
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
const { request } = require("./utils");
|
const { request } = require("./utils");
|
||||||
|
const retryer = require("./retryer");
|
||||||
const calculateRank = require("./calculateRank");
|
const calculateRank = require("./calculateRank");
|
||||||
require("dotenv").config();
|
require("dotenv").config();
|
||||||
|
|
||||||
async function fetchStats(username) {
|
const fetcher = (variables, token) => {
|
||||||
if (!username) throw Error("Invalid username");
|
return request(
|
||||||
|
{
|
||||||
const res = await request({
|
query: `
|
||||||
query: `
|
|
||||||
query userInfo($login: String!) {
|
query userInfo($login: String!) {
|
||||||
user(login: $login) {
|
user(login: $login) {
|
||||||
name
|
name
|
||||||
login
|
login
|
||||||
repositoriesContributedTo(first: 100, contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) {
|
|
||||||
totalCount
|
|
||||||
}
|
|
||||||
contributionsCollection {
|
contributionsCollection {
|
||||||
totalCommitContributions
|
totalCommitContributions
|
||||||
}
|
}
|
||||||
pullRequests(first: 100) {
|
repositoriesContributedTo(first: 1, contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) {
|
||||||
totalCount
|
totalCount
|
||||||
}
|
}
|
||||||
issues(first: 100) {
|
pullRequests(first: 1) {
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
issues(first: 1) {
|
||||||
totalCount
|
totalCount
|
||||||
}
|
}
|
||||||
followers {
|
followers {
|
||||||
@ -36,9 +36,17 @@ async function fetchStats(username) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
variables: { login: username },
|
variables,
|
||||||
});
|
},
|
||||||
|
{
|
||||||
|
Authorization: `bearer ${token}`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
async function fetchStats(username) {
|
||||||
|
if (!username) throw Error("Invalid username");
|
||||||
|
|
||||||
const stats = {
|
const stats = {
|
||||||
name: "",
|
name: "",
|
||||||
@ -47,12 +55,14 @@ async function fetchStats(username) {
|
|||||||
totalIssues: 0,
|
totalIssues: 0,
|
||||||
totalStars: 0,
|
totalStars: 0,
|
||||||
contributedTo: 0,
|
contributedTo: 0,
|
||||||
rank: "C",
|
rank: { level: "C", score: 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let res = await retryer(fetcher, { login: username });
|
||||||
|
|
||||||
if (res.data.errors) {
|
if (res.data.errors) {
|
||||||
console.log(res.data.errors);
|
console.log(res.data.errors);
|
||||||
throw Error("Could not fetch user");
|
throw Error(res.data.errors[0].message || "Could not fetch user");
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = res.data.data.user;
|
const user = res.data.data.user;
|
||||||
|
43
src/retryer.js
Normal file
43
src/retryer.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
const retryer = async (fetcher, variables, retries = 0) => {
|
||||||
|
if (retries > 7) {
|
||||||
|
throw new Error("Maximum retries exceeded");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
console.log(`Trying PAT_${retries + 1}`);
|
||||||
|
|
||||||
|
// try to fetch with the first token since RETRIES is 0 index i'm adding +1
|
||||||
|
let response = await fetcher(
|
||||||
|
variables,
|
||||||
|
process.env[`PAT_${retries + 1}`],
|
||||||
|
retries
|
||||||
|
);
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
const isRateExceeded = response.data.errors && response.data.errors[0].type === "RATE_LIMITED";
|
||||||
|
|
||||||
|
// if rate limit is hit increase the RETRIES and recursively call the retryer
|
||||||
|
// with username, and current RETRIES
|
||||||
|
if (isRateExceeded) {
|
||||||
|
console.log(`PAT_${retries + 1} Failed`);
|
||||||
|
retries++;
|
||||||
|
// directly return from the function
|
||||||
|
return retryer(fetcher, variables, retries);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally return the response
|
||||||
|
return response;
|
||||||
|
} catch (err) {
|
||||||
|
// prettier-ignore
|
||||||
|
// also checking for bad credentials if any tokens gets invalidated
|
||||||
|
const isBadCredential = err.response.data && err.response.data.message === "Bad credentials";
|
||||||
|
|
||||||
|
if (isBadCredential) {
|
||||||
|
console.log(`PAT_${retries + 1} Failed`);
|
||||||
|
retries++;
|
||||||
|
// directly return from the function
|
||||||
|
return retryer(fetcher, variables, retries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = retryer;
|
12
src/utils.js
12
src/utils.js
@ -33,13 +33,13 @@ function isValidHexColor(hexColor) {
|
|||||||
).test(hexColor);
|
).test(hexColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
function request(data) {
|
function request(data, headers) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
axios({
|
axios({
|
||||||
url: "https://api.github.com/graphql",
|
url: "https://api.github.com/graphql",
|
||||||
method: "post",
|
method: "post",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `bearer ${process.env.GITHUB_TOKEN}`,
|
...headers,
|
||||||
},
|
},
|
||||||
data,
|
data,
|
||||||
})
|
})
|
||||||
@ -48,4 +48,10 @@ function request(data) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { renderError, kFormatter, encodeHTML, isValidHexColor, request };
|
module.exports = {
|
||||||
|
renderError,
|
||||||
|
kFormatter,
|
||||||
|
encodeHTML,
|
||||||
|
isValidHexColor,
|
||||||
|
request,
|
||||||
|
};
|
||||||
|
@ -74,7 +74,7 @@ describe("Test fetchStats", () => {
|
|||||||
mock.onPost("https://api.github.com/graphql").reply(200, error);
|
mock.onPost("https://api.github.com/graphql").reply(200, error);
|
||||||
|
|
||||||
await expect(fetchStats("anuraghazra")).rejects.toThrow(
|
await expect(fetchStats("anuraghazra")).rejects.toThrow(
|
||||||
"Could not fetch user"
|
"Could not resolve to a User with the login of 'noname'."
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
50
tests/retryer.test.js
Normal file
50
tests/retryer.test.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
require("@testing-library/jest-dom");
|
||||||
|
const retryer = require("../src/retryer");
|
||||||
|
|
||||||
|
const fetcher = jest.fn((variables, token) => {
|
||||||
|
console.log(variables, token);
|
||||||
|
return new Promise((res, rej) => res({ data: "ok" }));
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetcherFail = jest.fn(() => {
|
||||||
|
return new Promise((res, rej) =>
|
||||||
|
res({ data: { errors: [{ type: "RATE_LIMITED" }] } })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetcherFailOnSecondTry = jest.fn((_vars, _token, retries) => {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
// faking rate limit
|
||||||
|
if (retries < 1) {
|
||||||
|
return res({ data: { errors: [{ type: "RATE_LIMITED" }] } });
|
||||||
|
}
|
||||||
|
return res({ data: "ok" });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Test Retryer", () => {
|
||||||
|
it("retryer should return value and have zero retries on first try", async () => {
|
||||||
|
let res = await retryer(fetcher, {});
|
||||||
|
|
||||||
|
expect(fetcher).toBeCalledTimes(1);
|
||||||
|
expect(res).toStrictEqual({ data: "ok" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("retryer should return value and have 2 retries", async () => {
|
||||||
|
let res = await retryer(fetcherFailOnSecondTry, {});
|
||||||
|
|
||||||
|
expect(fetcherFailOnSecondTry).toBeCalledTimes(2);
|
||||||
|
expect(res).toStrictEqual({ data: "ok" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("retryer should throw error if maximum retries reached", async () => {
|
||||||
|
let res;
|
||||||
|
|
||||||
|
try {
|
||||||
|
res = await retryer(fetcherFail, {});
|
||||||
|
} catch (err) {
|
||||||
|
expect(fetcherFail).toBeCalledTimes(8);
|
||||||
|
expect(err.message).toBe("Maximum retries exceeded");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user