42 Commits

Author SHA1 Message Date
157d2dee49 3.4.0 2019-08-31 13:42:50 +02:00
590ca961a6 Merge pull request #45 from 67P/docs/github-signup
Add GitHub signup oracle documentation
2019-08-31 13:26:23 +02:00
6476fe5c88 Improve signup docs 2019-08-31 13:23:11 +02:00
101e35622a Add GitHub signup oracle documentation 2019-08-31 12:52:36 +02:00
afa67e5ac7 Prefix all configuration environment variables
The convention here is to prefix all Kredit related configuration
variables with `KREDITS_`
2019-08-31 12:51:44 +02:00
cbbc6c359e Merge pull request #41 from 67P/feature/github-signup
Add GitHub signup oracle
2019-08-28 15:56:54 +02:00
c16ea51769 Error handling for fetching Github access token 2019-08-27 18:35:40 +02:00
fc49213b01 Update contracts min version 2019-08-27 18:23:22 +02:00
32dc6a7358 GitHub signup fixes
* Don't fail when profile has no name set
* Use scope that can only read public profile information (no scope)
* Better error handling
2019-08-27 18:22:36 +02:00
53dee63a76 Add env variable for session secret 2019-08-27 15:30:30 +02:00
97ec6ce9bc Add KREDITS_DAO_ADDRESS to readme 2019-08-27 15:22:38 +02:00
342a5cd829 Add gasLimit option when adding contributor 2019-08-27 13:16:43 +02:00
45064df737 Fix wrong logger function call 2019-08-27 12:57:36 +02:00
d9a9ad7793 Update deps
Fixes security vulns
2019-08-27 12:51:31 +02:00
d65e92e1fe Merge branch 'master' into feature/github-signup 2019-08-27 12:39:15 +02:00
0fff3b1db0 Merge pull request #43 from 67P/feature/custom_dao_address
Add ENV var for custom DAO address
2019-08-27 12:38:45 +02:00
1f1053e289 Remove apm address from options
It's already the default in the contracts module.
2019-08-27 12:37:50 +02:00
84f20efff0 Add ENV var for custom DAO address
This makes it much easier to test hubot-kredits with local development
chains.
2019-08-27 12:19:01 +02:00
cd8343516f 3.3.1 2019-08-14 15:42:23 +02:00
Greg Karékinian
41c5903cf5 Merge pull request #42 from 67P/chore/mediawiki_small_threshold
Change threshold for small wiki contributions
2019-08-14 12:51:48 +02:00
9a4dc43aa5 Change threshold for small wiki contributions
Turns out 280 is a bit low. Let's try 500 instead.
2019-08-14 12:46:19 +02:00
082bf4848d Minor changes 2019-07-26 20:50:36 +02:00
41dd813eaf Replace custom CORS header solution with middleware 2019-07-26 20:48:20 +02:00
b35cbcc63a Finish implementation of GitHub signup oracle 2019-07-26 04:15:11 +02:00
137e9eb4ed Add initial implementation of the GitHub signup oracle 2019-07-11 15:44:58 +02:00
67fd9ff031 3.3.0 2019-05-08 16:45:55 +02:00
800950ebc5 Merge pull request #39 from 67P/feature/38-label_mappings
Map issue/PR labels to contribution kind
2019-05-08 16:42:05 +02:00
98ccae96f0 [gitea] Use labels for contribution kind 2019-05-08 16:16:40 +02:00
e6730802f6 Use proper syntax for sending HTTP status
Fixes deprecation warnings for the old syntax.
2019-05-08 16:14:41 +02:00
d2976b312e Create utility modules for label processing 2019-05-08 16:14:41 +02:00
d2feee8b14 [github] Use labels for contribution kind 2019-05-08 16:14:24 +02:00
a5acf466a4 3.2.1 2019-04-30 10:41:50 +01:00
Greg Karékinian
f1bb5b391d Merge pull request #35 from 67P/bugfix/33-gitea_issue_id
Fix wrong Gitea issue IDs/URLs
2019-04-30 10:26:21 +02:00
840fcf9d25 Add commented setting for custom local DAO address 2019-04-28 13:01:33 +01:00
c390fc9ce7 Fix Gitea issue ID/URL being wrong
Internal issue IDs are different from the public ones in Gitea. The one
used in URLs etc. is called "number" instead of "id" in hook payloads.

