diff --git a/backend/api-renamed/downgrade.js b/backend/api-renamed/downgrade.js index 08975e04..39b69db6 100644 --- a/backend/api-renamed/downgrade.js +++ b/backend/api-renamed/downgrade.js @@ -1,4 +1,4 @@ -import { getUserAccess, deleteUser } from "../src/common/database.js"; +import { getUserAccessByKey, deleteUser } from "../src/common/database.js"; import axios from "axios"; import { logger } from "../src/common/log.js"; @@ -23,7 +23,7 @@ export default async (req, res) => { } // get token and private access status - const userAccess = await getUserAccess(user_key); + const userAccess = await getUserAccessByKey(user_key); if (!userAccess) { res.statusCode = 404; res.send("user not found"); diff --git a/backend/api-renamed/status/up.js b/backend/api-renamed/status/up.js index a942d0d7..05b5e263 100644 --- a/backend/api-renamed/status/up.js +++ b/backend/api-renamed/status/up.js @@ -87,7 +87,7 @@ export default async (req, res) => { try { let PATsValid = true; try { - await retryer(uptimeFetcher, {}); + await retryer(uptimeFetcher, null, {}); } catch (err) { // Resolve eslint no-unused-vars err; diff --git a/backend/api-renamed/user-access.js b/backend/api-renamed/user-access.js index dfadac35..a41af4cf 100644 --- a/backend/api-renamed/user-access.js +++ b/backend/api-renamed/user-access.js @@ -1,5 +1,5 @@ import { logger } from "../src/common/log.js"; -import { getUserAccess } from "../src/common/database.js"; +import { getUserAccessByKey } from "../src/common/database.js"; /** * @param {any} req The request. @@ -8,7 +8,7 @@ import { getUserAccess } from "../src/common/database.js"; export default async (req, res) => { const { user_key } = req.query; try { - const result = await getUserAccess(user_key); + const result = await getUserAccessByKey(user_key); if (!result) { res.statusCode = 404; diff --git a/backend/src/common/database.js b/backend/src/common/database.js index cfe92d0a..5c5e4950 100644 --- a/backend/src/common/database.js +++ b/backend/src/common/database.js @@ -197,7 +197,7 @@ export async function deleteUser(userKey) { * @param {string} userKey user key of the user to fetch information for * @returns {Promise<{token: string, privateAccess: boolean} | null>} token and private access status, or null if user not found */ -export async function getUserAccess(userKey) { +export async function getUserAccessByKey(userKey) { if (!pool) { return null; } @@ -225,3 +225,38 @@ export async function getUserAccess(userKey) { } } } + +/** + * Fetches token and private access status for a given username. + * + * @param {string} userName GitHub username of the user to fetch information for + * @returns {Promise<{token: string, privateAccess: boolean} | null>} token and private access status, or null if user not found + */ +export async function getUserAccessByName(userName) { + if (!pool) { + return null; + } + + const query = ` + SELECT access_token, private_access + FROM authenticated_users + WHERE user_id = $1 + LIMIT 1 + `; + try { + const { rows } = await pool.query(query, [userName]); + if (rows.length === 0) { + return null; + } + return { + token: rows[0].access_token, + privateAccess: rows[0].private_access + }; + } catch (err) { + if (err.code === "42P01") { + return null; + } else { + throw err; + } + } +} diff --git a/backend/src/common/retryer.js b/backend/src/common/retryer.js index 83366a94..ff36f3e9 100644 --- a/backend/src/common/retryer.js +++ b/backend/src/common/retryer.js @@ -2,19 +2,12 @@ import { CustomError } from "./error.js"; import { logger } from "./log.js"; +import { getUserAccessByKey, getUserAccessByName } from "./database.js"; function getRandomInt(max) { return Math.floor(Math.random() * max); } -// Script variables. - -// Count the number of GitHub API tokens available. -const PATs = Object.keys(process.env).filter((key) => - /PAT_\d*$/.exec(key), -).length; -const RETRIES = process.env.NODE_ENV === "test" ? 7 : PATs; - /** * @typedef {import("axios").AxiosResponse} AxiosResponse Axios response. * @typedef {(variables: any, token: string, retriesForTests?: number) => Promise} FetcherFunction Fetcher function. @@ -24,22 +17,39 @@ const RETRIES = process.env.NODE_ENV === "test" ? 7 : PATs; * Try to execute the fetcher function until it succeeds or the max number of retries is reached. * * @param {FetcherFunction} fetcher The fetcher function. + * @param username GitHub username of the user whose PAT to use, if available * @param {any} variables Object with arguments to pass to the fetcher function. * @returns {Promise} The response from the fetcher function. */ -const retryer = async (fetcher, variables) => { +const retryer = async (fetcher, username, variables) => { + let userPAT; + if (username) { + userPAT = await getUserAccessByName(username); + } + + let PATs; + if (userPAT) { + PATs = [userPAT.token]; + } else { + // Count the number of GitHub API tokens available. + PATs = Object.keys(process.env).filter((key) => + /PAT_\d*$/.exec(key), + ); + } + const RETRIES = process.env.NODE_ENV === "test" ? 7 : PATs.length; + if (!RETRIES) { throw new CustomError("No GitHub API tokens found", CustomError.NO_TOKENS); } - const startPAT = getRandomInt(PATs); + const startPAT = getRandomInt(PATs.length); for (let retries = 0; retries < RETRIES; retries++) { - const currentPAT = ((startPAT + retries) % PATs) + 1; + const currentPAT = ((startPAT + retries) % PATs.length); try { let response = await fetcher( variables, // @ts-ignore - process.env[`PAT_${currentPAT}`], + PATs[currentPAT], // used in tests for faking rate limit retries, ); @@ -89,5 +99,5 @@ const retryer = async (fetcher, variables) => { ); }; -export { retryer, RETRIES }; +export { retryer }; export default retryer; diff --git a/backend/src/fetchers/gist.js b/backend/src/fetchers/gist.js index d9cccc67..ea4ec183 100644 --- a/backend/src/fetchers/gist.js +++ b/backend/src/fetchers/gist.js @@ -90,7 +90,7 @@ const fetchGist = async (id) => { if (!id) { throw new MissingParamError(["id"], "/api/gist?id=GIST_ID"); } - const res = await retryer(fetcher, { gistName: id }); + const res = await retryer(fetcher, null, { gistName: id }); if (res.data.errors) { throw new Error(res.data.errors[0].message); } diff --git a/backend/src/fetchers/repo.js b/backend/src/fetchers/repo.js index 6d45f4e7..74d76f84 100644 --- a/backend/src/fetchers/repo.js +++ b/backend/src/fetchers/repo.js @@ -99,7 +99,7 @@ const fetchRepo = async ( throw new MissingParamError(["repo"], urlExample); } - let res = await retryer(fetcher, { login: owner, repo: reponame }); + let res = await retryer(fetcher, username, { login: owner, repo: reponame }); const data = res.data.data; diff --git a/backend/src/fetchers/stats.js b/backend/src/fetchers/stats.js index e5234064..7daab0a5 100644 --- a/backend/src/fetchers/stats.js +++ b/backend/src/fetchers/stats.js @@ -139,7 +139,7 @@ const statsFetcher = async ({ startTime, ownerAffiliations, }; - let res = await retryer(fetcher, variables); + let res = await retryer(fetcher, username, variables); if (res.data.errors) { return res; } @@ -221,7 +221,7 @@ const totalItemsFetcher = async (username, repo, owner, type, filter) => { let res; try { - res = await retryer(fetchTotalItems, { + res = await retryer(fetchTotalItems, username, { login: username, repo, owner, diff --git a/backend/src/fetchers/top-languages.js b/backend/src/fetchers/top-languages.js index c72ca089..e49319f1 100644 --- a/backend/src/fetchers/top-languages.js +++ b/backend/src/fetchers/top-languages.js @@ -73,7 +73,7 @@ const fetchTopLanguages = async ( } ownerAffiliations = parseOwnerAffiliations(ownerAffiliations); - const res = await retryer(fetcher, { login: username, ownerAffiliations }); + const res = await retryer(fetcher, username, { login: username, ownerAffiliations }); if (res.data.errors) { logger.error(res.data.errors);