diff --git a/.gitignore b/.gitignore index 25017502..2cdfa3d3 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,3 @@ vercel_token # IDE .vscode *.code-workspace - -.vercel diff --git a/src/common/utils.js b/src/common/utils.js index 1215fc9a..f5bee1fc 100644 --- a/src/common/utils.js +++ b/src/common/utils.js @@ -300,11 +300,16 @@ const CONSTANTS = { ONE_DAY: 86400, }; +const OWNER_AFFILIATIONS = ["OWNER", "COLLABORATOR", "ORGANIZATION_MEMBER"]; + const SECONDARY_ERROR_MESSAGES = { MAX_RETRY: "Please add an env variable called PAT_1 with your github token in vercel", USER_NOT_FOUND: "Make sure the provided username is not an organization", GRAPHQL_ERROR: "Please try again later", + INVALID_AFFILIATION: `Invalid owner affiliations. Valid values are: ${OWNER_AFFILIATIONS.join( + ", ", + )}`, }; /** @@ -324,6 +329,7 @@ class CustomError extends Error { static MAX_RETRY = "MAX_RETRY"; static USER_NOT_FOUND = "USER_NOT_FOUND"; static GRAPHQL_ERROR = "GRAPHQL_ERROR"; + static INVALID_AFFILIATION = "INVALID_AFFILIATION"; } /** @@ -423,6 +429,36 @@ const parseEmojis = (str) => { return toEmoji.get(emoji) || ""; }); }; +/** + * Parse owner affiliations. + * + * @param {string[]} affiliations + * @returns {string[]} Parsed affiliations. + * + * @throws {CustomError} If affiliations contains invalid values. + */ +const parseOwnerAffiliations = (affiliations) => { + // Set default value for ownerAffiliations. + // NOTE: Done here since parseArray() will always return an empty array even nothing + //was specified. + affiliations = + affiliations && affiliations.length > 0 + ? affiliations.map((affiliation) => affiliation.toUpperCase()) + : ["OWNER"]; + + // Check if ownerAffiliations contains valid values. + if ( + affiliations.some( + (affiliation) => !OWNER_AFFILIATIONS.includes(affiliation), + ) + ) { + throw new CustomError( + "Invalid query parameter", + CustomError.INVALID_AFFILIATION, + ); + } + return affiliations; +}; export { ERROR_CARD_LENGTH, @@ -441,10 +477,12 @@ export { wrapTextMultiline, logger, CONSTANTS, + OWNER_AFFILIATIONS, CustomError, MissingParamError, measureText, lowercaseTrim, chunkArray, parseEmojis, + parseOwnerAffiliations, }; diff --git a/src/fetchers/stats-fetcher.js b/src/fetchers/stats-fetcher.js index 74510128..5da5bc72 100644 --- a/src/fetchers/stats-fetcher.js +++ b/src/fetchers/stats-fetcher.js @@ -10,13 +10,14 @@ import { MissingParamError, request, wrapTextMultiline, + parseOwnerAffiliations, } from "../common/utils.js"; dotenv.config(); // GraphQL queries. const GRAPHQL_REPOS_FIELD = ` - repositories(first: 100, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}, after: $after) { + repositories(first: 100, after: $after, ownerAffiliations: $ownerAffiliations, orderBy: {direction: DESC, field: STARGAZERS}) { totalCount nodes { name @@ -40,7 +41,7 @@ const GRAPHQL_REPOS_QUERY = ` `; const GRAPHQL_STATS_QUERY = ` - query userInfo($login: String!, $after: String) { + query userInfo($login: String!, $after: String, $ownerAffiliations: [RepositoryAffiliation]) { user(login: $login) { name login @@ -92,6 +93,7 @@ const fetcher = (variables, token) => { * Fetch stats information for a given username. * * @param {string} username Github username. + * @param {string[]} ownerAffiliations The owner affiliations to filter by. Default: OWNER. * @returns {Promise} GraphQL Stats object. * * @description This function supports multi-page fetching if the 'FETCH_MULTI_PAGE_STARS' environment variable is set to true. @@ -105,7 +107,7 @@ const statsFetcher = async (username, ownerAffiliations) => { login: username, first: 100, after: endCursor, - ownerAffiliations: [ownerAffiliations], + ownerAffiliations: ownerAffiliations, }; let res = await retryer(fetcher, variables); if (res.data.errors) return res; @@ -202,14 +204,7 @@ const fetchStats = async ( contributedTo: 0, rank: { level: "C", score: 0 }, }; - - // Set default value for ownerAffiliations. - // NOTE: Done here since parseArray() will always return an empty array even nothing - //was specified. - ownerAffiliations = - ownerAffiliations && ownerAffiliations.length > 0 - ? ownerAffiliations - : ["OWNER"]; + ownerAffiliations = parseOwnerAffiliations(ownerAffiliations); let res = await statsFetcher(username, ownerAffiliations); diff --git a/src/fetchers/top-languages-fetcher.js b/src/fetchers/top-languages-fetcher.js index 54f86179..6649113a 100644 --- a/src/fetchers/top-languages-fetcher.js +++ b/src/fetchers/top-languages-fetcher.js @@ -6,6 +6,7 @@ import { MissingParamError, request, wrapTextMultiline, + parseOwnerAffiliations, } from "../common/utils.js"; /** @@ -61,14 +62,7 @@ const fetchTopLanguages = async ( ownerAffiliations = [], ) => { if (!username) throw new MissingParamError(["username"]); - - // Set default value for ownerAffiliations. - // NOTE: Done here since parseArray() will always return an empty array even nothing - //was specified. - ownerAffiliations = - ownerAffiliations && ownerAffiliations.length > 0 - ? ownerAffiliations - : ["OWNER"]; + ownerAffiliations = parseOwnerAffiliations(ownerAffiliations); const res = await retryer(fetcher, { login: username, ownerAffiliations });