fixes #33
2019-04-28 13:01:09 +01:00
23033b3813 3.2.0 2019-04-24 14:25:51 +01:00
fb1b10d2a3 Merge pull request #31 from 67P/feature/24-gitea
Add Gitea integration
2019-04-24 14:25:06 +01:00
d3b97d9129 Update kredits-contracts
Includes the Gitea accounts in profiles
2019-04-24 14:24:21 +01:00
d2f487b379 Merge pull request #32 from 67P/chore/remove_network_id_config
Remove network ID setting
2019-04-24 13:06:39 +00:00
ca9732c333 Add Gitea info to README 2019-04-24 13:38:16 +01:00
c03da5659f Remove network ID setting
Not needed anymore.
2019-04-24 13:34:00 +01:00
5a9edda1cd Add Gitea integration
Support for Gitea organization hooks, based on GitHub hooks code.

closes #24
2019-04-24 13:27:18 +01:00
9 changed files with 1250 additions and 303 deletions

View File

@@ -10,9 +10,29 @@ contributions.
## Setup ## Setup
### Ethereum wallet
You will need an Ethereum wallet for your bot, so it can interact with the You will need an Ethereum wallet for your bot, so it can interact with the
Ethereum smart contracts. `npm run create-wallet` will do the job for you. Ethereum smart contracts. `npm run create-wallet` will do the job for you.
The wallet must be funded with enough ETH to interact with the contracts.
### Contract permissions
The bot wallet needs the following Aragon contract permissions to interact
with [kredits-contracts]:
1. `ADD_CONTRIBUTION_ROLE` on the `Contribution` contract
2. `MANAGE_CONTRIBUTORS_ROLE` on the `Contributor` contract
These permissions can be configured using the [Aragon
CLI](https://hack.aragon.org/docs/cli-intro.html) (see [kredits-contracts].
aragon dao acl grant [DAO address] [contribution app address] ADD_CONTRIBUTION_ROLE [bot wallet address]
aragon dao acl grant [DAO address] [contributor app address] MANAGE_CONTRIBUTORS_ROLE [bot wallet address]
To get the `Contribution` and `Contributor` app addresses use `aragon dao apps`.
## Configuration ## Configuration
As usual in Hubot, you can add all config as environment variables. As usual in Hubot, you can add all config as environment variables.
@@ -24,7 +44,11 @@ As usual in Hubot, you can add all config as environment variables.
| `KREDITS_WALLET_PATH` | Path to an Etherum wallet JSON file (default: `./wallet.json`) | | `KREDITS_WALLET_PATH` | Path to an Etherum wallet JSON file (default: `./wallet.json`) |
| `KREDITS_WALLET_PASSWORD` | Wallet password | | `KREDITS_WALLET_PASSWORD` | Wallet password |
| `KREDITS_PROVIDER_URL` | Ethereum JSON-RPC URL (default: `http://localhost:7545`) | | `KREDITS_PROVIDER_URL` | Ethereum JSON-RPC URL (default: `http://localhost:7545`) |
| `KREDITS_NETWORK_ID` | The ethereum network ID to use (default: 100 = local) | | `KREDITS_WEB_URL` | URL of the Kredits Web app (default: `https://kredits.kosmos.org`) |
| `KREDITS_DAO_ADDRESS` | DAO Kernel address |
| `KREDITS_SESSION_SECRET` | Secret used by [grant](https://www.npmjs.com/package/grant) to sign the session ID |
| `KREDITS_GRANT_HOST` | Host used by [grant](https://www.npmjs.com/package/grant) to generate OAuth redirect URLs (default: `localhost:8888`) |
| `KREDITS_GRANT_PROTOCOL` | Protocol (http or https) used by [grant](https://www.npmjs.com/package/grant") to generate the OAuth redirect URLs (default: "http") |
## Integrations ## Integrations
@@ -35,6 +59,12 @@ which carry a kredits label: `kredits-1`, `kredits-2`, `kredits-3` for small,
medium and large contributions. If there are multiple people assigned, it will medium and large contributions. If there are multiple people assigned, it will
issue contribution tokens for all of them. issue contribution tokens for all of them.
If `KREDITS_GITHUB_KEY` and `KREDITS_GITHUB_SECRET` are set, the bot will also
expose OAuth endpoints to authenticate new contributors and register new
contributor profiles on the smart contract. For this feature, a [GitHub OAuth
app] is required and the [OAuth grant config variables](#Configuration) must be
set.
#### Setup #### Setup
Point a GitHub organization webhook to the following URL: Point a GitHub organization webhook to the following URL:
@@ -46,6 +76,27 @@ Point a GitHub organization webhook to the following URL:
| Key | Description | | Key | Description |
| --- | --- | | --- | --- |
| `KREDITS_GITHUB_REPO_BLACKLIST` | Repos which you do not want to issue kredits for. Format: `orgname/reponame`, e.g. `67P/test-one-two` | | `KREDITS_GITHUB_REPO_BLACKLIST` | Repos which you do not want to issue kredits for. Format: `orgname/reponame`, e.g. `67P/test-one-two` |
| `KREDITS_GITHUB_KEY` | Key of the [GitHub OAuth app] used to authenticate contributors |
| `KREDITS_GITHUB_SECRET` | Secret of the [GitHub OAuth app] used to authenticate contributors |
### Gitea
The Gitea integration will watch for closed issues and merged pull requests,
which carry a kredits label: `kredits-1`, `kredits-2`, `kredits-3` for small,
medium and large contributions. If there are multiple people assigned, it will
issue contribution tokens for all of them.
#### Setup
Point a Gitea organization webhook to the following URL:
https://your-hubot.example.com/incoming/kredits/gitea/{webhook_token}
#### Config
| Key | Description |
| --- | --- |
| `KREDITS_GITEA_REPO_BLACKLIST` | Repos which you do not want to issue kredits for. Format: `orgname/reponame`, e.g. `kosmos/test-one-two` |
### MediaWiki ### MediaWiki
@@ -62,3 +113,6 @@ wiki's API on its own.
| Key | Description | | Key | Description |
| --- | --- | | --- | --- |
| `KREDITS_MEDIAWIKI_URL` | Your wiki URL, e.g. `https://wiki.kosmos.org/` | | `KREDITS_MEDIAWIKI_URL` | Your wiki URL, e.g. `https://wiki.kosmos.org/` |
[kredits-contracts]: https://github.com/67P/kredits-contracts
[GitHub OAuth app]: https://developer.github.com/apps/about-apps/#about-oauth-apps

View File

@@ -7,7 +7,7 @@ const Kredits = require('kredits-contracts');
const walletPath = process.env.KREDITS_WALLET_PATH || './wallet.json'; const walletPath = process.env.KREDITS_WALLET_PATH || './wallet.json';
const walletJson = fs.readFileSync(walletPath); const walletJson = fs.readFileSync(walletPath);
const providerUrl = process.env.KREDITS_PROVIDER_URL; const providerUrl = process.env.KREDITS_PROVIDER_URL;
const networkId = parseInt(process.env.KREDITS_NETWORK_ID || 100); const daoAddress = process.env.KREDITS_DAO_ADDRESS;
const ipfsConfig = { const ipfsConfig = {
host: process.env.IPFS_API_HOST || 'localhost', host: process.env.IPFS_API_HOST || 'localhost',
@@ -49,13 +49,14 @@ module.exports = async function(robot) {
// Kredits contracts setup // Kredits contracts setup
// //
const opts = { ipfsConfig };
if (daoAddress) {
opts.addresses = { Kernel: daoAddress };
}
let kredits; let kredits;
try { try {
kredits = await new Kredits(signer.provider, signer, { kredits = await new Kredits(signer.provider, signer, opts).init();
// TODO support local devchain custom address
apm: 'open.aragonpm.eth',
ipfsConfig
}).init();
} catch(error) { } catch(error) {
robot.logger.warning('[hubot-kredits] Could not set up kredits:', error); robot.logger.warning('[hubot-kredits] Could not set up kredits:', error);
process.exit(1); process.exit(1);
@@ -149,6 +150,7 @@ module.exports = async function(robot) {
// //
require('./integrations/github')(robot, kredits); require('./integrations/github')(robot, kredits);
require('./integrations/gitea')(robot, kredits);
if (typeof process.env.KREDITS_MEDIAWIKI_URL !== 'undefined') { if (typeof process.env.KREDITS_MEDIAWIKI_URL !== 'undefined') {
require('./integrations/mediawiki')(robot, kredits); require('./integrations/mediawiki')(robot, kredits);

166
integrations/gitea.js Normal file
View File

@@ -0,0 +1,166 @@
const util = require('util');
const fetch = require('node-fetch');
const amountFromLabels = require('./utils/amount-from-labels');
const kindFromLabels = require('./utils/kind-from-labels');
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
module.exports = async function(robot, kredits) {
function messageRoom(message) {
robot.messageRoom(process.env.KREDITS_ROOM, message);
}
robot.logger.debug('[hubot-kredits] Loading Gitea integration...');
let repoBlackList = [];
if (process.env.KREDITS_GITEA_REPO_BLACKLIST) {
repoBlackList = process.env.KREDITS_GITEA_REPO_BLACKLIST.split(',');
robot.logger.debug('[hubot-kredits] Ignoring Gitea actions from ', util.inspect(repoBlackList));
}
const Contributor = kredits.Contributor;
const Contribution = kredits.Contribution;
function getContributorByGiteaUser(username) {
return Contributor.all().then(contributors => {
const contrib = contributors.find(c => {
return c.gitea_username === username;
});
if (!contrib) {
throw new Error(`No contributor found for ${username}`);
} else {
return contrib;
}
});
}
function createContribution(giteaUser, date, time, amount, kind, description, url, details) {
return getContributorByGiteaUser(giteaUser).then(contributor => {
robot.logger.debug(`[hubot-kredits] Creating contribution token for ${amount}₭S to ${giteaUser} for ${url}...`);
const contributionAttr = {
contributorId: contributor.id,
contributorIpfsHash: contributor.ipfsHash,
date,
time,
amount,
kind,
description,
url,
details
};
robot.logger.debug(`[hubot-kredits] contribution attributes:`);
robot.logger.debug(util.inspect(contributionAttr, { depth: 1, colors: true }));
return Contribution.addContribution(contributionAttr).catch(error => {
robot.logger.error(`[hubot-kredits] Error:`, error);
messageRoom(`I tried to add a contribution for ${giteaUser} for ${url}, but I encountered an error when submitting the tx:`);
messageRoom(error.message);
});
});
}
async function handleGiteaIssueClosed(data) {
const issue = data.issue;
const repoName = data.repository.full_name;
const web_url = `${data.repository.html_url}/issues/${issue.number}`;
const description = `${repoName}: ${issue.title}`;
const labels = issue.labels.map(l => l.name);
const amount = amountFromLabels(labels);
const kind = kindFromLabels(labels);
const assignees = issue.assignees ? issue.assignees.map(a => a.login) : [];
[ date, time ] = issue.closed_at.split('T');
if (amount === 0) {
robot.logger.info('[hubot-kredits] Kredits amount from issue label is zero; ignoring');
return Promise.resolve();
} else if (repoBlackList.includes(repoName)) {
robot.logger.debug(`[hubot-kredits] ${repoName} is on black list; ignoring`);
return Promise.resolve();
}
let recipients;
if (assignees.length > 0) {
recipients = assignees;
} else {
recipients = [issue.user.login];
}
for (const recipient of recipients) {
try {
await createContribution(recipient, date, time, amount,
kind, description, web_url,
{ issue, repository: data.repository });
await sleep(60000);
}
catch (err) { robot.logger.error(err); }
}
return Promise.resolve();
}
async function handleGiteaPullRequestClosed(data) {
const pull_request = data.pull_request;
const repoName = data.repository.full_name;
const web_url = pull_request.html_url;
const description = `${repoName}: ${pull_request.title}`;
const labels = pull_request.labels.map(l => l.name);
const amount = amountFromLabels(labels);
const kind = kindFromLabels(labels);
const assignees = pull_request.assignees ? pull_request.assignees.map(a => a.login) : [];
[ date, time ] = pull_request.merged_at.split('T');
if (amount === 0) {
robot.logger.info('[hubot-kredits] Kredits amount from issue label is zero; ignoring');
return Promise.resolve();
} else if (repoBlackList.includes(repoName)) {
robot.logger.debug(`[hubot-kredits] ${repoName} is on black list; ignoring`);
return Promise.resolve();
}
let recipients;
if (assignees.length > 0) {
recipients = assignees;
} else {
recipients = [pull_request.user.login];
}
for (const recipient of recipients) {
try {
await createContribution(recipient, date, time, amount,
kind, description, web_url,
{ pull_request, repository: data.repository });
await sleep(60000);
}
catch (err) { robot.logger.error(err); }
}
return Promise.resolve();
}
robot.router.post('/incoming/kredits/gitea/'+process.env.KREDITS_WEBHOOK_TOKEN, (req, res) => {
const evt = req.header('X-Gitea-Event');
let data = req.body;
// For some reason data is contained in a payload property on one
// machine, but directly in the root of the object on others
if (data.payload) { data = JSON.parse(data.payload); }
robot.logger.info(`Received Gitea hook. Event: ${evt}, action: ${data.action}`);
if (evt === 'pull_request' && data.action === 'closed' && data.pull_request.merged) {
handleGiteaPullRequestClosed(data);
res.sendStatus(200);
}
else if (evt === 'issues' && data.action === 'closed') {
handleGiteaIssueClosed(data);
res.sendStatus(200);
} else {
res.sendStatus(200);
}
});
};

View File

@@ -1,5 +1,10 @@
const util = require('util'); const util = require('util');
const fetch = require('node-fetch'); const fetch = require('node-fetch');
const session = require('express-session');
const grant = require('grant-express');
const cors = require('cors');
const amountFromLabels = require('./utils/amount-from-labels');
const kindFromLabels = require('./utils/kind-from-labels');
function sleep(ms) { function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms)); return new Promise(resolve => setTimeout(resolve, ms));
@@ -19,6 +24,8 @@ module.exports = async function(robot, kredits) {
robot.logger.debug('[hubot-kredits] Ignoring GitHub actions from ', util.inspect(repoBlackList)); robot.logger.debug('[hubot-kredits] Ignoring GitHub actions from ', util.inspect(repoBlackList));
} }
const kreditsWebUrl = process.env.KREDITS_WEB_URL || 'https://kredits.kosmos.org';
const Contributor = kredits.Contributor; const Contributor = kredits.Contributor;
const Contribution = kredits.Contribution; const Contribution = kredits.Contribution;
@@ -35,9 +42,9 @@ module.exports = async function(robot, kredits) {
}); });
} }
function createContribution(githubUser, date, time, amount, description, url, details) { function createContribution(githubUser, date, time, amount, kind, description, url, details) {
return getContributorByGithubUser(githubUser).then(contributor => { return getContributorByGithubUser(githubUser).then(contributor => {
robot.logger.debug(`[hubot-kredits] Creating contribution token for ${amount}₭S to ${githubUser} for ${url}...`); robot.logger.info(`[hubot-kredits] Creating contribution token for ${amount}₭S to ${githubUser} for ${url}...`);
const contributionAttr = { const contributionAttr = {
contributorId: contributor.id, contributorId: contributor.id,
@@ -45,12 +52,15 @@ module.exports = async function(robot, kredits) {
date, date,
time, time,
amount, amount,
url, kind,
description, description,
details, url,
kind: 'dev' details
}; };
robot.logger.debug(`[hubot-kredits] contribution attributes:`);
robot.logger.debug(util.inspect(contributionAttr, { depth: 1, colors: true }));
return Contribution.addContribution(contributionAttr).catch(error => { return Contribution.addContribution(contributionAttr).catch(error => {
robot.logger.error(`[hubot-kredits] Error:`, error); robot.logger.error(`[hubot-kredits] Error:`, error);
messageRoom(`I tried to add a contribution for ${githubUser} for ${url}, but I encountered an error when submitting the tx:`); messageRoom(`I tried to add a contribution for ${githubUser} for ${url}, but I encountered an error when submitting the tx:`);
@@ -59,38 +69,17 @@ module.exports = async function(robot, kredits) {
}); });
} }
function amountFromIssueLabels(issue) {
const kreditsLabel = issue.labels.map(l => l.name)
.filter(n => n.match(/^kredits/))[0];
// No label, no kredits
if (typeof kreditsLabel === 'undefined') { return 0; }
// TODO move to config maybe?
let amount;
switch(kreditsLabel) {
case 'kredits-1':
amount = 500;
break;
case 'kredits-2':
amount = 1500;
break;
case 'kredits-3':
amount = 5000;
break;
}
return amount;
}
async function handleGitHubIssueClosed(data) { async function handleGitHubIssueClosed(data) {
let recipients; let recipients;
const issue = data.issue; const issue = data.issue;
const assignees = issue.assignees.map(a => a.login); const assignees = issue.assignees.map(a => a.login);
const web_url = issue.html_url; const web_url = issue.html_url;
[date, time] = issue.closed_at.split('T'); [date, time] = issue.closed_at.split('T');
const amount = amountFromIssueLabels(issue); const labels = issue.labels.map(l => l.name);
const repoName = issue.repository_url.match(/.*\/(.+\/.+)$/)[1]; const amount = amountFromLabels(labels);
const kind = kindFromLabels(labels);
const repoName = issue.repository_url.match(/.*\/(.+\/.+)$/)[1];
const description = `${repoName}: ${issue.title}`; const description = `${repoName}: ${issue.title}`;
if (amount === 0) { if (amount === 0) {
@@ -109,7 +98,7 @@ module.exports = async function(robot, kredits) {
for (const recipient of recipients) { for (const recipient of recipients) {
try { try {
await createContribution(recipient, date, time, amount, description, web_url, issue); await createContribution(recipient, date, time, amount, kind, description, web_url, issue);
await sleep(60000); await sleep(60000);
} }
catch (err) { robot.logger.error(err); } catch (err) { robot.logger.error(err); }
@@ -141,8 +130,10 @@ module.exports = async function(robot, kredits) {
return response.json(); return response.json();
}) })
.then(async (issue) => { .then(async (issue) => {
const amount = amountFromIssueLabels(issue); const labels = issue.labels.map(l => l.name);
const repoName = pull_request.base.repo.full_name; const amount = amountFromLabels(labels);
const kind = kindFromLabels(labels);
const repoName = pull_request.base.repo.full_name;
const description = `${repoName}: ${pull_request.title}`; const description = `${repoName}: ${pull_request.title}`;
if (amount === 0) { if (amount === 0) {
@@ -155,7 +146,7 @@ module.exports = async function(robot, kredits) {
for (const recipient of recipients) { for (const recipient of recipients) {
try { try {
await createContribution(recipient, date, time, amount, description, web_url, pull_request); await createContribution(recipient, date, time, amount, kind, description, web_url, pull_request);
await sleep(60000); await sleep(60000);
} }
catch (err) { robot.logger.error(err); } catch (err) { robot.logger.error(err); }
@@ -176,14 +167,105 @@ module.exports = async function(robot, kredits) {
if (evt === 'pull_request' && data.action === 'closed' && data.pull_request.merged) { if (evt === 'pull_request' && data.action === 'closed' && data.pull_request.merged) {
handleGitHubPullRequestClosed(data); handleGitHubPullRequestClosed(data);
res.send(200); res.sendStatus(200);
} }
else if (evt === 'issues' && data.action === 'closed') { else if (evt === 'issues' && data.action === 'closed') {
handleGitHubIssueClosed(data); handleGitHubIssueClosed(data);
res.send(200); res.sendStatus(200);
} else { } else {
res.send(200); res.sendStatus(200);
} }
}); });
//
// GitHub signup
//
if (process.env.KREDITS_GITHUB_KEY && process.env.KREDITS_GITHUB_SECRET) {
const grantConfig = {
defaults: {
protocol: (process.env.KREDITS_GRANT_PROTOCOL || "http"),
host: (process.env.KREDITS_GRANT_HOST || 'localhost:8888'),
transport: 'session',
response: 'tokens',
path: '/kredits/signup'
},
github: {
key: process.env.KREDITS_GITHUB_KEY,
secret: process.env.KREDITS_GITHUB_SECRET,
callback: '/kredits/signup/github'
}
};
robot.router.use(session({ secret: process.env.KREDITS_SESSION_SECRET || 'grant' }));
robot.router.use('/kredits/signup', grant(grantConfig));
robot.router.get('/kredits/signup/github', async (req, res) => {
const access_token = req.session.grant.response.access_token;
res.redirect(`${kreditsWebUrl}/signup/github#access_token=${access_token}`);
});
robot.router.options('/kredits/signup/github', cors());
robot.router.post('/kredits/signup/github', cors(), async (req, res) => {
const accessToken = req.body.accessToken;
if (!accessToken) {
res.status(400).json({});
return;
}
try {
const githubResponse = await fetch('https://api.github.com/user', {
headers: {
'Accept': 'application/vnd.github.v3+json',
'Authorization': `token ${accessToken}`
}
});
} catch (error) {
robot.logger.error('[hubot-kredits] Fetching user data from GitHub failed:', error);
res.status(500).json({ error });
};
if (githubResponse.status >= 300) {
res.status(githubResponse.status).json({});
return;
}
const user = await githubResponse.json();
const contributor = await kredits.Contributor.findByAccount({
site: 'github.com',
username: user.login
});
if (!contributor) {
let contributorAttr = {};
contributorAttr.account = req.body.account;
contributorAttr.name = user.name || user.login;
contributorAttr.kind = "person";
contributorAttr.url = user.blog;
contributorAttr.github_username = user.login;
contributorAttr.github_uid = user.id;
kredits.Contributor.add(contributorAttr, { gasLimit: 350000 })
.then(transaction => {
robot.logger.info('[hubot-kredits] Contributor added from GitHub signup', transaction.hash);
res.status(201);
res.json({
transactionHash: transaction.hash,
github_username: user.login
});
}, error => {
robot.logger.error(`[hubot-kredits] Adding contributor failed: ${error}`);
res.status(422);
res.json({ error })
});
} else {
res.json({
github_username: user.login
});
}
});
} else {
robot.logger.warning('[hubot-kredits] No KREDITS_GITHUB_KEY and KREDITS_GITHUB_SECRET configured for OAuth signup');
}
}; };

View File

@@ -129,8 +129,7 @@ module.exports = async function(robot, kredits) {
function calculateAmountForChanges(details) { function calculateAmountForChanges(details) {
let amount; let amount;
if (details.charsAdded < 280) { if (details.charsAdded < 500) {
// less than a tweet
amount = 500; amount = 500;
} else if (details.charsAdded < 2000) { } else if (details.charsAdded < 2000) {
amount = 1500; amount = 1500;

View File

@@ -0,0 +1,21 @@
module.exports = function (labels) {
const kreditsLabel = labels.filter(n => n.match(/^kredits/))[0];
// No label, no kredits
if (typeof kreditsLabel === 'undefined') { return 0; }
// TODO move amounts to config?
let amount;
switch(kreditsLabel) {
case 'kredits-1':
amount = 500;
break;
case 'kredits-2':
amount = 1500;
break;
case 'kredits-3':
amount = 5000;
break;
}
return amount;
};

View File

@@ -0,0 +1,18 @@
module.exports = function (labels) {
let kind = 'dev';
if (labels.find(l => l.match(/ops|operations/))) {
kind = 'ops';
}
else if (labels.find(l => l.match(/docs|documentation/))) {
kind = 'docs';
}
else if (labels.find(l => l.match(/design/))) {
kind = 'design';
}
else if (labels.find(l => l.match(/community/))) {
kind = 'community';
}
return kind;
};

1101
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "hubot-kredits", "name": "hubot-kredits",
"version": "3.1.2", "version": "3.4.0",
"description": "Kosmos Kredits functionality for chat bots", "description": "Kosmos Kredits functionality for chat bots",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@@ -10,11 +10,15 @@
"create-wallet": "scripts/create-wallet.js" "create-wallet": "scripts/create-wallet.js"
}, },
"dependencies": { "dependencies": {
"cors": "^2.8.5",
"eth-provider": "^0.2.2", "eth-provider": "^0.2.2",
"ethers": "^4.0.27", "ethers": "^4.0.27",
"group-array": "^0.3.3", "express": "^4.17.1",
"express-session": "^1.16.2",
"grant-express": "^4.6.1",
"group-array": "^1.0.0",
"kosmos-schemas": "^1.1.2", "kosmos-schemas": "^1.1.2",
"kredits-contracts": "^5.1.1", "kredits-contracts": "^5.4.0",
"node-cron": "^2.0.3", "node-cron": "^2.0.3",
"node-fetch": "^2.3.0", "node-fetch": "^2.3.0",
"prompt": "^1.0.0" "prompt": "^1.0.0"