diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 830fa7a1..d1b820f4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ Ideas not as issues yet: ### Testing serverless locally -`yarn add serverless-dotenv-plugin` +`yarn add serverless-dotenv-plugin --dev` in `serverless.yml` plugins add: `- serverless-dotenv-plugin` diff --git a/package.json b/package.json index 550f2f7f..81f4657b 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,6 @@ "test-ci": "jest --reporters ./node_modules/jest-junit --collectCoverage" }, "dependencies": { - "@probot/serverless-lambda": "^0.2.0", "all-contributors-cli": "^5.10.1", "compromise": "^11.13.0", "probot": "^8.0.0-octokit-16-preview" diff --git a/serverless.yml b/serverless.yml index 77735ff0..9c45eb89 100644 --- a/serverless.yml +++ b/serverless.yml @@ -37,9 +37,16 @@ provider: functions: githubWebhook: - handler: src/handler.probot + handler: src/serverless-webhook.handler events: - http: path: / method: post cors: true + stats: + handler: src/serverless-stats.handler + events: + - http: + path: /stats + method: get + cors: true diff --git a/src/handler.js b/src/handler.js deleted file mode 100644 index b878df50..00000000 --- a/src/handler.js +++ /dev/null @@ -1,5 +0,0 @@ -// TODO: put back on release of: https://github.com/probot/serverless-lambda/pull/13/files -// const { serverless } = require('@probot/serverless-lambda') -const { serverless } = require('./serverless-lambda') -const appFn = require('./') -module.exports.probot = serverless(appFn) diff --git a/src/probot/getProbot.js b/src/probot/getProbot.js new file mode 100644 index 00000000..1c69c06b --- /dev/null +++ b/src/probot/getProbot.js @@ -0,0 +1,22 @@ +const { createProbot } = require('probot') +const { findPrivateKey } = require('probot/lib/private-key') + +let probot + +function getProbot() { + probot = + probot || + createProbot({ + id: process.env.APP_ID, + secret: process.env.WEBHOOK_SECRET, + cert: findPrivateKey(), + }) + + if (process.env.SENTRY_DSN) { + probot.load(require('probot/lib/apps/sentry')) + } + + return probot +} + +module.exports = getProbot diff --git a/src/serverless-lambda.js b/src/serverless-lambda.js deleted file mode 100644 index cea8c15f..00000000 --- a/src/serverless-lambda.js +++ /dev/null @@ -1,84 +0,0 @@ -// TODO: get off of fork once https://github.com/probot/serverless-lambda/pull/13/files is released - -const { createProbot } = require('probot') -const { resolve } = require('probot/lib/resolver') -const { findPrivateKey } = require('probot/lib/private-key') - -let probot - -const loadProbot = appFn => { - probot = - probot || - createProbot({ - id: process.env.APP_ID, - secret: process.env.WEBHOOK_SECRET, - cert: findPrivateKey(), - }) - - if (typeof appFn === 'string') { - appFn = resolve(appFn) - } - - if (process.env.SENTRY_DSN) { - probot.load(require('probot/lib/apps/sentry')) - } - probot.load(appFn) - - return probot -} - -module.exports.serverless = appFn => { - return async (event, context) => { - // Otherwise let's listen handle the payload - probot = probot || loadProbot(appFn) - - // Ends function immediately after callback - context.callbackWaitsForEmptyEventLoop = false - - // Determine incoming webhook event type - const e = - event.headers['x-github-event'] || event.headers['X-GitHub-Event'] - - // Convert the payload to an Object if API Gateway stringifies it - event.body = - typeof event.body === 'string' ? JSON.parse(event.body) : event.body - - const { info: logInfo, error: logError } = probot.apps[0].log - - // Do the thing - logInfo( - `Received event ${e}${ - event.body.action ? `.${event.body.action}` : '' - }`, - ) - if (event) { - try { - await probot.receive({ - name: e, - payload: event.body, - }) - const res = { - statusCode: 200, - body: JSON.stringify({ - message: `Received ${e}.${event.body.action}`, - }), - } - return context.done(null, res) - } catch (err) { - logError(err) - return context.done(null, { - statusCode: 500, - body: JSON.stringify(err), - }) - } - } else { - logError({ event, context }) - context.done(null, 'unknown error') - } - logInfo('Nothing to do.') - return context.done(null, { - statusCode: 200, - body: 'Nothing to do.', - }) - } -} diff --git a/src/serverless-stats.js b/src/serverless-stats.js new file mode 100644 index 00000000..b35627ff --- /dev/null +++ b/src/serverless-stats.js @@ -0,0 +1,86 @@ +const getProbot = require('./probot/getProbot') + +async function getInstallations(app) { + const github = await app.auth() + + return github.paginate( + github.apps.listInstallations.endpoint.merge({ + per_page: 100, + headers: { + accept: 'application/vnd.github.machine-man-preview+json', + }, + }), + response => { + return response.data + }, + ) +} + +async function popularInstallations({ app, installations }) { + let popular = await Promise.all( + installations.map(async installation => { + const { account } = installation + + const github = await app.auth(installation.id) + + const repositories = await github.paginate( + github.apps.listRepos.endpoint.merge({ + per_page: 100, + headers: { + accept: + 'application/vnd.github.machine-man-preview+json', + }, + }), + response => { + return response.data.repositories.filter( + repository => !repository.private, + ) + }, + ) + + account.stars = repositories.reduce((stars, repository) => { + return stars + repository.stargazers_count + }, 0) + + return account + }), + ) + + popular = popular.filter(installation => installation.stars > 0) + return popular.sort((a, b) => b.stars - a.stars).slice(0, 10) +} + +async function getStats(probot) { + const app = probot.apps[0] + + if (!app) { + throw new Error( + 'Stats needs an app, we usually depend on sentry being loaded. You can load sentry by setting the SENTRY_DSN env variable)', + ) + } + + const installations = await getInstallations(app) + const popular = await popularInstallations({ app, installations }) + return { + installations: installations.length, + popular, + } +} + +module.exports.handler = async (event, context) => { + context.callbackWaitsForEmptyEventLoop = false + + try { + const probot = getProbot() + const stats = await getStats(probot) + return { + statusCode: 200, + body: JSON.stringify(stats), + } + } catch (error) { + return { + statusCode: 500, + body: JSON.stringify(error.message), + } + } +} diff --git a/src/serverless-webhook.js b/src/serverless-webhook.js new file mode 100644 index 00000000..3a59e7b8 --- /dev/null +++ b/src/serverless-webhook.js @@ -0,0 +1,31 @@ +const getProbot = require('./probot/getProbot') +const appFn = require('./') + +module.exports.handler = async (event, context) => { + context.callbackWaitsForEmptyEventLoop = false + + try { + const probot = getProbot() + probot.load(appFn) + + const name = + event.headers['x-github-event'] || event.headers['X-GitHub-Event'] + const payload = + typeof event.body === 'string' ? JSON.parse(event.body) : event.body + await probot.receive({ + name, + payload, + }) + return { + statusCode: 200, + body: JSON.stringify({ + message: `Received ${name}.${payload.action}`, + }), + } + } catch (error) { + return { + statusCode: 500, + body: JSON.stringify(error.message), + } + } +} diff --git a/yarn.lock b/yarn.lock index ba3cfd2f..0e3b5f5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -181,10 +181,6 @@ buffer-equal-constant-time "^1.0.1" debug "^4.0.0" -"@probot/serverless-lambda@^0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@probot/serverless-lambda/-/serverless-lambda-0.2.0.tgz#09ec3c5b77486d8e5e406337b8d46a38418d1019" - "@serverless/platform-sdk@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@serverless/platform-sdk/-/platform-sdk-0.3.0.tgz#335adeb2759760e4232c321d72b4022e4dce7818"