Created and connected index.js files for use-cases, adapters, and controllers

This commit is contained in:
Chris Troutner
2021-07-06 17:17:26 -07:00
parent 574e4a3d90
commit fd2cfe0b2f
15 changed files with 3635 additions and 311 deletions
+4 -12
View File
@@ -12,7 +12,7 @@ const cors = require('kcors')
// Local libraries // Local libraries
const config = require('../config') // this first. const config = require('../config') // this first.
const IPFSLib = require('../src/lib/ipfs') // const IPFSLib = require('../src/lib/ipfs')
const AdminLib = require('../src/lib/admin') const AdminLib = require('../src/lib/admin')
const adminLib = new AdminLib() const adminLib = new AdminLib()
// const JSONRPC = require('../src/rpc') // const JSONRPC = require('../src/rpc')
@@ -52,13 +52,9 @@ async function startServer () {
app.use(passport.initialize()) app.use(passport.initialize())
app.use(passport.session()) app.use(passport.session())
// Attach boilerplate REST API endpoints. // Attach REST API and JSON RPC controllers to the app.
const restApi = require('../src/controllers/rest-api') const controllers = require('../src/controllers')
restApi.attachControllers(app) controllers.attachControllers(app)
// Custom Middleware Modules
// const modules = require('../src/modules')
// modules(app)
// Enable CORS for testing // Enable CORS for testing
// THIS IS A SECURITY RISK. COMMENT OUT FOR PRODUCTION // THIS IS A SECURITY RISK. COMMENT OUT FOR PRODUCTION
@@ -76,10 +72,6 @@ async function startServer () {
const success = await adminLib.createSystemUser() const success = await adminLib.createSystemUser()
if (success) console.log('System admin user created.') if (success) console.log('System admin user created.')
// Start the IPFS node.
const ipfsLib = new IPFSLib()
await ipfsLib.start()
return app return app
} }
// startServer() // startServer()
+3225 -210
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -25,12 +25,12 @@
}, },
"repository": "Permissionless-Software-Foundation/ipfs-service-provider", "repository": "Permissionless-Software-Foundation/ipfs-service-provider",
"dependencies": { "dependencies": {
"@psf/bch-js": "^4.18.0", "@psf/bch-js": "^4.20.1",
"axios": "^0.21.1", "axios": "^0.21.1",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"glob": "^7.1.6", "glob": "^7.1.6",
"ipfs": "^0.54.4", "ipfs": "^0.54.4",
"ipfs-coord": "^2.1.13", "ipfs-coord": "^3.2.0",
"jsonrpc-lite": "^2.2.0", "jsonrpc-lite": "^2.2.0",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"kcors": "^2.2.2", "kcors": "^2.2.2",
@@ -48,7 +48,6 @@
"mongoose": "^5.11.15", "mongoose": "^5.11.15",
"nodemailer": "^6.4.17", "nodemailer": "^6.4.17",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"uuid": "^8.3.2",
"winston": "^3.3.3", "winston": "^3.3.3",
"winston-daily-rotate-file": "^4.5.0" "winston-daily-rotate-file": "^4.5.0"
}, },
@@ -65,9 +64,10 @@
"husky": "^4.3.8", "husky": "^4.3.8",
"mocha": "^8.2.1", "mocha": "^8.2.1",
"nyc": "^15.1.0", "nyc": "^15.1.0",
"semantic-release": "^17.4.2", "semantic-release": "^17.4.4",
"sinon": "^9.2.4", "sinon": "^9.2.4",
"standard": "^16.0.3" "standard": "^16.0.3",
"uuid": "^8.3.2"
}, },
"release": { "release": {
"publish": [ "publish": [
+11
View File
@@ -0,0 +1,11 @@
/*
This is a top-level library that encapsulates all the additional Adapters.
The concept of Adapters comes from Clean Architecture:
https://troutsblog.com/blog/clean-architecture
*/
// Individual adapter libraries.
const IPFSAdapter = require('./ipfs')
const ipfs = new IPFSAdapter()
module.exports = { ipfs }
+43
View File
@@ -0,0 +1,43 @@
/*
top-level IPFS library that combines the individual IPFS-based libraries.
*/
const IpfsAdapter = require('./ipfs')
const IpfsCoordAdapter = require('./ipfs-coord')
class IPFS {
constructor (localConfig) {
// Encapsulate dependencies
this.ipfsAdapter = new IpfsAdapter()
this.ipfsCoordAdapter = {} // placeholder
// Properties of this class instance.
this.isReady = false
}
// Provides a global start() function that triggers the start() function in
// the underlying libraries.
async start () {
try {
// Start IPFS
await this.ipfsAdapter.start()
console.log('IPFS is ready.')
// this.ipfs is a Promise that will resolve into an instance of an IPFS node.
this.ipfs = this.ipfsAdapter.ipfs
// Start ipfs-coord
this.ipfsCoordAdapter = new IpfsCoordAdapter({
ipfs: this.ipfs
})
await this.ipfsCoordAdapter.start()
console.log('ipfs-coord is ready.')
} catch (err) {
console.error('Error in adapters/ipfs/index.js/start()')
throw err
}
}
}
module.exports = IPFS
+73
View File
@@ -0,0 +1,73 @@
/*
Clean Architecture Adapter for ipfs-coord.
This library deals with ipfs-coord library so that the apps business logic
doesn't need to have any specific knowledge of the library.
*/
// Global npm libraries
const IpfsCoord = require('ipfs-coord')
const BCHJS = require('@psf/bch-js')
// Local libraries
const config = require('../../../config')
// const JSONRPC = require('../../controllers/json-rpc/')
let _this
class IpfsCoordAdapter {
constructor (localConfig = {}) {
// Dependency injection.
this.ipfs = localConfig.ipfs
if (!this.ipfs) {
throw new Error(
'Instance of IPFS must be passed when instantiating ipfs-coord.'
)
}
// Encapsulate dependencies
this.IpfsCoord = IpfsCoord
this.bchjs = new BCHJS()
// this.rpc = new JSONRPC()
this.config = config
// Properties of this class instance.
this.isReady = false
_this = this
}
async start () {
this.ipfsCoord = new this.IpfsCoord({
ipfs: this.ipfs,
type: 'node.js',
// type: 'browser',
bchjs: this.bchjs,
privateLog: console.log, // Default to console.log
isCircuitRelay: this.config.isCircuitRelay,
apiInfo: this.config.apiInfo,
announceJsonLd: this.config.announceJsonLd
})
// Wait for the ipfs-coord library to signal that it is ready.
await this.ipfsCoord.isReady()
// Signal that this adapter is ready.
this.isReady = true
return this.isReady
}
// Expects router to be a function, which handles the input data from the
// pubsub channel. It's expected to be capable of routing JSON RPC commands.
attachRPCRouter (router) {
try {
_this.ipfsCoord.privateLog = router
_this.ipfsCoord.ipfs.orbitdb.privateLog = router
} catch (err) {
console.error('Error in attachRPCRouter()')
throw err
}
}
}
module.exports = IpfsCoordAdapter
+80
View File
@@ -0,0 +1,80 @@
/*
Clean Architecture Adapter for IPFS.
This library deals with IPFS so that the apps business logic doesn't need
to have any specific knowledge of the js-ipfs library.
*/
// Global npm libraries
const IPFS = require('ipfs')
// Local libraries
const config = require('../../../config')
class IpfsAdapter {
constructor (localConfig) {
// Encapsulate dependencies
this.IPFS = IPFS
// Properties of this class instance.
this.isReady = false
this.config = config
}
// Start an IPFS node.
async start () {
try {
// Ipfs Options
const ipfsOptions = {
repo: './ipfsdata',
start: true,
config: {
relay: {
enabled: true, // enable circuit relay dialer and listener
hop: {
enabled: true // enable circuit relay HOP (make this node a relay)
}
},
pubsub: true, // enable pubsub
Swarm: {
ConnMgr: {
HighWater: 30,
LowWater: 10
}
},
Addresses: {
Swarm: [
`/ip4/0.0.0.0/tcp/${this.config.ipfsTcpPort}`,
`/ip4/0.0.0.0/tcp/${this.config.ipfsWsPort}/ws`
]
}
}
}
// Create a new IPFS node.
this.ipfs = await this.IPFS.create(ipfsOptions)
// Set the 'server' profile so the node does not scan private networks.
await this.ipfs.config.profiles.apply('server')
// const nodeConfig = await this.ipfs.config.getAll()
// console.log(
// `IPFS node configuration: ${JSON.stringify(nodeConfig, null, 2)}`
// )
// Stop the IPFS node if we're running tests.
if (this.config.env === 'test') {
await this.ipfs.stop()
}
// Signal that this adapter is ready.
this.isReady = true
return this.ipfs
} catch (err) {
console.error('Error in ipfs.js/start()')
throw err
}
}
}
module.exports = IpfsAdapter
+47
View File
@@ -0,0 +1,47 @@
/*
This is a top-level library that encapsulates all the additional Controllers.
The concept of Controllers comes from Clean Architecture:
https://troutsblog.com/blog/clean-architecture
*/
// Public npm libraries.
// Load the REST API Controllers.
const boilerplateRESTControllers = require('./rest-api')
// Load the Clean Architecture Adapters library
const adapters = require('../adapters')
// Load the JSON RPC Controller.
const JSONRPC = require('./json-rpc')
// Load the Clean Architecture Use Case libraries.
const UseCases = require('../use-cases')
const useCases = new UseCases({ adapters })
// Top-level function for this library.
// Start the various Controllers and attach them to the app.
async function attachControllers (app) {
// Attach the REST controllers to the Koa app.
attachRESTControllers(app)
// Start IPFS.
await adapters.ipfs.start()
attachRPCControllers()
}
function attachRESTControllers (app) {
// Attach the REST API Controllers associated with the boilerplate code to the Koa app.
boilerplateRESTControllers.attachRESTControllers(app)
}
// Add the JSON RPC router to the ipfs-coord adapter.
function attachRPCControllers () {
const jsonRpcController = new JSONRPC({ adapters, useCases })
// Attach the input of the JSON RPC router to the output of ipfs-coord.
adapters.ipfs.ipfsCoordAdapter.attachRPCRouter(jsonRpcController.router)
}
module.exports = { attachControllers }
+15 -3
View File
@@ -15,15 +15,27 @@ let _this
class JSONRPC { class JSONRPC {
constructor (localConfig) { constructor (localConfig) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating PostEntry REST Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating PostEntry REST Controller.'
)
}
// Encapsulate dependencies // Encapsulate dependencies
this.ipfsCoord = this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord
this.jsonrpc = jsonrpc this.jsonrpc = jsonrpc
this.userController = new UserController() this.userController = new UserController()
this.authController = new AuthController() this.authController = new AuthController()
this.aboutController = new AboutController() this.aboutController = new AboutController()
// This will be replaced once the ipfs-coord lib finishes initializing.
this.ipfsCoord = {}
_this = this _this = this
} }
+1 -71
View File
@@ -7,40 +7,11 @@
// Public npm libraries. // Public npm libraries.
// Load the REST API Controllers. // Load the REST API Controllers.
// const EntryRESTController = require('./rest/entry')
// const WebhookRESTController = require('./rest/webhook')
// const PostWebhook = require('./rest/post-webhook')
const AuthRESTController = require('./auth') const AuthRESTController = require('./auth')
const UserRESTController = require('./users') const UserRESTController = require('./users')
const ContactRESTController = require('./contact') const ContactRESTController = require('./contact')
const LogsRESTController = require('./logs') const LogsRESTController = require('./logs')
// Load the Clean Architecture Adapters library
// const adapters = require('../adapters')
// Load the JSON RPC Controller.
// const JSONRPC = require('./json-rpc')
// Load the Clean Architecture Use Case libraries.
// const UseCases = require('../use-cases')
// const useCases = new UseCases({ adapters })
// Top-level function for this library.
// Start the various Controllers and attach them to the app.
async function attachControllers (app) {
// Attach the REST controllers to the Koa app.
attachRESTControllers(app)
// Start the P2WDB.
// await adapters.p2wdb.start()
// Start the P2WDB and attach the validation event handler/controller to
// the add-entry Use Case.
// await attachValidationController()
// attachRPCControllers()
}
function attachRESTControllers (app) { function attachRESTControllers (app) {
// Attach the REST API Controllers associated with the /auth route // Attach the REST API Controllers associated with the /auth route
const authRESTController = new AuthRESTController() const authRESTController = new AuthRESTController()
@@ -59,45 +30,4 @@ function attachRESTControllers (app) {
logsRESTController.attach(app) logsRESTController.attach(app)
} }
// Add the JSON RPC router to the ipfs-coord adapter. module.exports = { attachRESTControllers }
// function attachRPCControllers () {
// const jsonRpcController = new JSONRPC({ adapters, useCases })
//
// // Attach the input of the JSON RPC router to the output of ipfs-coord.
// adapters.p2wdb.ipfsAdapters.ipfsCoordAdapter.attachRPCRouter(
// jsonRpcController.router
// )
// }
// Start the P2WDB and its downstream depenencies (IPFS, ipfs-coord, OrbitDB).
// Also attach the post-validation, peer-replication event handler (controller)
// to the Add-Entry Use Case.
// async function attachValidationController () {
// try {
// // Trigger the addPeerEntry() use-case after a replication-validation event.
// adapters.p2wdb.orbit.validationEvent.on(
// 'ValidationSucceeded',
// async function (data) {
// try {
// // console.log(
// // 'ValidationSucceeded event triggering addPeerEntry() with this data: ',
// // data
// // )
//
// await useCases.entry.addEntry.addPeerEntry(data)
// } catch (err) {
// console.error(
// 'Error trying to process peer data with addPeerEntry(): ',
// err
// )
// // Do not throw an error. This is a top-level function.
// }
// }
// )
// } catch (err) {
// console.error('Error in controllers/index.js/startP2wdb()')
// throw err
// }
// }
module.exports = { attachControllers }
+18
View File
@@ -0,0 +1,18 @@
/*
This is a top-level library that encapsulates all the additional Use Cases.
The concept of Use Cases comes from Clean Architecture:
https://troutsblog.com/blog/clean-architecture
*/
class UseCases {
constructor (localConfig = {}) {
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of adapters must be passed in when instantiating Use Cases library.'
)
}
}
}
module.exports = UseCases
+88 -9
View File
@@ -3,29 +3,108 @@
*/ */
// Public npm libraries // Public npm libraries
const assert = require('chai').assert
const jsonrpc = require('jsonrpc-lite') const jsonrpc = require('jsonrpc-lite')
const sinon = require('sinon')
const { v4: uid } = require('uuid')
// Set the environment variable to signal this is a test.
process.env.SVC_ENV = 'test'
// Local libraries.
const JSONRPC = require('../../../src/controllers/json-rpc') const JSONRPC = require('../../../src/controllers/json-rpc')
const adapters = require('../mocks/adapters')
const UseCasesMock = require('../mocks/use-cases')
describe('#JSON RPC', () => { describe('#JSON RPC', () => {
let uut let uut
let sandbox
beforeEach(() => { beforeEach(() => {
uut = new JSONRPC() sandbox = sinon.createSandbox()
const useCases = new UseCasesMock()
uut = new JSONRPC({ adapters, useCases })
}) })
afterEach(() => sandbox.restore())
describe('#router', () => { describe('#router', () => {
it('should do something', async () => { it('should exit quietly if given a random string', async () => {
// const request = { const str = 'random string message'
// 'users', // id await uut.router(str)
// 'getAll', // method
// {} assert.isOk('Not throwing an error is a pass.')
// } })
const json = jsonrpc.request('users', 'getAll', {})
it('should exit quietly if invalid JSON RPC message received', async () => {
const malformedRpc = '{"jsonrpc":"2.0"}'
await uut.router(malformedRpc, 'peerA')
assert.isOk('Not throwing an error is a pass.')
})
it('should return default response if routing is not possible', async () => {
const id = uid()
const json = jsonrpc.request(id, 'unknownMethod', {})
const str = JSON.stringify(json) const str = JSON.stringify(json)
await uut.router(str) const result = await uut.router(str, 'peerA')
// console.log('result: ', result)
const jsonObj = jsonrpc.parse(result.retStr)
// console.log(`jsonObj: ${JSON.stringify(jsonObj, null, 2)}`)
// Assert the expected properties exist on the returned object.
assert.property(jsonObj, 'payload')
assert.property(jsonObj, 'type')
assert.property(jsonObj.payload, 'jsonrpc')
assert.property(jsonObj.payload, 'id')
assert.property(jsonObj.payload, 'result')
assert.property(jsonObj.payload.result, 'reciever')
assert.property(jsonObj.payload.result.value, 'success')
assert.property(jsonObj.payload.result.value, 'message')
// Assert the expected values exist.
assert.equal(jsonObj.payload.id, id)
assert.equal(jsonObj.payload.result.value.success, false)
assert.equal(jsonObj.payload.result.value.status, 422)
assert.equal(
jsonObj.payload.result.value.message,
'Input does not match routing rules.'
)
})
it('should catch and handle errors', async () => {
// Force an error
sandbox.stub(uut.jsonrpc, 'parse').throws(new Error('test error'))
const malformedRpc = '{"jsonrpc":"2.0"}'
await uut.router(malformedRpc, 'peerA')
assert.isOk('Not throwing an error is a pass.')
})
it('should route to users handler', async () => {
const id = uid()
const userCall = jsonrpc.request(id, 'users', { endpoint: 'getAll' })
const jsonStr = JSON.stringify(userCall, null, 2)
// Mock the users controller.
sandbox.stub(uut.userController, 'userRouter').resolves('true')
const result = await uut.router(jsonStr, 'peerA')
// console.log(result)
const obj = JSON.parse(result.retStr)
// console.log('obj: ', obj)
assert.equal(obj.result.value, 'true')
assert.equal(obj.result.method, 'users')
assert.equal(obj.id, id)
}) })
}) })
}) })
+4 -1
View File
@@ -13,6 +13,8 @@ process.env.SVC_ENV = 'test'
// Local libraries. // Local libraries.
const JSONRPC = require('../../../src/controllers/json-rpc') const JSONRPC = require('../../../src/controllers/json-rpc')
const adapters = require('../mocks/adapters')
const UseCasesMock = require('../mocks/use-cases')
describe('#JSON RPC', () => { describe('#JSON RPC', () => {
let uut let uut
@@ -21,7 +23,8 @@ describe('#JSON RPC', () => {
beforeEach(() => { beforeEach(() => {
sandbox = sinon.createSandbox() sandbox = sinon.createSandbox()
uut = new JSONRPC() const useCases = new UseCasesMock()
uut = new JSONRPC({ adapters, useCases })
}) })
afterEach(() => sandbox.restore()) afterEach(() => sandbox.restore())
+14
View File
@@ -0,0 +1,14 @@
/*
Mocks for the Adapter library.
*/
const ipfs = {
ipfsAdapter: {
ipfs: {}
},
ipfsCoordAdapter: {
ipfsCoord: {}
}
}
module.exports = { ipfs }
+7
View File
@@ -0,0 +1,7 @@
/*
Mocks for the use cases.
*/
class UseCasesMock {}
module.exports = UseCasesMock