From 425d2ada65f0f7cac82068580bb44c9342bf77ba Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sat, 20 Apr 2024 07:14:17 -0700 Subject: [PATCH 1/6] Binding 'this' object to some subfunctions --- src/controllers/index.js | 7 +++++++ src/controllers/rest-api/index.js | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/controllers/index.js b/src/controllers/index.js index 117de46..12f91ac 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -21,6 +21,13 @@ class Controllers { this.useCases = new UseCases({ adapters: this.adapters }) this.timerControllers = new TimerControllers({ adapters: this.adapters, useCases: this.useCases }) 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. diff --git a/src/controllers/rest-api/index.js b/src/controllers/rest-api/index.js index 63e18a6..c6dbf22 100644 --- a/src/controllers/rest-api/index.js +++ b/src/controllers/rest-api/index.js @@ -29,7 +29,8 @@ class RESTControllers { ) } - // console.log('Controllers localConfig: ', localConfig) + // Bind 'this' object to all subfunctions. + this.attachRESTControllers = this.attachRESTControllers.bind(this) } attachRESTControllers (app) { From 5e2cf86f4beaf766d43bdaf43e8092623fcee333 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Fri, 26 Apr 2024 14:38:03 -0700 Subject: [PATCH 2/6] Checking in code before switching branches --- bin/server.js | 5 + src/controllers/rest-api/usage/controller.js | 145 +++++++++++++++++++ src/controllers/rest-api/usage/index.js | 67 +++++++++ src/use-cases/usage-use-cases.js | 69 +++++++++ 4 files changed, 286 insertions(+) create mode 100644 src/controllers/rest-api/usage/controller.js create mode 100644 src/controllers/rest-api/usage/index.js create mode 100644 src/use-cases/usage-use-cases.js diff --git a/bin/server.js b/bin/server.js index 535c238..ccf49b2 100644 --- a/bin/server.js +++ b/bin/server.js @@ -25,10 +25,14 @@ import config from '../config/index.js' // this first. import AdminLib from '../src/adapters/admin.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 Controllers from '../src/controllers/index.js' import { applyPassportMods } from '../config/passport.js' +// import {usageMiddleware} from '../src/use-cases/usage-use-cases.js' +// console.log('usageMiddleware: ', usageMiddleware) + class Server { constructor () { // Encapsulate dependencies @@ -65,6 +69,7 @@ class Server { app.use(bodyParser()) app.use(session()) app.use(errorMiddleware()) + app.use(usageMiddleware()) // Used to generate the docs. app.use(mount('/', serve(`${process.cwd()}/docs`))) diff --git a/src/controllers/rest-api/usage/controller.js b/src/controllers/rest-api/usage/controller.js new file mode 100644 index 0000000..d883288 --- /dev/null +++ b/src/controllers/rest-api/usage/controller.js @@ -0,0 +1,145 @@ +/* + REST API Controller library for the /ipfs route +*/ + +// Global npm libraries + +// Local libraries +import wlogger from '../../../adapters/wlogger.js' + +class IpfsRESTControllerLib { + 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.getPeers = this.getPeers.bind(this) + this.getRelays = this.getRelays.bind(this) + this.handleError = this.handleError.bind(this) + this.connect = this.connect.bind(this) + this.getThisNode = this.getThisNode.bind(this) + } + + /** + * @api {get} /ipfs Get status on IPFS infrastructure + * @apiPermission public + * @apiName GetIpfsStatus + * @apiGroup REST BCH + * + * @apiExample Example usage: + * curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs + * + */ + async getStatus (ctx) { + try { + const status = await this.adapters.ipfs.getStatus() + + ctx.body = { status } + } catch (err) { + wlogger.error('Error in ipfs/controller.js/getStatus(): ') + // ctx.throw(422, err.message) + this.handleError(ctx, err) + } + } + + // Return information on IPFS peers this node is connected to. + async getPeers (ctx) { + try { + const showAll = ctx.request.body.showAll + + const peers = await this.adapters.ipfs.getPeers(showAll) + + ctx.body = { peers } + } catch (err) { + wlogger.error('Error in ipfs/controller.js/getPeers(): ') + // ctx.throw(422, err.message) + this.handleError(ctx, err) + } + } + + // Get data about the known Circuit Relays. Hydrate with data from peers list. + async getRelays (ctx) { + try { + const relays = await this.adapters.ipfs.getRelays() + + ctx.body = { relays } + } catch (err) { + wlogger.error('Error in ipfs/controller.js/getRelays(): ') + // ctx.throw(422, err.message) + this.handleError(ctx, err) + } + } + + async connect (ctx) { + try { + const multiaddr = ctx.request.body.multiaddr + const getDetails = ctx.request.body.getDetails + + // console.log('this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs: ', this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs) + const result = await this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs.connectToPeer({ multiaddr, getDetails }) + // console.log('result: ', result) + + ctx.body = result + } catch (err) { + wlogger.error('Error in ipfs/controller.js/connect():', err) + // ctx.throw(422, err.message) + this.handleError(ctx, err) + } + } + + /** + * @api {get} /ipfs/node Get a copy of the thisNode object from helia-coord + * @apiPermission public + * @apiName GetThisNode + * @apiGroup REST BCH + * + * @apiExample Example usage: + * curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs/node + * + */ + async getThisNode (ctx) { + try { + const thisNode = this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode + + ctx.body = { thisNode } + } catch (err) { + wlogger.error('Error in ipfs/controller.js/getThisNode(): ') + // 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 IpfsRESTControllerLib diff --git a/src/controllers/rest-api/usage/index.js b/src/controllers/rest-api/usage/index.js new file mode 100644 index 0000000..1bf0d11 --- /dev/null +++ b/src/controllers/rest-api/usage/index.js @@ -0,0 +1,67 @@ +/* + REST API library for the /usage route. +*/ + +// Public npm libraries. +import Router from 'koa-router' + +// Local libraries. +import IPFSRESTControllerLib from './controller.js' +import Validators from '../middleware/validators.js' + +// let _this + +class IpfsRouter { + 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.ipfsRESTController = new IPFSRESTControllerLib(dependencies) + this.validators = new Validators() + + // Instantiate the router and set the base route. + const baseUrl = '/ipfs' + 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.ipfsRESTController.getStatus) + this.router.post('/peers', this.ipfsRESTController.getPeers) + this.router.post('/relays', this.ipfsRESTController.getRelays) + this.router.post('/connect', this.ipfsRESTController.connect) + this.router.get('/node', this.ipfsRESTController.getThisNode) + + // Attach the Controller routes to the Koa app. + app.use(this.router.routes()) + app.use(this.router.allowedMethods()) + } +} + +// module.exports = BchRouter +export default IpfsRouter diff --git a/src/use-cases/usage-use-cases.js b/src/use-cases/usage-use-cases.js new file mode 100644 index 0000000..d982ae1 --- /dev/null +++ b/src/use-cases/usage-use-cases.js @@ -0,0 +1,69 @@ +/* + 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. +const 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 + + + // State + + } + + // 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 + } + } +} + +// 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 } From 064ac36dc721183be7d01006f8cc01c4c618a816 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Fri, 26 Apr 2024 14:38:17 -0700 Subject: [PATCH 3/6] linting --- src/use-cases/usage-use-cases.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/use-cases/usage-use-cases.js b/src/use-cases/usage-use-cases.js index d982ae1..5bd0475 100644 --- a/src/use-cases/usage-use-cases.js +++ b/src/use-cases/usage-use-cases.js @@ -21,9 +21,7 @@ class UsageUseCases { // Bind 'this' object to all subfunctions - // State - } // Track the calls to a REST API From 556c6ec522a53eb436095348ad91d0c783180b26 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sat, 7 Dec 2024 20:48:08 -0800 Subject: [PATCH 4/6] fix(usage): Adding Usage REST API --- src/controllers/rest-api/index.js | 5 ++ src/controllers/rest-api/usage/controller.js | 88 ++------------------ src/controllers/rest-api/usage/index.js | 16 ++-- src/use-cases/index.js | 3 + src/use-cases/usage-use-cases.js | 1 + 5 files changed, 24 insertions(+), 89 deletions(-) diff --git a/src/controllers/rest-api/index.js b/src/controllers/rest-api/index.js index 3537539..74d5175 100644 --- a/src/controllers/rest-api/index.js +++ b/src/controllers/rest-api/index.js @@ -13,6 +13,7 @@ import ContactRESTController from './contact/index.js' import LogsRESTController from './logs/index.js' import IpfsRESTController from './ipfs/index.js' import config from '../../../config/index.js' +import UsageRESTController from './usage/index.js' class RESTControllers { constructor (localConfig = {}) { @@ -64,6 +65,10 @@ class RESTControllers { // Attach the REST API Controllers associated with the /ipfs route const ipfsRESTController = new IpfsRESTController(dependencies) ipfsRESTController.attach(app) + + // Attach the REST API Controllers associated with the /usage route + const usageRESTController = new UsageRESTController(dependencies) + usageRESTController.attach(app) } } diff --git a/src/controllers/rest-api/usage/controller.js b/src/controllers/rest-api/usage/controller.js index d883288..4c05da7 100644 --- a/src/controllers/rest-api/usage/controller.js +++ b/src/controllers/rest-api/usage/controller.js @@ -7,7 +7,7 @@ // Local libraries import wlogger from '../../../adapters/wlogger.js' -class IpfsRESTControllerLib { +class UsageRESTControllerLib { constructor (localConfig = {}) { // Dependency Injection. this.adapters = localConfig.adapters @@ -29,97 +29,27 @@ class IpfsRESTControllerLib { // Bind 'this' object to all subfunctions this.getStatus = this.getStatus.bind(this) - this.getPeers = this.getPeers.bind(this) - this.getRelays = this.getRelays.bind(this) this.handleError = this.handleError.bind(this) - this.connect = this.connect.bind(this) - this.getThisNode = this.getThisNode.bind(this) } /** - * @api {get} /ipfs Get status on IPFS infrastructure + * @api {get} /usage Get status on IPFS infrastructure * @apiPermission public - * @apiName GetIpfsStatus - * @apiGroup REST BCH + * @apiName GetUsageStatus + * @apiGroup REST Usage * * @apiExample Example usage: - * curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs + * curl -H "Content-Type: application/json" -X GET localhost:5020/usage * */ async getStatus (ctx) { try { - const status = await this.adapters.ipfs.getStatus() + // const status = await this.adapters.ipfs.getStatus() + const status = await this.useCases.usage.getRestSummary() ctx.body = { status } } catch (err) { - wlogger.error('Error in ipfs/controller.js/getStatus(): ') - // ctx.throw(422, err.message) - this.handleError(ctx, err) - } - } - - // Return information on IPFS peers this node is connected to. - async getPeers (ctx) { - try { - const showAll = ctx.request.body.showAll - - const peers = await this.adapters.ipfs.getPeers(showAll) - - ctx.body = { peers } - } catch (err) { - wlogger.error('Error in ipfs/controller.js/getPeers(): ') - // ctx.throw(422, err.message) - this.handleError(ctx, err) - } - } - - // Get data about the known Circuit Relays. Hydrate with data from peers list. - async getRelays (ctx) { - try { - const relays = await this.adapters.ipfs.getRelays() - - ctx.body = { relays } - } catch (err) { - wlogger.error('Error in ipfs/controller.js/getRelays(): ') - // ctx.throw(422, err.message) - this.handleError(ctx, err) - } - } - - async connect (ctx) { - try { - const multiaddr = ctx.request.body.multiaddr - const getDetails = ctx.request.body.getDetails - - // console.log('this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs: ', this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs) - const result = await this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs.connectToPeer({ multiaddr, getDetails }) - // console.log('result: ', result) - - ctx.body = result - } catch (err) { - wlogger.error('Error in ipfs/controller.js/connect():', err) - // ctx.throw(422, err.message) - this.handleError(ctx, err) - } - } - - /** - * @api {get} /ipfs/node Get a copy of the thisNode object from helia-coord - * @apiPermission public - * @apiName GetThisNode - * @apiGroup REST BCH - * - * @apiExample Example usage: - * curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs/node - * - */ - async getThisNode (ctx) { - try { - const thisNode = this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode - - ctx.body = { thisNode } - } catch (err) { - wlogger.error('Error in ipfs/controller.js/getThisNode(): ') + wlogger.error('Error in usage/controller.js/getStatus(): ') // ctx.throw(422, err.message) this.handleError(ctx, err) } @@ -142,4 +72,4 @@ class IpfsRESTControllerLib { } // module.exports = IpfsRESTControllerLib -export default IpfsRESTControllerLib +export default UsageRESTControllerLib diff --git a/src/controllers/rest-api/usage/index.js b/src/controllers/rest-api/usage/index.js index 1bf0d11..64733b2 100644 --- a/src/controllers/rest-api/usage/index.js +++ b/src/controllers/rest-api/usage/index.js @@ -6,12 +6,12 @@ import Router from 'koa-router' // Local libraries. -import IPFSRESTControllerLib from './controller.js' +import UsageRESTControllerLib from './controller.js' import Validators from '../middleware/validators.js' // let _this -class IpfsRouter { +class UsageRouter { constructor (localConfig = {}) { // Dependency Injection. this.adapters = localConfig.adapters @@ -33,11 +33,11 @@ class IpfsRouter { } // Encapsulate dependencies. - this.ipfsRESTController = new IPFSRESTControllerLib(dependencies) + this.usageRESTController = new UsageRESTControllerLib(dependencies) this.validators = new Validators() // Instantiate the router and set the base route. - const baseUrl = '/ipfs' + const baseUrl = '/usage' this.router = new Router({ prefix: baseUrl }) // _this = this @@ -51,11 +51,7 @@ class IpfsRouter { } // Define the routes and attach the controller. - this.router.get('/', this.ipfsRESTController.getStatus) - this.router.post('/peers', this.ipfsRESTController.getPeers) - this.router.post('/relays', this.ipfsRESTController.getRelays) - this.router.post('/connect', this.ipfsRESTController.connect) - this.router.get('/node', this.ipfsRESTController.getThisNode) + this.router.get('/', this.usageRESTController.getStatus) // Attach the Controller routes to the Koa app. app.use(this.router.routes()) @@ -64,4 +60,4 @@ class IpfsRouter { } // module.exports = BchRouter -export default IpfsRouter +export default UsageRouter diff --git a/src/use-cases/index.js b/src/use-cases/index.js index a2ffac6..8ce82b3 100644 --- a/src/use-cases/index.js +++ b/src/use-cases/index.js @@ -4,7 +4,9 @@ https://troutsblog.com/blog/clean-architecture */ +// Local libraries import UserUseCases from './user.js' +import { UsageUseCases } from './usage-use-cases.js' class UseCases { constructor (localConfig = {}) { @@ -17,6 +19,7 @@ class UseCases { // console.log('use-cases/index.js localConfig: ', localConfig) this.user = new UserUseCases(localConfig) + this.usage = new UsageUseCases(localConfig) } // Run any startup Use Cases at the start of the app. diff --git a/src/use-cases/usage-use-cases.js b/src/use-cases/usage-use-cases.js index 5bd0475..0920031 100644 --- a/src/use-cases/usage-use-cases.js +++ b/src/use-cases/usage-use-cases.js @@ -20,6 +20,7 @@ class UsageUseCases { } // Bind 'this' object to all subfunctions + this.getRestSummary = this.getRestSummary.bind(this) // State } From 764905b04187a5bdb31ac896996d304f31f929c7 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sat, 7 Dec 2024 21:25:10 -0800 Subject: [PATCH 5/6] fix(usage): Adding endpoints to analyize 24 hour usage of the REST API --- src/controllers/rest-api/usage/controller.js | 50 +++++++++++++- src/controllers/rest-api/usage/index.js | 2 + src/controllers/timer-controllers.js | 17 ++++- src/use-cases/usage-use-cases.js | 69 +++++++++++++++++++- 4 files changed, 134 insertions(+), 4 deletions(-) diff --git a/src/controllers/rest-api/usage/controller.js b/src/controllers/rest-api/usage/controller.js index 4c05da7..d78db1e 100644 --- a/src/controllers/rest-api/usage/controller.js +++ b/src/controllers/rest-api/usage/controller.js @@ -29,6 +29,8 @@ class UsageRESTControllerLib { // 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) } @@ -42,10 +44,10 @@ class UsageRESTControllerLib { * curl -H "Content-Type: application/json" -X GET localhost:5020/usage * */ - async getStatus (ctx) { + getStatus (ctx) { try { // const status = await this.adapters.ipfs.getStatus() - const status = await this.useCases.usage.getRestSummary() + const status = this.useCases.usage.getRestSummary() ctx.body = { status } } catch (err) { @@ -55,6 +57,50 @@ class UsageRESTControllerLib { } } + /** + * @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. diff --git a/src/controllers/rest-api/usage/index.js b/src/controllers/rest-api/usage/index.js index 64733b2..f0b1004 100644 --- a/src/controllers/rest-api/usage/index.js +++ b/src/controllers/rest-api/usage/index.js @@ -52,6 +52,8 @@ class UsageRouter { // 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()) diff --git a/src/controllers/timer-controllers.js b/src/controllers/timer-controllers.js index daa0c64..b96b224 100644 --- a/src/controllers/timer-controllers.js +++ b/src/controllers/timer-controllers.js @@ -28,6 +28,7 @@ class TimerControllers { // Bind 'this' object to all subfunctions. this.exampleTimerFunc = this.exampleTimerFunc.bind(this) + this.cleanUsage = this.cleanUsage.bind(this) // this.startTimers() } @@ -36,13 +37,15 @@ class TimerControllers { startTimers () { // Any new timer control functions can be added here. They will be started // when the server starts. - this.optimizeWalletHandle = setInterval(this.exampleTimerFunc, 60000 * 10) + this.optimizeWalletHandle = setInterval(this.exampleTimerFunc, 60000 * 60) + this.cleanUsageHandle = setInterval(this.cleanUsage, 60000 * 1) // 1 hour return true } stopTimers () { clearInterval(this.optimizeWalletHandle) + clearInterval(this.cleanusageHandle) } // Replace this example function with your own timer handler. @@ -60,6 +63,18 @@ class TimerControllers { 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 diff --git a/src/use-cases/usage-use-cases.js b/src/use-cases/usage-use-cases.js index 0920031..356485a 100644 --- a/src/use-cases/usage-use-cases.js +++ b/src/use-cases/usage-use-cases.js @@ -7,7 +7,7 @@ // This global variable is used to share data between the REST middleware and // the Usage Use Case class instance. -const restCalls = [] +let restCalls = [] class UsageUseCases { constructor (localConfig = {}) { @@ -21,10 +21,27 @@ class UsageUseCases { // 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 { @@ -36,6 +53,56 @@ class UsageUseCases { 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 From 3606a2d31b890ab85c4850178f4fdbcd7f72edd7 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sat, 7 Dec 2024 21:27:41 -0800 Subject: [PATCH 6/6] code cleanup --- bin/server.js | 3 --- src/controllers/timer-controllers.js | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/bin/server.js b/bin/server.js index 55fd7a1..8fb2d65 100644 --- a/bin/server.js +++ b/bin/server.js @@ -30,9 +30,6 @@ import wlogger from '../src/adapters/wlogger.js' import Controllers from '../src/controllers/index.js' import { applyPassportMods } from '../config/passport.js' -// import {usageMiddleware} from '../src/use-cases/usage-use-cases.js' -// console.log('usageMiddleware: ', usageMiddleware) - class Server { constructor () { // Encapsulate dependencies diff --git a/src/controllers/timer-controllers.js b/src/controllers/timer-controllers.js index b96b224..10224b9 100644 --- a/src/controllers/timer-controllers.js +++ b/src/controllers/timer-controllers.js @@ -38,7 +38,7 @@ class TimerControllers { // Any new timer control functions can be added here. They will be started // when the server starts. this.optimizeWalletHandle = setInterval(this.exampleTimerFunc, 60000 * 60) - this.cleanUsageHandle = setInterval(this.cleanUsage, 60000 * 1) // 1 hour + this.cleanUsageHandle = setInterval(this.cleanUsage, 60000 * 60) // 1 hour return true }