Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/1468 support for bulk quotes post passthrough #233

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6510e54
updated to newly released version of event-sdk
rmothilal Mar 2, 2020
680f0fd
Merge branch 'master' of https://github.com/mojaloop/quoting-service
rmothilal Mar 2, 2020
c0b645b
updated dependencies and version
rmothilal Mar 2, 2020
5aa9b6b
Merge branch 'master' of https://github.com/mojaloop/quoting-service
rmothilal Mar 3, 2020
7939283
Merge branch 'master' of https://github.com/mojaloop/quoting-service
rmothilal Mar 17, 2020
308babe
Merge branch 'master' of https://github.com/mojaloop/quoting-service
rmothilal Mar 19, 2020
a2f221c
Merge branch 'master' of https://github.com/mojaloop/quoting-service
rmothilal May 29, 2020
d583ced
Merge branch 'master' of https://github.com/mojaloop/quoting-service
rmothilal Jun 8, 2020
52a7c31
Merge branch 'master' of https://github.com/mojaloop/quoting-service
rmothilal Jun 30, 2020
3cf3910
Merge branch 'master' of https://github.com/mojaloop/quoting-service
rmothilal Jul 1, 2020
8bb2d9e
Merge branch 'master' of https://github.com/mojaloop/quoting-service
rmothilal Jul 8, 2020
828b594
added support for bulk quotes post functionality
rmothilal Jul 9, 2020
d16ab68
added put, and get functionality for bulk quotes
rmothilal Jul 10, 2020
57a932c
Fix log text
rmothilal Jul 14, 2020
dcc68d0
added the put bulkQuotes error endpoint
rmothilal Jul 14, 2020
81fd1d4
updated according to stevens comments
rmothilal Jul 15, 2020
344c84a
removed unnecessary await
rmothilal Jul 15, 2020
97e9284
removed the await
rmothilal Jul 16, 2020
1752de0
awaits need to be there for audits
rmothilal Jul 16, 2020
89183eb
fixes for error handling according to @oderayi to handle exceptions i…
rmothilal Jul 16, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .ncurc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"reject": [
"json-rules-engine"
"json-rules-engine",
"@mojaloop/sdk-standard-components"
]
}
2,942 changes: 1,434 additions & 1,508 deletions package-lock.json

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "quoting-service",
"description": "Quoting Service hosted by a scheme",
"license": "Apache-2.0",
"version": "10.5.5",
"version": "10.6.0",
"author": "ModusBox",
"contributors": [
"James Bush <james.bush@modusbox.com>",
Expand Down Expand Up @@ -60,10 +60,10 @@
"dependencies": {
"@hapi/good": "9.0.0",
"@hapi/hapi": "19.1.1",
"@mojaloop/central-services-error-handling": "10.4.1",
"@mojaloop/central-services-logger": "10.4.0",
"@mojaloop/central-services-shared": "10.5.3",
"@mojaloop/event-sdk": "10.4.0",
"@mojaloop/central-services-error-handling": "10.6.0",
"@mojaloop/central-services-logger": "10.6.0",
"@mojaloop/central-services-shared": "10.6.2",
"@mojaloop/event-sdk": "10.6.0",
"@mojaloop/ml-number": "8.2.0",
"@mojaloop/sdk-standard-components": "10.3.2",
"axios": "0.19.2",
Expand All @@ -72,7 +72,7 @@
"good-console": "8.0.0",
"good-squeeze": "5.1.0",
"json-rules-engine": "5.0.2",
"knex": "0.21.1",
"knex": "0.21.2",
"memory-cache": "0.2.0",
"minimist": "1.2.5",
"mysql": "2.18.1",
Expand All @@ -81,11 +81,11 @@
"rc": "1.2.8"
},
"devDependencies": {
"@types/jest": "26.0.3",
"eslint": "7.3.1",
"@types/jest": "26.0.4",
"eslint": "7.4.0",
"jest": "26.1.0",
"jest-junit": "11.0.1",
"npm-audit-resolver": "2.2.0",
"npm-audit-resolver": "2.2.1",
"npm-check-updates": "7.0.1",
"nyc": "15.1.0",
"pre-commit": "1.2.2",
Expand Down
43 changes: 41 additions & 2 deletions src/handlers/bulkQuotes.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,18 @@

* Henk Kodde <henk.kodde@modusbox.com>
* Georgi Georgiev <georgi.georgiev@modusbox.com>
* Rajiv Mothilal <rajiv.mothilal@modusbox.com>
--------------
******/

'use strict'

const util = require('util')
const Enum = require('@mojaloop/central-services-shared').Enum
const ErrorHandler = require('@mojaloop/central-services-error-handling')
const EventSdk = require('@mojaloop/event-sdk')
const LibUtil = require('../lib/util')
const BulkQuotesModel = require('../model/bulkQuotes')

/**
* Operations on /bulkQuotes
Expand All @@ -45,7 +51,40 @@ module.exports = {
* produces: application/json
* responses: 202, 400, 401, 403, 404, 405, 406, 501, 503
*/
post: function BulkQuotes (context, request, h) {
throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.NOT_IMPLEMENTED, 'Bulk quotes not implemented')
post: async function BulkQuotes (context, request, h) {
// log request
request.server.log(['info'], `got a POST /bulkQuotes request: ${util.inspect(request.payload)}`)

// instantiate a new quote model
const model = new BulkQuotesModel({
db: request.server.app.database,
requestId: request.info.id
})

// extract some things from the request we may need if we have to deal with an error e.g. the
// originator and quoteId
const bulkQuoteId = request.payload.bulkQuoteId
const fspiopSource = request.headers[Enum.Http.Headers.FSPIOP.SOURCE]

const span = request.span
try {
const spanTags = LibUtil.getSpanTags(request, Enum.Events.Event.Type.BULK_QUOTE, Enum.Events.Event.Action.PREPARE)
span.setTags(spanTags)
await span.audit({
headers: request.headers,
payload: request.payload
}, EventSdk.AuditEventAction.start)

// call the quote request handler in the model
model.handleBulkQuoteRequest(request.headers, request.payload, span)
} catch (err) {
// something went wrong, use the model to handle the error in a sensible way
request.server.log(['error'], `ERROR - POST /bulkQuotes: ${LibUtil.getStackOrInspect(err)}`)
const fspiopError = ErrorHandler.ReformatFSPIOPError(err)
model.handleException(fspiopSource, bulkQuoteId, fspiopError, request.headers, span)
} finally {
// eslint-disable-next-line no-unsafe-finally
return h.response().code(Enum.Http.ReturnCodes.ACCEPTED.CODE)
}
}
}
83 changes: 75 additions & 8 deletions src/handlers/bulkQuotes/{id}.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,97 @@

'use strict'

const ErrorHandler = require('@mojaloop/central-services-error-handling')

const util = require('util')
const Enum = require('@mojaloop/central-services-shared').Enum
const EventSdk = require('@mojaloop/event-sdk')
const LibUtil = require('../../lib/util')
const BulkQuotesModel = require('../../model/bulkQuotes.js')
/**
* Operations on /bulkQuotes/{id}
*/
module.exports = {
/**
* summary: BulkQuotesById
* summary: getBulkQuotesById
* description: The HTTP request GET /bulkQuotes/&lt;id&gt; is used to get information regarding an earlier created or requested bulk quote. The &lt;id&gt; in the URI should contain the bulkQuoteId that was used for the creation of the bulk quote.
* parameters: Accept
* produces: application/json
* responses: 202, 400, 401, 403, 404, 405, 406, 501, 503
*/
get: function BulkQuotesById (context, request, h) {
throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.NOT_IMPLEMENTED, 'Bulk quotes not implemented')
get: async function getBulkQuotesById (context, request, h) {
// log request
request.server.log(['info'], `got a GET /bulkQuotes/{id} request for bulkQuoteId ${request.params.id}`)

// instantiate a new quote model
const model = new BulkQuotesModel({
db: request.server.app.database,
requestId: request.info.id
})

// extract some things from the request we may need if we have to deal with an error e.g. the
// originator and quoteId
const bulkQuoteId = request.params.id
const fspiopSource = request.headers[Enum.Http.Headers.FSPIOP.SOURCE]

const span = request.span
try {
const spanTags = LibUtil.getSpanTags(request, Enum.Events.Event.Type.BULK_QUOTE, Enum.Events.Event.Action.GET)
span.setTags(spanTags)
await span.audit({
headers: request.headers,
payload: request.payload
}, EventSdk.AuditEventAction.start)
// call the model to re-forward the quote update to the correct party
// note that we do not check if our caller is the correct party, but we
// will send the callback to the correct party regardless.
model.handleBulkQuoteGet(request.headers, bulkQuoteId, span)
} catch (err) {
// something went wrong, use the model to handle the error in a sensible way
request.server.log(['error'], `ERROR - GET /bulkQuotes/{id}: ${LibUtil.getStackOrInspect(err)}`)
model.handleException(fspiopSource, bulkQuoteId, err, request.headers, span)
} finally {
// eslint-disable-next-line no-unsafe-finally
return h.response().code(Enum.Http.ReturnCodes.ACCEPTED.CODE)
}
},
/**
* summary: BulkQuotesById
* summary: putBulkQuotesById
* description: The callback PUT /bulkQuotes/&lt;id&gt; is used to inform the client of a requested or created bulk quote. The &lt;id&gt; in the URI should contain the bulkQuoteId that was used for the creation of the bulk quote, or the &lt;id&gt; that was used in the GET /bulkQuotes/&lt;id&gt;.
* parameters: body, Content-Length
* produces: application/json
* responses: 200, 400, 401, 403, 404, 405, 406, 501, 503
*/
put: function BulkQuotesById1 (context, request, h) {
throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.NOT_IMPLEMENTED, 'Bulk quotes not implemented')
put: async function putBulkQuotesById (context, request, h) {
// log request
request.server.log(['info'], `got a PUT /bulkQuotes/{id} request: ${util.inspect(request.payload)}`)

// instantiate a new quote model
const model = new BulkQuotesModel({
db: request.server.app.database,
requestId: request.info.id
})

// extract some things from the request we may need if we have to deal with an error e.g. the
// originator and quoteId
const bulkQuoteId = request.params.id
const fspiopSource = request.headers[Enum.Http.Headers.FSPIOP.SOURCE]

const span = request.span
try {
const spanTags = LibUtil.getSpanTags(request, Enum.Events.Event.Type.BULK_QUOTE, Enum.Events.Event.Action.FULFIL)
span.setTags(spanTags)
await span.audit({
headers: request.headers,
payload: request.payload
}, EventSdk.AuditEventAction.start)
// call the quote update handler in the model
model.handleBulkQuoteUpdate(request.headers, bulkQuoteId, request.payload, span)
} catch (err) {
// something went wrong, use the model to handle the error in a sensible way
request.server.log(['error'], `ERROR - PUT /bulkQuotes/{id}: ${LibUtil.getStackOrInspect(err)}`)
model.handleException(fspiopSource, bulkQuoteId, err, request.headers, span)
} finally {
// eslint-disable-next-line no-unsafe-finally
return h.response().code(Enum.Http.ReturnCodes.OK.CODE)
}
}
}
42 changes: 38 additions & 4 deletions src/handlers/bulkQuotes/{id}/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,11 @@

'use strict'

const ErrorHandler = require('@mojaloop/central-services-error-handling')

const util = require('util')
const Enum = require('@mojaloop/central-services-shared').Enum
const EventSdk = require('@mojaloop/event-sdk')
const LibUtil = require('../../../lib/util')
const BulkQuotesModel = require('../../../model/bulkQuotes')
/**
* Operations on /bulkQuotes/{id}/error
*/
Expand All @@ -45,7 +48,38 @@ module.exports = {
* produces: application/json
* responses: 200, 400, 401, 403, 404, 405, 406, 501, 503
*/
put: function BulkQuotesErrorById (context, request, h) {
throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.NOT_IMPLEMENTED, 'Bulk quotes not implemented')
put: async function BulkQuotesErrorById (context, request, h) {
// log request
request.server.log(['info'], `got a PUT /bulkQuotes/{id}/error request: ${util.inspect(request.payload)}`)

// instantiate a new quote model
const model = new BulkQuotesModel({
db: request.server.app.database,
requestId: request.info.id
})

// extract some things from the request we may need if we have to deal with an error e.g. the
// originator and quoteId
const bulkQuoteId = request.params.id
const fspiopSource = request.headers[Enum.Http.Headers.FSPIOP.SOURCE]

const span = request.span
try {
const spanTags = LibUtil.getSpanTags(request, Enum.Events.Event.Type.BULK_QUOTE, Enum.Events.Event.Action.ABORT)
span.setTags(spanTags)
await span.audit({
headers: request.headers,
payload: request.payload
}, EventSdk.AuditEventAction.start)
// call the quote error handler in the model
model.handleBulkQuoteError(request.headers, bulkQuoteId, request.payload.errorInformation, span)
} catch (err) {
// something went wrong, use the model to handle the error in a sensible way
request.server.log(['error'], `ERROR - PUT /bulkQuotes/{id}/error: ${LibUtil.getStackOrInspect(err)}`)
model.handleException(fspiopSource, bulkQuoteId, err, request.headers)
} finally {
// eslint-disable-next-line no-unsafe-finally
return h.response().code(Enum.Http.ReturnCodes.OK.CODE)
}
}
}
92 changes: 91 additions & 1 deletion src/lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
'use strict'

const util = require('util')
const crypto = require('crypto')
const Enum = require('@mojaloop/central-services-shared').Enum
const Logger = require('@mojaloop/central-services-logger')

Expand Down Expand Up @@ -92,9 +93,98 @@ function getSafe (path, obj) {
return path.reduce((xs, x) => (xs && xs[x]) ? xs[x] : undefined, obj)
}

/**
* Utility function to remove null and undefined keys from an object.
* This is useful for removing "nulls" that come back from database queries
* when projecting into API spec objects
*
* @returns {object}
*/
function removeEmptyKeys (originalObject) {
const obj = { ...originalObject }
Object.keys(obj).forEach(key => {
if (obj[key] && typeof obj[key] === 'object') {
if (Object.keys(obj[key]).length < 1) {
// remove empty object
delete obj[key]
} else {
// recurse
obj[key] = removeEmptyKeys(obj[key])
}
} else if (obj[key] == null) {
// null or undefined, remove it
delete obj[key]
}
})
return obj
}

/**
* Generates and returns an object containing API spec compliant HTTP request headers
*
* @returns {object}
*/
function generateRequestHeaders (headers, noAccept) {
const ret = {
'Content-Type': headers['content-type'] || headers['Content-Type'],
Date: headers.date,
'FSPIOP-Source': headers['fspiop-source'],
'FSPIOP-Destination': headers['fspiop-destination'],
'FSPIOP-HTTP-Method': headers['fspiop-http-method'],
'FSPIOP-Signature': headers['fspiop-signature'],
'FSPIOP-URI': headers['fspiop-uri'],
Accept: null
}

if (!noAccept) {
ret.Accept = headers.accept || headers.Accept
}

return removeEmptyKeys(ret)
}

/**
* Generates and returns an object containing API spec compliant lowercase HTTP request headers for JWS Signing
*
* @returns {object}
*/
function generateRequestHeadersForJWS (headers, noAccept) {
const ret = {
'Content-Type': headers['content-type'] || headers['Content-Type'],
date: headers.date,
'fspiop-source': headers['fspiop-source'],
'fspiop-destination': headers['fspiop-destination'],
'fspiop-http-method': headers['fspiop-http-method'],
'fspiop-signature': headers['fspiop-signature'],
'fspiop-uri': headers['fspiop-uri'],
Accept: null
}

if (!noAccept) {
ret.Accept = headers.accept || headers.Accept
}

return removeEmptyKeys(ret)
}

/**
* Returns the SHA-256 hash of the supplied request object
*
* @returns {undefined}
*/
function calculateRequestHash (request) {
// calculate a SHA-256 of the request
const requestStr = JSON.stringify(request)
return crypto.createHash('sha256').update(requestStr).digest('hex')
}

module.exports = {
failActionHandler,
getSafe,
getSpanTags,
getStackOrInspect
getStackOrInspect,
generateRequestHeaders,
generateRequestHeadersForJWS,
calculateRequestHash,
removeEmptyKeys
}
Loading