diff --git a/package-lock.json b/package-lock.json index 95e7dfa..04540c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@ethersproject/experimental": "5.7.0", "@kredits/contracts": "^7.3.0", + "axios": "^1.7.9", "cors": "^2.8.5", "dotenv": "^16.3.1", "eth-provider": "^0.13.6", @@ -987,6 +988,21 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -1351,6 +1367,17 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1482,6 +1509,14 @@ "ms": "2.0.0" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1892,6 +1927,25 @@ "node": ">= 0.8" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -1911,6 +1965,19 @@ "node": ">=0.10.0" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -3000,6 +3067,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -4279,6 +4351,21 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "requires": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -4598,6 +4685,14 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4702,6 +4797,11 @@ "ms": "2.0.0" } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -5017,6 +5117,11 @@ "unpipe": "~1.0.0" } }, + "follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==" + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -5030,6 +5135,16 @@ "for-in": "^1.0.1" } }, + "form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -5859,6 +5974,11 @@ "ipaddr.js": "1.9.1" } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", diff --git a/package.json b/package.json index 19f29e9..d69aaeb 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "@ethersproject/experimental": "5.7.0", "@kredits/contracts": "^7.3.0", + "axios": "^1.7.9", "cors": "^2.8.5", "dotenv": "^16.3.1", "eth-provider": "^0.13.6", diff --git a/scripts/lib/gitea-reviews.js b/scripts/lib/gitea-reviews.js index 4df6084..6c4a15c 100644 --- a/scripts/lib/gitea-reviews.js +++ b/scripts/lib/gitea-reviews.js @@ -1,29 +1,25 @@ -const fetch = require('node-fetch'); +const axios = require('axios'); module.exports = class GiteaReviews { - token = null; + client = null; kreditsAmounts = null; - pageLimit = 100; + pageLimit = 50; constructor (token, kreditsAmounts) { - this.token = token; this.kreditsAmounts = kreditsAmounts; - } - async request (path) { - return fetch( - `https://gitea.kosmos.org/api/v1${path}`, - { - headers: { - 'accepts': 'application/json', - 'Authorization': `token ${this.token}` - } + this.client = axios.create({ + baseURL: 'https://gitea.kosmos.org/api/v1', + headers: { + 'accepts': 'application/json', + 'Authorization': `token ${token}` } - ).then(response => response.json()); + }); } async getReviewContributions (repos, startDate, endDate) { + let pulls = []; let reviewContributions = {} await Promise.all(repos.map(async (repo) => { @@ -32,17 +28,17 @@ module.exports = class GiteaReviews { do { try { - result = await this.request(`/repos/${repo}/pulls?state=closed&limit=${this.pageLimit}&page=${page}`); + result = await this.client.get(`/repos/${repo}/pulls?state=closed&limit=${this.pageLimit}&page=${page}`); } catch(error) { console.log(`failed to fetch PRs for repo ${repo}:`, error.message); continue; } - if (!result || result.length === 0) { + if (!result || !result.data || result.data.length === 0) { continue; } - let pullRequests = result.filter(pr => { + let pullRequests = result.data.filter(pr => { if (!pr.merged) return false; // only interested in merged PRs // check if the PR has been merged in the given timeframe @@ -56,21 +52,23 @@ module.exports = class GiteaReviews { await Promise.all(pullRequests.map(async (pr) => { let reviews; try { - reviews = await this.request(`/repos/${repo}/pulls/${pr.number}/reviews`); + reviews = await this.client.get(`/repos/${repo}/pulls/${pr.number}/reviews`); } catch(error) { console.log(`failed to fetch reviews for repo ${repo}, PR ${pr.number}:`, error.message); return; } - if (!reviews || reviews.length === 0) { + if (!reviews || !reviews.data || reviews.data.length === 0) { return; } - reviews = reviews.filter(review => { + reviews = reviews.data.filter(review => { return ['APPROVED', 'REJECTED'].includes(review.state); }); reviews.forEach(review => { + // console.debug(`Review from /repos/${repo}/pulls/${pr.number}`); + if (typeof reviewContributions[review.user.login] === 'undefined') { reviewContributions[review.user.login] = []; } @@ -88,7 +86,7 @@ module.exports = class GiteaReviews { })); page++; - } while (result && result.length > 0); + } while (result && result.data && result.data.length > 0); })); return reviewContributions; diff --git a/scripts/lib/github-reviews.js b/scripts/lib/github-reviews.js index 3b210f8..d618df6 100644 --- a/scripts/lib/github-reviews.js +++ b/scripts/lib/github-reviews.js @@ -1,30 +1,26 @@ -const fetch = require('node-fetch'); +const axios = require('axios'); module.exports = class GithubReviews { - token = null; + client = null; kreditsAmounts = null; pageLimit = 100; constructor (token, kreditsAmounts) { - this.token = token; this.kreditsAmounts = kreditsAmounts; - } - async request (path) { - return fetch( - `https://api.github.com${path}`, - { - headers: { - 'Accept': 'application/vnd.github.v3+json', - 'User-Agent': 'Kosmos Kredits for reviews', - 'Authorization': `token ${this.token}` - } + this.client = axios.create({ + baseURL: 'https://api.github.com', + headers: { + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'Kosmos Kredits for reviews', + 'Authorization': `token ${token}` } - ).then(response => response.json()); + }); } async getReviewContributions (repos, startDate, endDate) { + let pulls = []; let reviewContributions = {} await Promise.all(repos.map(async (repo) => { @@ -33,17 +29,17 @@ module.exports = class GithubReviews { do { try { - result = await this.request(`/repos/${repo}/pulls?state=closed&perPage=${this.pageLimit}&page=${page}`); + result = await this.client.get(`/repos/${repo}/pulls?state=closed&perPage=${this.pageLimit}&page=${page}`); } catch(error) { console.log(`failed to fetch PRs for repo ${repo}:`, error.message); continue; } - if (!result || result.length === 0) { + if (!result || !result.data || result.data.length === 0) { continue; } - let pullRequests = result.filter(pr => { + let pullRequests = result.data.filter(pr => { if (!pr.merged_at) return false; // only interested in merged PRs // check if the PR has been merged in the given timeframe @@ -57,21 +53,23 @@ module.exports = class GithubReviews { await Promise.all(pullRequests.map(async (pr) => { let reviews; try { - reviews = await this.request(`/repos/${repo}/pulls/${pr.number}/reviews`); + reviews = await this.client.get(`/repos/${repo}/pulls/${pr.number}/reviews`); } catch(error) { console.log(`failed to fetch reviews for repo ${repo}, PR ${pr.number}:`, error.message); return; } - if (!reviews || reviews.length === 0) { + if (!reviews || !reviews.data || reviews.data.length === 0) { return; } - reviews = reviews.filter(review => { + reviews = reviews.data.filter(review => { return ['APPROVED', 'REJECTED'].includes(review.state); }); reviews.forEach(review => { + // console.debug(`Review from /repos/${repo}/pulls/${pr.number}`); + if (typeof reviewContributions[review.user.login] === 'undefined') { reviewContributions[review.user.login] = []; } @@ -89,7 +87,7 @@ module.exports = class GithubReviews { })); page++; - } while (result && result.length > 0); + } while (result && result.data && result.data.length > 0); })); return reviewContributions; diff --git a/repos.json b/scripts/repos.json similarity index 64% rename from repos.json rename to scripts/repos.json index 9b90db7..dda7dec 100644 --- a/repos.json +++ b/scripts/repos.json @@ -1,25 +1,25 @@ { "github": [ - "67P/botka", - "67P/hal8000", "67P/hubot-kredits", "67P/hubot-remotestorage-logger", "67P/hyperchannel", - "67P/kosmos-schemas", - "67P/kredits-contracts", "67P/kredits-signup", "67P/kredits-web", - "67P/remotestorage-module-kosmos", "67P/waves", "sockethub/sockethub" ], "gitea": [ "kosmos/akkounts", + "kosmos/botka", "kosmos/chef", "kosmos/gitea.kosmos.org", + "kosmos/hal8000", "kosmos/ipfs-cookbook", - "kosmos/kredits-ipfs-pinner", + "kosmos/rs-module-kosmos", + "kosmos/schemas", "kosmos/website", - "kosmos/wormhole" + "kosmos/wormhole", + "kredits/contracts", + "kredits/ipfs-pinner" ] } diff --git a/scripts/review-kredits.js b/scripts/review-kredits.js index 6f41e51..23242e0 100755 --- a/scripts/review-kredits.js +++ b/scripts/review-kredits.js @@ -1,62 +1,68 @@ #!/usr/bin/env node -require('dotenv').config({ path: '.env' }); +require('dotenv').config(); + +const yargs = require('yargs/yargs'); +const { hideBin } = require('yargs/helpers'); +const util = require('util'); +const fs = require('fs'); + const GiteaReviews = require('./lib/gitea-reviews'); const GithubReviews = require('./lib/github-reviews'); const ethers = require('ethers'); const NonceManager = require('@ethersproject/experimental').NonceManager; -const Kredits = require('kredits-contracts'); -const util = require('util'); +const Kredits = require('@kredits/contracts'); -const yargs = require('yargs/yargs'); -const { hideBin } = require('yargs/helpers'); -const fs = require('fs'); - -const walletPath = process.env.KREDITS_WALLET_PATH || '../wallet.json'; +const walletPath = process.env.KREDITS_WALLET_PATH || './wallet.json'; const walletJson = fs.readFileSync(walletPath); -const providerUrl = process.env.KREDITS_PROVIDER_URL; -const daoAddress = process.env.KREDITS_DAO_ADDRESS; +const providerUrl = process.env.KREDITS_PROVIDER_URL || 'http://localhost:7545'; const ipfsConfig = { host: process.env.IPFS_API_HOST || 'localhost', port: process.env.IPFS_API_PORT || '5001', - protocol: process.env.IPFS_API_PROTOCOL || 'http' + protocol: process.env.IPFS_API_PROTOCOL || 'http', }; +console.log('ipfsConfig:', ipfsConfig); const kreditsAmounts = { 'kredits-1': 100, 'kredits-2': 300, - 'kredits-3': 1000 + 'kredits-3': 1000, }; -const repos = require('../repos.json'); +const repos = require('./repos.json'); const argv = yargs(hideBin(process.argv)) .option('start', { alias: 's', - description: 'Include reviews for PRs merged after this date' + description: 'Include reviews for PRs merged after this date', }) .option('end', { alias: 'e', - description: 'Include reviews for PRs merged before this date' + description: 'Include reviews for PRs merged before this date', }) .option('dry', { alias: 'd', type: 'boolean', - description: 'Only list contribution details without creating them' + description: 'Only list contribution details without creating them', }) .help() .version() .demandOption('start', 'Please provide a start date') - .default('end', function now () { - return (new Date()).toISOString().split('.')[0]+"Z"; + .default('end', function now() { + return new Date().toISOString().split('.')[0] + 'Z'; }) .example([ - ['$0 --start 2020-11-01 --end 2020-11-30T23:59:59Z', 'Create contributions for reviews of pull requests merged in November 2020'], - ['$0 --start 2021-01-01', 'Create contributions for reviews of pull requests merged from Januar 2021 until now'], - ]) - .argv + [ + '$0 --start 2020-11-01 --end 2020-11-30T23:59:59Z', + 'Create contributions for reviews of pull requests merged in November 2020', + ], + [ + '$0 --start 2021-01-01', + 'Create contributions for reviews of pull requests merged from Januar 2021 until now', + ], + ]).argv; const startTimestamp = Date.parse(argv.start); const endTimestamp = Date.parse(argv.end); @@ -71,50 +77,47 @@ if (isNaN(endTimestamp)) { process.exit(1); } -// check for existence of GITHUB_TOKEN and GITEA_TOKEN -if (!process.env.GITHUB_TOKEN || !process.env.GITEA_TOKEN) { - console.log('Please set both GITHUB_TOKEN and GITEA_TOKEN'); - process.exit(1); -} - const startDate = new Date(startTimestamp); const endDate = new Date(endTimestamp); async function getAllReviews(repos, startDate, endDate) { - const githubReviews = new GithubReviews(process.env.GITHUB_TOKEN, kreditsAmounts); - const giteaReviews = new GiteaReviews(process.env.GITEA_TOKEN, kreditsAmounts); + const githubReviews = new GithubReviews( + process.env.GITHUB_TOKEN, + kreditsAmounts + ); + const giteaReviews = new GiteaReviews( + process.env.GITEA_TOKEN, + kreditsAmounts + ); return Promise.all([ githubReviews.getReviewContributions(repos.github, startDate, endDate), - giteaReviews.getReviewContributions(repos.gitea, startDate, endDate) - ]).then(reviews => { - return { github: reviews[0], gitea: reviews[1] } + giteaReviews.getReviewContributions(repos.gitea, startDate, endDate), + ]).then((reviews) => { + return { github: reviews[0], gitea: reviews[1] }; }); } -async function initializeKredits () { +async function initializeKredits() { // - // Ethereum wallet setup + // Wallet setup // let wallet; try { wallet = await ethers.Wallet.fromEncryptedJson(walletJson, process.env.KREDITS_WALLET_PASSWORD); } catch(error) { - console.log('Could not load wallet:', error); + console.warn('Could not load wallet:', error); process.exit(1); } // - // Ethereum provider/node setup + // Solidity VM provider/node setup // - let ethProvider; - if (providerUrl) { - ethProvider = new ethers.providers.JsonRpcProvider(providerUrl); - } else { - ethProvider = new ethers.getDefaultProvider('rinkeby'); - } + console.log('Using blockchain node/API at', providerUrl); + + const ethProvider = new ethers.providers.JsonRpcProvider(providerUrl); const signer = new NonceManager(wallet.connect(ethProvider)); // @@ -122,51 +125,58 @@ async function initializeKredits () { // const opts = { ipfsConfig }; - if (daoAddress) { - opts.addresses = { Kernel: daoAddress }; - } let kredits; try { kredits = await new Kredits(signer.provider, signer, opts).init(); } catch(error) { - console.log('Could not set up kredits:', error); + console.warn('Could not set up kredits:', error); process.exit(1); } + console.log('Wallet address: ' + wallet.address); + + // + // Check robot's wallet balance and alert when it's broke + // + + ethProvider.getBalance(wallet.address).then(balance => { + console.log('Wallet balance: ' + ethers.utils.formatEther(balance) + ' RBTC'); + if (balance.lt(ethers.utils.parseEther('0.0001'))) { + console.warn(`I\'m broke! Please send some RBTC to ${wallet.address} first.`); + process.exit(1); + } + }); + return kredits; } -function createContribution(contributorName, contributionAttributes, Contribution) { - console.log(`Creating contribution token for ${contributionAttributes.amount}₭S to ${contributorName} for ${contributionAttributes.description}...`); - - return Contribution.add(contributionAttributes).catch(error => { - console.log(`I tried to add a contribution for ${contributorName}, but I encountered an error when submitting the tx:`); - console.log(`Error:`, error); - console.log('Contribution attributes:'); - console.log(util.inspect(contributionAttributes, { depth: 2, colors: true })); - }); -} - -async function generateContributionData(reviews, Contributor, startDate, endDate) { - const dateFormatOptions = { year: 'numeric', month: 'long', day: 'numeric' }; +async function generateContributionData(reviews, Contributor) { const contributors = await Contributor.all(); const contributionData = {}; - const now = (new Date()).toISOString().split('.')[0]+"Z"; - [date, time] = now.split('T'); + + const nextDay = new Date(endDate); + nextDay.setUTCDate(nextDay.getUTCDate() + 1); + nextDay.setUTCHours(0, 0, 0, 0); + const nextDayStr = nextDay.toISOString().split('.')[0] + 'Z'; + [date, time] = nextDayStr.split('T'); function addContributionDataForPlatform(platform) { - for (const [username, platformReviews] of Object.entries(reviews[platform])) { - const contributor = contributors.find(c => { + for (const [username, platformReviews] of Object.entries( + reviews[platform] + )) { + const contributor = contributors.find((c) => { return c[`${platform}_username`] === username; }); if (!contributor) { - console.log(`Could not find contributor for ${platform} user "${username}"`); + console.log( + `Could not find contributor for ${platform} user "${username}"` + ); continue; } - const urls = platformReviews.map(review => review.pr.html_url); + const urls = platformReviews.map((review) => review.pr.html_url); const kreditsAmount = platformReviews.reduce((amount, review) => { return review.kredits + amount; }, 0); @@ -175,9 +185,6 @@ async function generateContributionData(reviews, Contributor, startDate, endDate contributionData[contributor.name].amount += kreditsAmount; contributionData[contributor.name].details.pullRequests.push(...urls); } else { - const formattedStartDate = startDate.toLocaleString('en-us', dateFormatOptions); - const formattedEndDate = endDate.toLocaleString('en-us', dateFormatOptions); - contributionData[contributor.name] = { contributorId: contributor.id, contributorIpfsHash: contributor.ipfsHash, @@ -185,11 +192,11 @@ async function generateContributionData(reviews, Contributor, startDate, endDate time, amount: kreditsAmount, kind: 'dev', - description: `PR reviews from ${formattedStartDate} to ${formattedEndDate}`, details: { - 'pullRequests': urls - } - } + kind: 'review', + pullRequests: urls, + }, + }; } } } @@ -200,20 +207,40 @@ async function generateContributionData(reviews, Contributor, startDate, endDate return contributionData; } -Promise.all([initializeKredits(), getAllReviews(repos, startDate, endDate)]).then((values) => { +Promise.all([ + initializeKredits(), + getAllReviews(repos, startDate, endDate), +]).then((values) => { const kredits = values[0]; const reviews = values[1]; + const Contributor = kredits.Contributor; + const Contribution = kredits.Contribution; - generateContributionData(reviews, kredits.Contributor, startDate, endDate).then(contributionData => { - if (argv.dry) { - console.log('Contributions:'); - console.log(util.inspect(contributionData, { depth: 3, colors: true })); - } else { - // create contributions - for (const [username, contributionAttributes] of Object.entries(contributionData)) { - createContribution(username, contributionAttributes, kredits.Contribution); + async function createContribution(nickname, attrs) { + console.log(`Creating review contribution for ${nickname}...`); + console.log(util.inspect(attrs, { depth: 1, colors: true })); + + return Contribution.add(attrs) + .catch(error => { + console.error(`Error:`, error.message); + }); + } + + generateContributionData(reviews, Contributor).then( + async (contributionData) => { + if (argv.dry) { + console.log('Contributions:'); + console.log(util.inspect(contributionData, { depth: 3, colors: true })); + return; + } else { + for (const nickname of Object.keys(contributionData)) { + const description = `Reviewed ${contributionData[nickname].details.pullRequests.length} pull requests (from ${startDate.toISOString().split('T')[0]} to ${endDate.toISOString().split('T')[0]})`; + await createContribution(nickname, { + ...contributionData[nickname], + description, + }); + } } } - }); + ); }); -