Files
shields/services_github_github-total-star.service.js.html
2020-12-25 18:49:41 +00:00

298 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: services/github/github-total-star.service.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: services/github/github-total-star.service.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>'use strict'
const Joi = require('joi')
const gql = require('graphql-tag')
const { nonNegativeInteger } = require('../validators')
const { metric } = require('../text-formatters')
const { GithubAuthV4Service } = require('./github-auth-service')
const {
documentation: commonDocumentation,
transformErrors,
} = require('./github-helpers')
const MAX_REPO_LIMIT = 200
const customDocumentation = `This badge takes into account up to &lt;code>${MAX_REPO_LIMIT}&lt;/code> of the most starred repositories of given user / org.`
const userDocumentation = `${commonDocumentation}
&lt;p>
&lt;b>Note:&lt;/b>&lt;br>
1. ${customDocumentation}&lt;br>
2. &lt;code>affiliations&lt;/code> query param accepts three values (must be UPPER case) &lt;code>OWNER&lt;/code>, &lt;code>COLLABORATOR&lt;/code>, &lt;code>ORGANIZATION_MEMBER&lt;/code>.
One can pass comma separated combinations of these values (no spaces) e.g. &lt;code>OWNER,COLLABORATOR&lt;/code> or &lt;code>OWNER,COLLABORATOR,ORGANIZATION_MEMBER&lt;/code>.
Default value is &lt;code>OWNER&lt;/code>. See the explanation of these values &lt;a href="https://docs.github.com/en/graphql/reference/enums#repositoryaffiliation">here&lt;/a>.
&lt;/p>
`
const orgDocumentation = `${commonDocumentation}
&lt;p>
&lt;b>Note:&lt;/b> ${customDocumentation}
&lt;/p>`
const pageInfoSchema = Joi.object({
hasNextPage: Joi.boolean().required(),
endCursor: Joi.string().allow(null).required(),
}).required()
const nodesSchema = Joi.array()
.items(
Joi.object({
stargazers: Joi.object({
totalCount: nonNegativeInteger,
}).required(),
})
)
.default([])
const repositoriesSchema = Joi.object({
pageInfo: pageInfoSchema,
nodes: nodesSchema,
}).required()
const schema = Joi.object({
data: Joi.alternatives(
Joi.object({
user: Joi.object({
repositories: repositoriesSchema,
}).required(),
}).required(),
Joi.object({
organization: Joi.object({
repositories: repositoriesSchema,
}).required(),
}).required()
).required(),
}).required()
const query = gql`
query fetchStars(
$user: String!
$nextCursor: String
$affiliations: [RepositoryAffiliation]!
) {
user(login: $user) {
repositories(
first: 100
after: $nextCursor
ownerAffiliations: $affiliations
orderBy: { field: STARGAZERS, direction: DESC }
) {
pageInfo {
hasNextPage
endCursor
}
nodes {
stargazers {
totalCount
}
}
}
}
organization(login: $user) {
repositories(
first: 100
after: $nextCursor
orderBy: { field: STARGAZERS, direction: DESC }
) {
pageInfo {
hasNextPage
endCursor
}
nodes {
stargazers {
totalCount
}
}
}
}
}
`
const affiliationsAllowedValues = [
'OWNER',
`COLLABORATOR`,
'ORGANIZATION_MEMBER',
]
/**
* Validates affiliations should contain combination of allowed values in any order.
*
* @param {string} value affiliation current value
* @param {*} helpers object to construct custom error
*
* @returns {string} valiadtion error or value unchanged
*/
const validateAffiliations = (value, helpers) => {
const values = value.split(',')
if (values.some(e => !affiliationsAllowedValues.includes(e))) {
return helpers.error('any.invalid')
}
return value
}
const queryParamSchema = Joi.object({
affiliations: Joi.string().default('OWNER').custom(validateAffiliations),
}).required()
module.exports = class GithubTotalStarService extends GithubAuthV4Service {
static defaultLabel = 'stars'
static category = 'social'
static route = {
base: 'github/stars',
pattern: ':user',
queryParamSchema,
}
static examples = [
{
title: "GitHub User's stars",
namedParams: {
user: 'chris48s',
},
queryParams: { affiliations: 'OWNER,COLLABORATOR' },
staticPreview: {
label: this.defaultLabel,
message: 54,
style: 'social',
},
documentation: userDocumentation,
},
{
title: "GitHub Org's stars",
pattern: ':org',
namedParams: {
org: 'badges',
},
staticPreview: {
label: this.defaultLabel,
message: metric(7000),
style: 'social',
},
documentation: orgDocumentation,
},
]
static defaultBadgeData = {
label: this.defaultLabel,
namedLogo: 'github',
}
static render({ totalStars, user }) {
return {
message: metric(totalStars),
color: 'blue',
link: [`https://github.com/${user}`],
}
}
async fetch({ user, affiliations, nextCursor }) {
const variables = { user, affiliations, nextCursor }
return await this._requestGraphql({
query,
variables,
schema,
transformJson: json =>
json.data.organization || json.data.user ? { data: json.data } : json,
transformErrors: e => transformErrors(e, 'user/org'),
})
}
transform(repos) {
const totalStars = repos
.map(element => element.stargazers.totalCount)
.reduce((accumulator, currentValue) => accumulator + currentValue, 0)
const lastRepo = repos.slice(-1).pop() // undefined when repos is empty
const hasStars = lastRepo ? lastRepo.stargazers.totalCount !== 0 : false
return {
totalStars,
hasStars,
}
}
async getTotalStars({ user }, { affiliations }) {
let grandTotalStars = 0
let fetchedReposCount = 0
let nextCursor = null
let hasNext
do {
const { data } = await this.fetch({
user,
affiliations: affiliations.split(','),
nextCursor,
})
const {
repositories: {
pageInfo: { hasNextPage, endCursor },
nodes: repos,
},
} = data.user || data.organization
const { totalStars, hasStars } = this.transform(repos)
// repos are sorted based on the stars. If last repo has zero star,
// no need to fire additional fetch call, as repos on next page will have zero stars only.
hasNext = hasNextPage &amp;&amp; hasStars
nextCursor = endCursor
grandTotalStars += totalStars
fetchedReposCount += repos.length
} while (hasNext &amp;&amp; fetchedReposCount &lt; MAX_REPO_LIMIT)
return grandTotalStars
}
async handle({ user }, queryParams) {
const totalStars = await this.getTotalStars({ user }, queryParams)
return this.constructor.render({ totalStars, user })
}
}
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-badge-maker.html">badge-maker</a></li><li><a href="module-core_base-service_base.html">core/base-service/base</a></li><li><a href="module-core_base-service_base-graphql.html">core/base-service/base-graphql</a></li><li><a href="module-core_base-service_base-json.html">core/base-service/base-json</a></li><li><a href="module-core_base-service_base-svg-scraping.html">core/base-service/base-svg-scraping</a></li><li><a href="module-core_base-service_base-xml.html">core/base-service/base-xml</a></li><li><a href="module-core_base-service_base-yaml.html">core/base-service/base-yaml</a></li><li><a href="module-core_base-service_errors.html">core/base-service/errors</a></li><li><a href="module-core_base-service_graphql.html">core/base-service/graphql</a></li><li><a href="module-core_server_server.html">core/server/server</a></li><li><a href="module-core_service-test-runner_create-service-tester.html">core/service-test-runner/create-service-tester</a></li><li><a href="module-core_service-test-runner_icedfrisby-shields.html">core/service-test-runner/icedfrisby-shields</a></li><li><a href="module-core_service-test-runner_infer-pull-request.html">core/service-test-runner/infer-pull-request</a></li><li><a href="module-core_service-test-runner_runner.html">core/service-test-runner/runner</a></li><li><a href="module-core_service-test-runner_service-tester.html">core/service-test-runner/service-tester</a></li><li><a href="module-core_service-test-runner_services-for-title.html">core/service-test-runner/services-for-title</a></li><li><a href="module-core_token-pooling_token-pool.html">core/token-pooling/token-pool</a></li><li><a href="module-services_dynamic_json-path.html">services/dynamic/json-path</a></li><li><a href="module-services_steam_steam-base.html">services/steam/steam-base</a></li></ul><h3>Classes</h3><ul><li><a href="module.exports.html">exports</a></li><li><a href="module-core_base-service_base-graphql-BaseGraphqlService.html">BaseGraphqlService</a></li><li><a href="module-core_base-service_base-json-BaseJsonService.html">BaseJsonService</a></li><li><a href="module-core_base-service_base-svg-scraping-BaseSvgScrapingService.html">BaseSvgScrapingService</a></li><li><a href="module-core_base-service_base-xml-BaseXmlService.html">BaseXmlService</a></li><li><a href="module-core_base-service_base-yaml-BaseYamlService.html">BaseYamlService</a></li><li><a href="module-core_base-service_base-BaseService.html">BaseService</a></li><li><a href="module-core_base-service_errors-Deprecated.html">Deprecated</a></li><li><a href="module-core_base-service_errors-ImproperlyConfigured.html">ImproperlyConfigured</a></li><li><a href="module-core_base-service_errors-Inaccessible.html">Inaccessible</a></li><li><a href="module-core_base-service_errors-InvalidParameter.html">InvalidParameter</a></li><li><a href="module-core_base-service_errors-InvalidResponse.html">InvalidResponse</a></li><li><a href="module-core_base-service_errors-NotFound.html">NotFound</a></li><li><a href="module-core_base-service_errors-ShieldsRuntimeError.html">ShieldsRuntimeError</a></li><li><a href="module-core_server_server-Server.html">Server</a></li><li><a href="module-core_service-test-runner_runner-Runner.html">Runner</a></li><li><a href="module-core_service-test-runner_service-tester-ServiceTester.html">ServiceTester</a></li><li><a href="module-core_token-pooling_token-pool-Token.html">Token</a></li><li><a href="module-core_token-pooling_token-pool-TokenPool.html">TokenPool</a></li><li><a href="module-services_steam_steam-base-BaseSteamAPI.html">BaseSteamAPI</a></li></ul><h3>Tutorials</h3><ul><li><a href="tutorial-TUTORIAL.html">TUTORIAL</a></li><li><a href="tutorial-badge-urls.html">badge-urls</a></li><li><a href="tutorial-code-walkthrough.html">code-walkthrough</a></li><li><a href="tutorial-deprecating-badges.html">deprecating-badges</a></li><li><a href="tutorial-json-format.html">json-format</a></li><li><a href="tutorial-logos.html">logos</a></li><li><a href="tutorial-performance-testing.html">performance-testing</a></li><li><a href="tutorial-production-hosting.html">production-hosting</a></li><li><a href="tutorial-rewriting-services.html">rewriting-services</a></li><li><a href="tutorial-self-hosting.html">self-hosting</a></li><li><a href="tutorial-server-secrets.html">server-secrets</a></li><li><a href="tutorial-service-tests.html">service-tests</a></li><li><a href="tutorial-users.html">users</a></li></ul><h3>Global</h3><ul><li><a href="global.html#validateAffiliations">validateAffiliations</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Fri Dec 25 2020 18:49:22 GMT+0000 (Coordinated Universal Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>