gittoken-analytics
Version:
Analytics Processor for GitToken Events
287 lines (267 loc) • 11.3 kB
JavaScript
import GitTokenContract from 'gittoken-contracts/build/contracts/GitToken.json'
import Promise, { join, promisifyAll } from 'bluebird'
import Web3 from 'web3'
import mysql from 'mysql'
import updateTokenInflationRate from './updateTokenInflationRate'
import updateInflationRateAverage from './updateInflationRateAverage'
import updateLeaderboard from './updateLeaderboard'
import updateTotalSupply from './updateTotalSupply'
import updateContributionFrequency from './updateContributionFrequency'
import updateSummaryStatistics from './updateSummaryStatistics'
import saveContributionEvent from './saveContributionEvent'
import updateRewardTypeStats from './updateRewardTypeStats'
import updateUserTokenCreation from './updateUserTokenCreation'
import milestoneCreated from './milestoneCreated'
import milestoneCompleted from './milestoneCompleted'
// const { abi } = JSON.parse(GitTokenContract)
export default class GitTokenAnalytics {
/**
* GitToken Analytics Constructor Options
* @param {Object} options { mysql: { ...} }
*/
constructor(options) {
this.listen()
const { web3Provider, mysqlOpts, contractAddress, abi } = options
this.saveContributionEvent = saveContributionEvent.bind(this)
this.updateLeaderboard = updateLeaderboard.bind(this)
this.updateTotalSupply = updateTotalSupply.bind(this)
this.updateContributionFrequency = updateContributionFrequency.bind(this)
this.updateSummaryStatistics = updateSummaryStatistics.bind(this)
this.updateTokenInflationRate = updateTokenInflationRate.bind(this)
this.updateInflationRateAverage = updateInflationRateAverage.bind(this)
this.updateRewardTypeStats = updateRewardTypeStats.bind(this)
this.updateUserTokenCreation = updateUserTokenCreation.bind(this)
this.milestoneCreated = milestoneCreated.bind(this)
this.milestoneCompleted = milestoneCompleted.bind(this)
this.contractDetails = {}
if (web3Provider && mysqlOpts && contractAddress && abi) {
this.configure({ web3Provider, mysqlOpts, contractAddress, abi }).then((configured) => {
console.log('GitToken Analytics Processor Configured')
console.log(JSON.stringify(configured, null, 2))
this._watchContributionEvents()
})
} else {
console.log(`GitToken Analytics Processor listening for 'configure' event.`)
}
}
configure({ web3Provider, mysqlOpts, contractAddress, abi }) {
return new Promise((resolve, reject) => {
this.establishMySqlConnection({ mysqlOpts }).then(() => {
return this.configureWeb3Provider({ web3Provider })
}).then(() => {
return this.configureContract({ abi, contractAddress })
}).then((contract) => {
return this.getContractDetails()
}).then(() => {
// console.log('this.contractDetails', this.contractDetails)
resolve({
contractDetails: this.contractDetails
})
}).catch((error) => {
console.log('error', error)
this.handleError({ error, method: 'configure' })
})
})
}
establishMySqlConnection({ mysqlOpts }) {
return new Promise((resolve, reject) => {
try {
this.mysql = mysql.createConnection({ ...mysqlOpts })
this.mysql.connect()
resolve({ mysql: this.mysql })
} catch (error) {
this.handleError({ error, method: 'establishMySqlConnection' })
}
})
}
query({ queryString, queryObject=[] }) {
return new Promise((resolve, reject) => {
/* TODO: Check mysql docs for second param (queryObject) */
this.mysql.query(queryString, (error, result) => {
if (error) { this.handleError({ error, method: 'query' }) }
resolve(result)
})
})
}
configureWeb3Provider({ web3Provider }) {
return new Promise((resolve, reject) => {
try {
console.log('web3Provider', web3Provider)
this.web3 = new Web3(new Web3.providers.HttpProvider(web3Provider))
this.eth = promisifyAll(this.web3.eth)
resolve({ web3: this.web3, eth: this.eth })
} catch (error) {
this.handleError({ error, method: 'configureWeb3Provider' })
}
})
}
configureContract({ abi, contractAddress }) {
return new Promise((resolve, reject) => {
this.contract = this.web3.eth.contract(abi).at(contractAddress)
Promise.resolve(Object.keys(this.contract)).map((method) => {
if (this.contract[method] && this.contract[method]['request']) {
this.contract[method] = promisifyAll(this.contract[method])
}
}).then(() => {
resolve(this.contract)
}).catch((error) => {
this.handleError({ error, method: 'configureContract' })
})
})
}
_watchContributionEvents() {
const events = this.contract.Contribution({}, { fromBlock: 0, toBlock: 'latest' })
events.watch((error, result) => {
if (error) { this.handleError({ error, method: '_watchContributionEvents' }) }
console.log('_watchContributionEvents::result', result)
this.saveContributionEvent({ event: result }).then((contribution) => {
return join(
this.updateLeaderboard({ contribution }),
this.updateTotalSupply({ contribution }),
this.updateContributionFrequency({ contribution }),
this.updateTokenInflationRate({ contribution }),
this.updateInflationRateAverage({ contribution }),
this.updateSummaryStatistics({ contribution }),
this.updateRewardTypeStats({ contribution }),
this.updateUserTokenCreation({ contribution }),
contribution
)
}).then((data) => {
// console.log(JSON.stringify(data, null, 2))
process.send(JSON.stringify({
event: 'broadcast_contribution_data',
message: `Analytics updated on new contribution event.`,
data
}))
})
})
}
getContractDetails() {
return new Promise((resolve, reject) => {
join(
this.contract.name.callAsync(),
this.contract.symbol.callAsync(),
this.contract.decimals.callAsync(),
this.contract.organization.callAsync()
).then((data) => {
// console.log('getContractDetails::data', data)
try {
this.contractDetails = {
name: data[0],
symbol: data[1],
decimals: data[2].toNumber(),
organization: data[3],
address: this.contract.address
}
resolve({ contractDetails: this.contractDetails })
} catch (error) {
throw error
}
}).catch((error) => {
console.log('contractDetails::error', error)
this.handleError({ error, method: 'getContractDetails' })
})
})
}
listen() {
console.log('GitToken Analytics Listening on Separate Process: ', process.pid)
process.on('message', (msg) => {
// console.log('msg', msg)
const { event, data } = JSON.parse(msg)
switch(event) {
case 'configure':
const { web3Provider, mysqlOpts, contractAddress, abi } = data
// console.log('listen::contractAddress, abi', contractAddress, abi)
this.configure({
web3Provider,
mysqlOpts,
contractAddress,
abi
}).then((configured) => {
process.send(JSON.stringify({ event, data: configured, message: 'GitToken Analytics Processor Configured' }))
this._watchContributionEvents()
})
break;
case 'contract_details':
this.getContractDetails().then((result) => {
process.send(JSON.stringify({ event, data: result, message: 'Contract details retrieved.' }))
})
break;
case 'get_contributions':
this.query({ queryString: `SELECT * FROM contributions;` }).then((result) => {
process.send(JSON.stringify({ event, data: result, message: `${event} data retrieved.` }))
})
break;
case 'get_total_supply':
this.query({ queryString: `SELECT * FROM total_supply;` }).then((result) => {
process.send(JSON.stringify({ event, data: result, message: `${event} data retrieved.` }))
})
break;
case 'get_leaderboard':
this.query({ queryString: `SELECT * FROM leaderboard;` }).then((result) => {
process.send(JSON.stringify({ event, data: result, message: `${event} data retrieved.` }))
})
break;
case 'get_contribution_frequency':
this.query({ queryString: `SELECT * FROM contribution_frequency;` }).then((result) => {
process.send(JSON.stringify({ event, data: result, message: `${event} data retrieved.` }))
})
break;
case 'get_token_inflation':
this.query({ queryString: `SELECT * FROM token_inflation;` }).then((result) => {
process.send(JSON.stringify({ event, data: result, message: `${event} data retrieved.` }))
})
break;
case 'get_token_inflation_mean':
this.query({ queryString: `SELECT * FROM token_inflation_mean;` }).then((result) => {
process.send(JSON.stringify({ event, data: result, message: `${event} data retrieved.` }))
})
break;
case 'get_user_token_creation':
this.query({ queryString: `SELECT * FROM user_token_creation;` }).then((result) => {
process.send(JSON.stringify({ event, data: result, message: `${event} data retrieved.` }))
})
break;
case 'get_reward_type_stats':
this.query({ queryString: `SELECT * FROM reward_type_stats;` }).then((result) => {
process.send(JSON.stringify({ event, data: result, message: `${event} data retrieved.` }))
})
break;
case 'get_summary_statistics':
this.query({ queryString: `SELECT * FROM summary_statistics;` }).then((result) => {
process.send(JSON.stringify({ event, data: result, message: `${event} data retrieved.` }))
})
break;
case 'get_milestones':
this.query({ queryString: `SELECT * FROM milestones;` }).then((result) => {
process.send(JSON.stringify({ event, data: result, message: `${event} data retrieved.` }))
})
break;
case 'milestone_created':
this.milestoneCreated({ data }).then((result) => {
process.send(JSON.stringify({ event, data: result, message: `${event} data retrieved.` }))
})
break;
case 'milestone_closed':
this.milestoneCompleted({ data }).then((result) => {
process.send(JSON.stringify({ event, data: result, message: `${event} data retrieved.` }))
})
break;
default:
process.send(JSON.stringify({
message: 'Unhandled Analytics Event',
data: [],
event: 'error'
}))
}
})
}
handleError({ error, method }) {
/**
* TODO Add switch case handler based on error codes, etc.
* Determine when to send back message to parent process
*/
console.log('handleError::method', method)
console.log(error)
}
}