Merge pull request #177 from Permissionless-Software-Foundation/ct-unstable

Usage: back up to DB to track usage over restarts.
This commit is contained in:
Chris Troutner
2025-06-05 18:27:06 -07:00
committed by GitHub
7 changed files with 159 additions and 27 deletions
+2
View File
@@ -4,11 +4,13 @@
// Load Mongoose models.
import Users from './models/users.js'
import Usage from './models/usage.js'
class LocalDB {
constructor () {
// Encapsulate dependencies
this.Users = Users
this.Usage = Usage
}
}
+15
View File
@@ -0,0 +1,15 @@
/*
Usage data model
*/
// Global npm libraries
import mongoose from 'mongoose'
const Usage = new mongoose.Schema({
ip: { type: String },
url: { type: String },
method: { type: String },
timestamp: { type: Date }
})
export default mongoose.model('usage', Usage)
+35 -17
View File
@@ -23,58 +23,76 @@ class TimerControllers {
this.debugLevel = localConfig.debugLevel
// Constants
this.cleanUsageInterval = 60000 * 60 // 1 hour
this.backupUsageInterval = 60000 * 10 // 10 minutes
// Encapsulate dependencies
this.config = config
// Bind 'this' object to all subfunctions.
this.exampleTimerFunc = this.exampleTimerFunc.bind(this)
this.cleanUsage = this.cleanUsage.bind(this)
// this.startTimers()
this.backupUsage = this.backupUsage.bind(this)
}
// Start all the time-based controllers.
startTimers () {
// 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 * 60) // 1 hour
this.cleanUsageHandle = setInterval(this.cleanUsage, this.cleanUsageInterval)
this.backupUsageHandle = setInterval(this.backupUsage, this.backupUsageInterval)
return true
}
stopTimers () {
clearInterval(this.optimizeWalletHandle)
clearInterval(this.cleanUsageHandle)
clearInterval(this.backupUsageHandle)
}
// Replace this example function with your own timer handler.
exampleTimerFunc (negativeTest) {
// Clean the usage state so that stats reflect the last 24 hours.
cleanUsage () {
try {
console.log('Example timer controller executed.')
clearInterval(this.cleanUsageHandle)
if (negativeTest) throw new Error('test error')
const now = new Date()
console.log(`cleanUsage() Timer Controller executing at ${now.toLocaleString()}`)
this.useCases.usage.cleanUsage()
this.cleanUsageHandle = setInterval(this.cleanUsage, this.cleanUsageInterval)
return true
} catch (err) {
console.error('Error in exampleTimerFunc(): ', err)
console.error('Error in time-controller.js/cleanUsage(): ', err)
this.cleanUsageHandle = setInterval(this.cleanUsage, this.cleanUsageInterval)
// Note: Do not throw an error. This is a top-level function.
return false
}
}
// Clean the usage state so that stats reflect the last 24 hours.
cleanUsage () {
// Backup the usage stats to the database
async backupUsage () {
try {
const now = new Date()
console.log(`cleanUsage() Timer Controller executing at ${now.toLocaleString()}`)
clearInterval(this.backupUsageHandle)
this.useCases.usage.cleanUsage()
console.log('backupUsage() Timer Controller executing at ', new Date().toLocaleString())
// Clear the database of old usage data.
await this.useCases.usage.clearUsage()
// Save the current usage snapshot to the database.
await this.useCases.usage.saveUsage()
this.backupUsageHandle = setInterval(this.backupUsage, this.backupUsageInterval)
return true
} catch (err) {
console.error('Error in time-controller.js/cleanUsage(): ', err)
console.error('Error in time-controller.js/backupUsage(): ', err)
this.backupUsageHandle = setInterval(this.backupUsage, this.backupUsageInterval)
// Note: Do not throw an error. This is a top-level function.
return false
+3
View File
@@ -24,6 +24,9 @@ class UseCases {
// Run any startup Use Cases at the start of the app.
async start () {
// Load the usage stats from the database
await this.usage.loadUsage()
console.log('Async Use Cases have been started.')
return true
+69
View File
@@ -3,6 +3,10 @@
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.
Usage stats are held in memory. But they are periodically backed up to the
Mongo database. On startup, the usage stats are loaded from the database.
This allows the usage stats to be persisted across restarts.
*/
// This global variable is used to share data between the REST middleware and
@@ -19,11 +23,17 @@ class UsageUseCases {
)
}
// Encapsulate dependencies
this.UsageModel = this.adapters.localdb.Usage
// Bind 'this' object to all subfunctions
this.cleanUsage = this.cleanUsage.bind(this)
this.getRestSummary = this.getRestSummary.bind(this)
this.getTopIps = this.getTopIps.bind(this)
this.getTopEndpoints = this.getTopEndpoints.bind(this)
this.clearUsage = this.clearUsage.bind(this)
this.saveUsage = this.saveUsage.bind(this)
this.loadUsage = this.loadUsage.bind(this)
// State
}
@@ -108,6 +118,65 @@ class UsageUseCases {
throw err
}
}
// Clear the usage database data
async clearUsage () {
try {
await this.UsageModel.deleteMany({})
// Debugging: verify the database is empty
// Delete this code after debugging
const usage = await this.UsageModel.find({})
console.log('clearUsage() usage: ', usage)
} catch (err) {
console.error('Error in usage-use-cases.js/clearUsage()')
throw err
}
}
// Save the usage data to the database
async saveUsage (inObj = {}) {
try {
for (let i = 0; i < restCalls.length; i++) {
const thisRestCall = restCalls[i]
// Debugging: delete this code after debugging
if (i === 5) {
console.log('saveUsage() thisRestCall: ', thisRestCall)
}
const usageData = {
ip: thisRestCall.ip,
url: thisRestCall.url,
method: thisRestCall.method,
timestamp: thisRestCall.timestamp
}
const usage = new this.UsageModel(usageData)
await usage.save()
}
} catch (err) {
console.error('Error in usage-use-cases.js/saveUsage()')
throw err
}
}
// Load usage data from the database
async loadUsage () {
try {
const usage = await this.UsageModel.find({})
// console.log('usage: ', usage)
if (usage[5]) {
console.log('loadUsage() usage[5]: ', usage[5])
}
restCalls = usage
} catch (err) {
console.error('Error in usage-use-cases.js/loadUsage(): ', err)
// throw err
}
}
}
// This Koa middleware is called any time there is a REST API. It logs the
+27 -10
View File
@@ -66,19 +66,19 @@ describe('#Timer-Controllers', () => {
})
})
describe('#exampleTimerFunc', () => {
it('should kick off the Use Case', async () => {
const result = await uut.exampleTimerFunc()
// describe('#exampleTimerFunc', () => {
// it('should kick off the Use Case', async () => {
// const result = await uut.exampleTimerFunc()
assert.equal(result, true)
})
// assert.equal(result, true)
// })
it('should return false on error', async () => {
const result = await uut.exampleTimerFunc(true)
// it('should return false on error', async () => {
// const result = await uut.exampleTimerFunc(true)
assert.equal(result, false)
})
})
// assert.equal(result, false)
// })
// })
describe('#cleanUsage', () => {
it('should kick off the Use Case', async () => {
@@ -94,4 +94,21 @@ describe('#Timer-Controllers', () => {
assert.equal(result, false)
})
})
describe('#backupUsage', () => {
it('should kick off the Use Case', async () => {
const result = await uut.backupUsage()
assert.equal(result, true)
})
it('should return false on error', async () => {
sandbox.stub(uut.useCases.usage, 'clearUsage').throws(new Error('test error'))
// sandbox.stub(uut.useCases.usage, 'saveUsage').throws(new Error('test error'))
const result = await uut.backupUsage()
assert.equal(result, false)
})
})
})
+8
View File
@@ -47,6 +47,14 @@ class UsageUseCaseMock {
async getTopEndpoints(existingUser, newData) {
return true
}
async clearUsage() {
return true
}
async saveUsage() {
return true
}
}
class UseCasesMock {