mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex.git
synced 2026-09-21 16:52:00 -07:00
Merge pull request #165 from Permissionless-Software-Foundation/usage
Usage
This commit is contained in:
@@ -25,6 +25,7 @@ import config from '../config/index.js' // this first.
|
|||||||
|
|
||||||
import AdminLib from '../src/adapters/admin.js'
|
import AdminLib from '../src/adapters/admin.js'
|
||||||
import errorMiddleware from '../src/controllers/rest-api/middleware/error.js'
|
import errorMiddleware from '../src/controllers/rest-api/middleware/error.js'
|
||||||
|
import { usageMiddleware } from '../src/use-cases/usage-use-cases.js'
|
||||||
import wlogger from '../src/adapters/wlogger.js'
|
import wlogger from '../src/adapters/wlogger.js'
|
||||||
import Controllers from '../src/controllers/index.js'
|
import Controllers from '../src/controllers/index.js'
|
||||||
import { applyPassportMods } from '../config/passport.js'
|
import { applyPassportMods } from '../config/passport.js'
|
||||||
@@ -67,6 +68,7 @@ class Server {
|
|||||||
app.use(bodyParser())
|
app.use(bodyParser())
|
||||||
app.use(session())
|
app.use(session())
|
||||||
app.use(errorMiddleware())
|
app.use(errorMiddleware())
|
||||||
|
app.use(usageMiddleware())
|
||||||
|
|
||||||
// Used to generate the docs.
|
// Used to generate the docs.
|
||||||
app.use(mount('/', serve(`${process.cwd()}/docs`)))
|
app.use(mount('/', serve(`${process.cwd()}/docs`)))
|
||||||
|
|||||||
@@ -21,6 +21,13 @@ class Controllers {
|
|||||||
this.useCases = new UseCases({ adapters: this.adapters })
|
this.useCases = new UseCases({ adapters: this.adapters })
|
||||||
this.timerControllers = new TimerControllers({ adapters: this.adapters, useCases: this.useCases })
|
this.timerControllers = new TimerControllers({ adapters: this.adapters, useCases: this.useCases })
|
||||||
this.config = config
|
this.config = config
|
||||||
|
|
||||||
|
// Bind 'this' object to all subfunction
|
||||||
|
this.initAdapters = this.initAdapters.bind(this)
|
||||||
|
this.initUseCases = this.initUseCases.bind(this)
|
||||||
|
this.attachRESTControllers = this.attachRESTControllers.bind(this)
|
||||||
|
this.attachControllers = this.attachControllers.bind(this)
|
||||||
|
this.attachRPCControllers = this.attachRPCControllers.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spin up any adapter libraries that have async startup needs.
|
// Spin up any adapter libraries that have async startup needs.
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import ContactRESTController from './contact/index.js'
|
|||||||
import LogsRESTController from './logs/index.js'
|
import LogsRESTController from './logs/index.js'
|
||||||
import IpfsRESTController from './ipfs/index.js'
|
import IpfsRESTController from './ipfs/index.js'
|
||||||
import config from '../../../config/index.js'
|
import config from '../../../config/index.js'
|
||||||
|
import UsageRESTController from './usage/index.js'
|
||||||
|
|
||||||
class RESTControllers {
|
class RESTControllers {
|
||||||
constructor (localConfig = {}) {
|
constructor (localConfig = {}) {
|
||||||
@@ -30,6 +31,9 @@ class RESTControllers {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bind 'this' object to all subfunctions.
|
||||||
|
this.attachRESTControllers = this.attachRESTControllers.bind(this)
|
||||||
|
|
||||||
// Encapsulate dependencies
|
// Encapsulate dependencies
|
||||||
this.config = config
|
this.config = config
|
||||||
}
|
}
|
||||||
@@ -61,6 +65,10 @@ class RESTControllers {
|
|||||||
// Attach the REST API Controllers associated with the /ipfs route
|
// Attach the REST API Controllers associated with the /ipfs route
|
||||||
const ipfsRESTController = new IpfsRESTController(dependencies)
|
const ipfsRESTController = new IpfsRESTController(dependencies)
|
||||||
ipfsRESTController.attach(app)
|
ipfsRESTController.attach(app)
|
||||||
|
|
||||||
|
// Attach the REST API Controllers associated with the /usage route
|
||||||
|
const usageRESTController = new UsageRESTController(dependencies)
|
||||||
|
usageRESTController.attach(app)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
REST API Controller library for the /ipfs route
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Global npm libraries
|
||||||
|
|
||||||
|
// Local libraries
|
||||||
|
import wlogger from '../../../adapters/wlogger.js'
|
||||||
|
|
||||||
|
class UsageRESTControllerLib {
|
||||||
|
constructor (localConfig = {}) {
|
||||||
|
// Dependency Injection.
|
||||||
|
this.adapters = localConfig.adapters
|
||||||
|
if (!this.adapters) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Adapters library required when instantiating /ipfs REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.useCases = localConfig.useCases
|
||||||
|
if (!this.useCases) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Use Cases library required when instantiating /ipfs REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encapsulate dependencies
|
||||||
|
// this.UserModel = this.adapters.localdb.Users
|
||||||
|
// this.userUseCases = this.useCases.user
|
||||||
|
|
||||||
|
// Bind 'this' object to all subfunctions
|
||||||
|
this.getStatus = this.getStatus.bind(this)
|
||||||
|
this.getTopIps = this.getTopIps.bind(this)
|
||||||
|
this.getTopEndpoints = this.getTopEndpoints.bind(this)
|
||||||
|
this.handleError = this.handleError.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} /usage Get status on IPFS infrastructure
|
||||||
|
* @apiPermission public
|
||||||
|
* @apiName GetUsageStatus
|
||||||
|
* @apiGroup REST Usage
|
||||||
|
*
|
||||||
|
* @apiExample Example usage:
|
||||||
|
* curl -H "Content-Type: application/json" -X GET localhost:5020/usage
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
getStatus (ctx) {
|
||||||
|
try {
|
||||||
|
// const status = await this.adapters.ipfs.getStatus()
|
||||||
|
const status = this.useCases.usage.getRestSummary()
|
||||||
|
|
||||||
|
ctx.body = { status }
|
||||||
|
} catch (err) {
|
||||||
|
wlogger.error('Error in usage/controller.js/getStatus(): ')
|
||||||
|
// ctx.throw(422, err.message)
|
||||||
|
this.handleError(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} /usage/ips Get top IP addresses consuming the REST API
|
||||||
|
* @apiPermission public
|
||||||
|
* @apiName GetUsageIPs
|
||||||
|
* @apiGroup REST Usage
|
||||||
|
*
|
||||||
|
* @apiExample Example usage:
|
||||||
|
* curl -H "Content-Type: application/json" -X GET localhost:5020/usage/ips
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
getTopIps (ctx) {
|
||||||
|
try {
|
||||||
|
const ips = this.useCases.usage.getTopIps()
|
||||||
|
|
||||||
|
ctx.body = { ips }
|
||||||
|
} catch (err) {
|
||||||
|
wlogger.error('Error in usage/controller.js/getTopIps(): ')
|
||||||
|
// ctx.throw(422, err.message)
|
||||||
|
this.handleError(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} /usage/endpoints Get top endpoints consumed from the REST API
|
||||||
|
* @apiPermission public
|
||||||
|
* @apiName GetUsageEndpoints
|
||||||
|
* @apiGroup REST Usage
|
||||||
|
*
|
||||||
|
* @apiExample Example usage:
|
||||||
|
* curl -H "Content-Type: application/json" -X GET localhost:5020/usage/endpoints
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
getTopEndpoints (ctx) {
|
||||||
|
try {
|
||||||
|
const endpoints = this.useCases.usage.getTopEndpoints()
|
||||||
|
|
||||||
|
ctx.body = { endpoints }
|
||||||
|
} catch (err) {
|
||||||
|
wlogger.error('Error in usage/controller.js/getTopEndpoints(): ')
|
||||||
|
// ctx.throw(422, err.message)
|
||||||
|
this.handleError(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DRY error handler
|
||||||
|
handleError (ctx, err) {
|
||||||
|
// If an HTTP status is specified by the buisiness logic, use that.
|
||||||
|
if (err.status) {
|
||||||
|
if (err.message) {
|
||||||
|
ctx.throw(err.status, err.message)
|
||||||
|
} else {
|
||||||
|
ctx.throw(err.status)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// By default use a 422 error if the HTTP status is not specified.
|
||||||
|
ctx.throw(422, err.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// module.exports = IpfsRESTControllerLib
|
||||||
|
export default UsageRESTControllerLib
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
REST API library for the /usage route.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public npm libraries.
|
||||||
|
import Router from 'koa-router'
|
||||||
|
|
||||||
|
// Local libraries.
|
||||||
|
import UsageRESTControllerLib from './controller.js'
|
||||||
|
import Validators from '../middleware/validators.js'
|
||||||
|
|
||||||
|
// let _this
|
||||||
|
|
||||||
|
class UsageRouter {
|
||||||
|
constructor (localConfig = {}) {
|
||||||
|
// Dependency Injection.
|
||||||
|
this.adapters = localConfig.adapters
|
||||||
|
if (!this.adapters) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Adapters library required when instantiating IPFS REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.useCases = localConfig.useCases
|
||||||
|
if (!this.useCases) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Use Cases library required when instantiating IPFS REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dependencies = {
|
||||||
|
adapters: this.adapters,
|
||||||
|
useCases: this.useCases
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encapsulate dependencies.
|
||||||
|
this.usageRESTController = new UsageRESTControllerLib(dependencies)
|
||||||
|
this.validators = new Validators()
|
||||||
|
|
||||||
|
// Instantiate the router and set the base route.
|
||||||
|
const baseUrl = '/usage'
|
||||||
|
this.router = new Router({ prefix: baseUrl })
|
||||||
|
|
||||||
|
// _this = this
|
||||||
|
}
|
||||||
|
|
||||||
|
attach (app) {
|
||||||
|
if (!app) {
|
||||||
|
throw new Error(
|
||||||
|
'Must pass app object when attaching REST API controllers.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the routes and attach the controller.
|
||||||
|
this.router.get('/', this.usageRESTController.getStatus)
|
||||||
|
this.router.get('/ips', this.usageRESTController.getTopIps)
|
||||||
|
this.router.get('/endpoints', this.usageRESTController.getTopEndpoints)
|
||||||
|
|
||||||
|
// Attach the Controller routes to the Koa app.
|
||||||
|
app.use(this.router.routes())
|
||||||
|
app.use(this.router.allowedMethods())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// module.exports = BchRouter
|
||||||
|
export default UsageRouter
|
||||||
@@ -28,6 +28,7 @@ class TimerControllers {
|
|||||||
|
|
||||||
// Bind 'this' object to all subfunctions.
|
// Bind 'this' object to all subfunctions.
|
||||||
this.exampleTimerFunc = this.exampleTimerFunc.bind(this)
|
this.exampleTimerFunc = this.exampleTimerFunc.bind(this)
|
||||||
|
this.cleanUsage = this.cleanUsage.bind(this)
|
||||||
|
|
||||||
// this.startTimers()
|
// this.startTimers()
|
||||||
}
|
}
|
||||||
@@ -36,13 +37,15 @@ class TimerControllers {
|
|||||||
startTimers () {
|
startTimers () {
|
||||||
// Any new timer control functions can be added here. They will be started
|
// Any new timer control functions can be added here. They will be started
|
||||||
// when the server starts.
|
// when the server starts.
|
||||||
this.optimizeWalletHandle = setInterval(this.exampleTimerFunc, 60000 * 10)
|
this.optimizeWalletHandle = setInterval(this.exampleTimerFunc, 60000 * 60)
|
||||||
|
this.cleanUsageHandle = setInterval(this.cleanUsage, 60000 * 60) // 1 hour
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
stopTimers () {
|
stopTimers () {
|
||||||
clearInterval(this.optimizeWalletHandle)
|
clearInterval(this.optimizeWalletHandle)
|
||||||
|
clearInterval(this.cleanusageHandle)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace this example function with your own timer handler.
|
// Replace this example function with your own timer handler.
|
||||||
@@ -60,6 +63,18 @@ class TimerControllers {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean the usage state so that stats reflect the last 24 hours.
|
||||||
|
cleanUsage () {
|
||||||
|
try {
|
||||||
|
this.useCases.usage.cleanUsage()
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in time-controller.js/cleanUsage(): ', err)
|
||||||
|
|
||||||
|
// Note: Do not throw an error. This is a top-level function.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TimerControllers
|
export default TimerControllers
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
https://troutsblog.com/blog/clean-architecture
|
https://troutsblog.com/blog/clean-architecture
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Local libraries
|
||||||
import UserUseCases from './user.js'
|
import UserUseCases from './user.js'
|
||||||
|
import { UsageUseCases } from './usage-use-cases.js'
|
||||||
|
|
||||||
class UseCases {
|
class UseCases {
|
||||||
constructor (localConfig = {}) {
|
constructor (localConfig = {}) {
|
||||||
@@ -17,6 +19,7 @@ class UseCases {
|
|||||||
|
|
||||||
// console.log('use-cases/index.js localConfig: ', localConfig)
|
// console.log('use-cases/index.js localConfig: ', localConfig)
|
||||||
this.user = new UserUseCases(localConfig)
|
this.user = new UserUseCases(localConfig)
|
||||||
|
this.usage = new UsageUseCases(localConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run any startup Use Cases at the start of the app.
|
// Run any startup Use Cases at the start of the app.
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
Use Case library for tracking usage. This library contains business logic
|
||||||
|
for tracking the usage of REST API and JSON RPC calls. This library is used
|
||||||
|
by admins to keep an eye on how many API calls were made in a 24-hour and
|
||||||
|
1-hour time period.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This global variable is used to share data between the REST middleware and
|
||||||
|
// the Usage Use Case class instance.
|
||||||
|
let restCalls = []
|
||||||
|
|
||||||
|
class UsageUseCases {
|
||||||
|
constructor (localConfig = {}) {
|
||||||
|
// console.log('User localConfig: ', localConfig)
|
||||||
|
this.adapters = localConfig.adapters
|
||||||
|
if (!this.adapters) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of adapters must be passed in when instantiating Usage Use Cases library.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind 'this' object to all subfunctions
|
||||||
|
this.getRestSummary = this.getRestSummary.bind(this)
|
||||||
|
this.getTopIps = this.getTopIps.bind(this)
|
||||||
|
this.getTopEndpoints = this.getTopEndpoints.bind(this)
|
||||||
|
|
||||||
|
// State
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the state by removing entries that are older than 24 hours. This
|
||||||
|
// ensures stats reflect only the last 24 hours.
|
||||||
|
// This function is called by a Timer Controller.
|
||||||
|
cleanUsage () {
|
||||||
|
try {
|
||||||
|
const now = new Date()
|
||||||
|
const twentyFourHoursAgo = now.getTime() - (60000 * 60 * 24)
|
||||||
|
|
||||||
|
restCalls = restCalls.filter(x => x.timestamp > twentyFourHoursAgo)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in usage-use-cases.js/cleanUsage()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track the calls to a REST API
|
||||||
|
getRestSummary (inObj = {}) {
|
||||||
|
try {
|
||||||
|
console.log(`getRestSummary(): There have been ${restCalls.length} REST calls`)
|
||||||
|
|
||||||
|
return restCalls.length
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in usage-use-cases.js/getRestSummary()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the top 20 IP addresses from the stats.
|
||||||
|
getTopIps () {
|
||||||
|
try {
|
||||||
|
const ips = restCalls.map(x => x.ip)
|
||||||
|
|
||||||
|
// Create a Map to count occurrences of each IP address string
|
||||||
|
const countMap = new Map()
|
||||||
|
ips.forEach(ip => {
|
||||||
|
countMap.set(ip, (countMap.get(ip) || 0) + 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Convert the Map into an array of objects with `str` and `cnt` properties
|
||||||
|
const result = Array.from(countMap, ([ip, cnt]) => ({ ip, cnt }))
|
||||||
|
|
||||||
|
// Sort the results by the `cnt` property in descending order
|
||||||
|
result.sort((a, b) => b.cnt - a.cnt)
|
||||||
|
|
||||||
|
// Ensure the result has at most 20 elements
|
||||||
|
return result.slice(0, 20)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in usage-use-cases.js/getTopIps()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the top 20 most consumed endpoints.
|
||||||
|
getTopEndpoints () {
|
||||||
|
try {
|
||||||
|
const endpoints = restCalls.map(x => `${x.method} ${x.url}`)
|
||||||
|
|
||||||
|
// Create a Map to count occurrences of each IP address string
|
||||||
|
const countMap = new Map()
|
||||||
|
endpoints.forEach(endpoint => {
|
||||||
|
countMap.set(endpoint, (countMap.get(endpoint) || 0) + 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Convert the Map into an array of objects with `str` and `cnt` properties
|
||||||
|
const result = Array.from(countMap, ([endpoint, cnt]) => ({ endpoint, cnt }))
|
||||||
|
|
||||||
|
// Sort the results by the `cnt` property in descending order
|
||||||
|
result.sort((a, b) => b.cnt - a.cnt)
|
||||||
|
|
||||||
|
// Ensure the result has at most 20 elements
|
||||||
|
return result.slice(0, 20)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in usage-use-cases.js/getTopEndpoints()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This Koa middleware is called any time there is a REST API. It logs the
|
||||||
|
// details from the request object.
|
||||||
|
function usageMiddleware () {
|
||||||
|
return async (ctx, next) => {
|
||||||
|
try {
|
||||||
|
await next()
|
||||||
|
|
||||||
|
console.log('ctx.request: ', ctx.request)
|
||||||
|
const now = new Date()
|
||||||
|
|
||||||
|
const reqObj = {
|
||||||
|
ip: ctx.request.ip,
|
||||||
|
url: ctx.request.url,
|
||||||
|
method: ctx.request.method,
|
||||||
|
timestamp: now.getTime()
|
||||||
|
}
|
||||||
|
console.log('reqObj: ', reqObj)
|
||||||
|
|
||||||
|
restCalls.push(reqObj)
|
||||||
|
} catch (err) {
|
||||||
|
ctx.status = err.status || 500
|
||||||
|
ctx.body = err.message
|
||||||
|
ctx.app.emit('error', err, ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { UsageUseCases, usageMiddleware }
|
||||||
Reference in New Issue
Block a user