From 80beb3f04fc106903a88b01ea9aa78b5f45fff0c Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Wed, 18 Dec 2024 13:08:55 +0000 Subject: [PATCH] refactor(csi-944): refactor code for maintainability (#379) --- docker-compose.yml | 1 - scripts/_wait4_all.js | 2 - src/api/bulkQuotes.js | 12 +- src/api/bulkQuotes/{id}.js | 24 +-- src/api/quotes.js | 16 +- src/api/quotes/{id}.js | 36 ++--- src/constants.js | 1 - src/data/bulkQuotes.js | 70 +++++---- src/data/bulkQuotes/{id}.js | 136 ++++++++-------- src/data/bulkQuotes/{id}/error.js | 68 ++++---- src/data/cachedDatabase.js | 26 ++-- src/data/database.js | 250 +++++++++++++++--------------- src/data/quotes.js | 68 ++++---- src/data/quotes/{id}.js | 138 +++++++++-------- src/data/quotes/{id}/error.js | 70 +++++---- src/lib/http.js | 3 - src/lib/util.js | 132 +++++++++------- src/model/fxQuotes.js | 111 ++++++------- src/model/index.js | 1 - src/model/quotes.js | 12 -- src/server.js | 13 -- test/unit/lib/util.test.js | 2 +- 22 files changed, 586 insertions(+), 606 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index c40f1391..3ab02a7c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -90,7 +90,6 @@ services: volumes: # override the default values with our own - this is because the KAFKA_HOST env variable is ignored for some reason - ./docker/ml-api-adapter/default.json:/opt/app/config/default.json - # TODO: we should be able to remove this - I think the image command is broken command: - "node" - "src/api/index.js" diff --git a/scripts/_wait4_all.js b/scripts/_wait4_all.js index b608d33a..f0e81a12 100644 --- a/scripts/_wait4_all.js +++ b/scripts/_wait4_all.js @@ -88,8 +88,6 @@ async function updateServiceStatus (waitingMap) { const startingServices = getServicesForStatus(waitingMap, 'starting') Promise.all(startingServices.map(async serviceName => { - // TODO: This info may be useful in future! - // const currentStatus = waitingMap[serviceName] const progress = await getProgress(serviceName) waitingMap[serviceName] = progress })) diff --git a/src/api/bulkQuotes.js b/src/api/bulkQuotes.js index da9c6f73..6554fa98 100644 --- a/src/api/bulkQuotes.js +++ b/src/api/bulkQuotes.js @@ -46,12 +46,12 @@ const { kafkaConfig } = new Config() */ module.exports = { /** - * summary: BulkQuotes - * description: The HTTP request POST /bulkQuotes is used to request the creation of a bulk quote for the provided financial transactions in the server. - * parameters: body, Accept, Content-Length, Content-Type, Date, X-Forwarded-For, FSPIOP-Source, FSPIOP-Destination, FSPIOP-Encryption, FSPIOP-Signature, FSPIOP-URI, FSPIOP-HTTP-Method - * produces: application/json - * responses: 202, 400, 401, 403, 404, 405, 406, 501, 503 - */ + * summary: BulkQuotes + * description: The HTTP request POST /bulkQuotes is used to request the creation of a bulk quote for the provided financial transactions in the server. + * parameters: body, Accept, Content-Length, Content-Type, Date, X-Forwarded-For, FSPIOP-Source, FSPIOP-Destination, FSPIOP-Encryption, FSPIOP-Signature, FSPIOP-URI, FSPIOP-HTTP-Method + * produces: application/json + * responses: 202, 400, 401, 403, 404, 405, 406, 501, 503 + */ post: async function BulkQuotes (context, request, h) { const histTimerEnd = Metrics.getHistogram( 'bulkQuotes_post', diff --git a/src/api/bulkQuotes/{id}.js b/src/api/bulkQuotes/{id}.js index a607839e..0d81cd84 100644 --- a/src/api/bulkQuotes/{id}.js +++ b/src/api/bulkQuotes/{id}.js @@ -45,12 +45,12 @@ const { kafkaConfig } = new Config() */ module.exports = { /** - * summary: getBulkQuotesById - * description: The HTTP request GET /bulkQuotes/<id> is used to get information regarding an earlier created or requested bulk quote. The <id> 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 - */ + * summary: getBulkQuotesById + * description: The HTTP request GET /bulkQuotes/<id> is used to get information regarding an earlier created or requested bulk quote. The <id> 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: async function getBulkQuotesById (context, request, h) { const histTimerEnd = Metrics.getHistogram( 'bulkQuotes_id_get', @@ -75,12 +75,12 @@ module.exports = { } }, /** - * summary: putBulkQuotesById - * description: The callback PUT /bulkQuotes/<id> is used to inform the client of a requested or created bulk quote. The <id> in the URI should contain the bulkQuoteId that was used for the creation of the bulk quote, or the <id> that was used in the GET /bulkQuotes/<id>. - * parameters: body, Content-Length - * produces: application/json - * responses: 200, 400, 401, 403, 404, 405, 406, 501, 503 - */ + * summary: putBulkQuotesById + * description: The callback PUT /bulkQuotes/<id> is used to inform the client of a requested or created bulk quote. The <id> in the URI should contain the bulkQuoteId that was used for the creation of the bulk quote, or the <id> that was used in the GET /bulkQuotes/<id>. + * parameters: body, Content-Length + * produces: application/json + * responses: 200, 400, 401, 403, 404, 405, 406, 501, 503 + */ put: async function putBulkQuotesById (context, request, h) { const histTimerEnd = Metrics.getHistogram( 'bulkQuotes_id_put', diff --git a/src/api/quotes.js b/src/api/quotes.js index ed1e9895..93f41f96 100644 --- a/src/api/quotes.js +++ b/src/api/quotes.js @@ -46,14 +46,14 @@ const { kafkaConfig } = new Config() */ module.exports = { /** - * summary: Quotes - * description: - * - The HTTP request POST /quotes is used to request the creation of a quote for the provided financial transaction in the server. - * - The HTTP request `POST /fxQuotes` is used to ask an FXP to provide a quotation for a currency conversion. - * parameters: body, Accept, Content-Length, Content-Type, Date, X-Forwarded-For, FSPIOP-Source, FSPIOP-Destination, FSPIOP-Encryption, FSPIOP-Signature, FSPIOP-URI, FSPIOP-HTTP-Method - * produces: application/json - * responses: 202, 400, 401, 403, 404, 405, 406, 501, 503 - */ + * summary: Quotes + * description: + * - The HTTP request POST /quotes is used to request the creation of a quote for the provided financial transaction in the server. + * - The HTTP request `POST /fxQuotes` is used to ask an FXP to provide a quotation for a currency conversion. + * parameters: body, Accept, Content-Length, Content-Type, Date, X-Forwarded-For, FSPIOP-Source, FSPIOP-Destination, FSPIOP-Encryption, FSPIOP-Signature, FSPIOP-URI, FSPIOP-HTTP-Method + * produces: application/json + * responses: 202, 400, 401, 403, 404, 405, 406, 501, 503 + */ post: async function Quotes (context, request, h) { const isFX = !!request.payload.conversionRequestId diff --git a/src/api/quotes/{id}.js b/src/api/quotes/{id}.js index 85805b15..0c12c0d9 100644 --- a/src/api/quotes/{id}.js +++ b/src/api/quotes/{id}.js @@ -46,14 +46,14 @@ const { kafkaConfig } = new Config() */ module.exports = { /** - * summary: QuotesById - * description: - * - The HTTP request GET /quotes/<id> is used to get information regarding an earlier created or requested quote. The <id> in the URI should contain the quoteId that was used for the creation of the quote. - * - The HTTP request `GET /fxQuotes/{ID}` is used to request information regarding a request for quotation for a currency conversion which the sender has previously issued. The `{ID}` in the URI should contain the `conversionRequestId` that was used for the creation of the quote. - * parameters: Accept - * produces: application/json - * responses: 202, 400, 401, 403, 404, 405, 406, 501, 503 - */ + * summary: QuotesById + * description: + * - The HTTP request GET /quotes/<id> is used to get information regarding an earlier created or requested quote. The <id> in the URI should contain the quoteId that was used for the creation of the quote. + * - The HTTP request `GET /fxQuotes/{ID}` is used to request information regarding a request for quotation for a currency conversion which the sender has previously issued. The `{ID}` in the URI should contain the `conversionRequestId` that was used for the creation of the quote. + * parameters: Accept + * produces: application/json + * responses: 202, 400, 401, 403, 404, 405, 406, 501, 503 + */ get: async function getQuotesById (context, request, h) { const isFX = request.headers['content-type'].includes('fxQuotes') @@ -84,16 +84,16 @@ module.exports = { }, /** - * summary: QuotesById and QuotesByIdAndError - * description: - * - The callback PUT /quotes/<id> is used to inform the client of a requested or created quote. The <id> in the URI should contain the quoteId that was used for the creation of the quote, or the <id> that was used in the GET /quotes/<id>GET /quotes/<id>, - * - The callback `PUT /fxQuotes/{ID}` is used to inform the requester about the outcome of a request for quotation for a currency conversion. The `{ID}` in the URI should contain the `conversionRequestId` that was used for the creation of the FX quote, or the `{ID}` that was used in the `GET /fxQuotes/{ID}` request. - * - If the server is unable to find or create a quote, or some other processing error occurs, the error callback PUT /quotes/<id>/error is used. The <id> in the URI should contain the quoteId that was used for the creation of the quote, or the <id> that was used in the GET /quotes/<id>. - * - If the FXP is unable to find or create a FX quote, or some other processing error occurs, the error callback `PUT /fxQuotes/{ID}/error` is used. The `{ID}` in the URI should contain the `conversionRequestId` that was used for the creation of the FX quote, or the `{ID}` that was used in the `GET /fxQuotes/{ID}` request. - * parameters: body, Content-Length - * produces: application/json - * responses: 200, 400, 401, 403, 404, 405, 406, 501, 503 - */ + * summary: QuotesById and QuotesByIdAndError + * description: + * - The callback PUT /quotes/<id> is used to inform the client of a requested or created quote. The <id> in the URI should contain the quoteId that was used for the creation of the quote, or the <id> that was used in the GET /quotes/<id>GET /quotes/<id>, + * - The callback `PUT /fxQuotes/{ID}` is used to inform the requester about the outcome of a request for quotation for a currency conversion. The `{ID}` in the URI should contain the `conversionRequestId` that was used for the creation of the FX quote, or the `{ID}` that was used in the `GET /fxQuotes/{ID}` request. + * - If the server is unable to find or create a quote, or some other processing error occurs, the error callback PUT /quotes/<id>/error is used. The <id> in the URI should contain the quoteId that was used for the creation of the quote, or the <id> that was used in the GET /quotes/<id>. + * - If the FXP is unable to find or create a FX quote, or some other processing error occurs, the error callback `PUT /fxQuotes/{ID}/error` is used. The `{ID}` in the URI should contain the `conversionRequestId` that was used for the creation of the FX quote, or the `{ID}` that was used in the `GET /fxQuotes/{ID}` request. + * parameters: body, Content-Length + * produces: application/json + * responses: 200, 400, 401, 403, 404, 405, 406, 501, 503 + */ put: async function putQuotesById (context, request, h) { const isFX = request.headers['content-type'].includes('fxQuotes') const isError = !!request.payload.errorInformation diff --git a/src/constants.js b/src/constants.js index f9b85bab..51805cb0 100644 --- a/src/constants.js +++ b/src/constants.js @@ -13,7 +13,6 @@ const HEADERS = Object.freeze({ fspiopSignature: 'FSPIOP-Signature', fspiopUri: 'FSPIOP-URI' }) -// todo: think, if it's better to use all headers keys in lowercase const ERROR_MESSAGES = { CALLBACK_UNSUCCESSFUL_HTTP_RESPONSE: 'Got non-success response sending error callback', diff --git a/src/data/bulkQuotes.js b/src/data/bulkQuotes.js index 30621c99..9ce253fa 100644 --- a/src/data/bulkQuotes.js +++ b/src/data/bulkQuotes.js @@ -33,25 +33,27 @@ /* istanbul ignore file */ 'use strict' + const Mockgen = require('../../test/util/mockgen.js') + /** * Operations on /bulkQuotes */ module.exports = { /** - * summary: BulkQuotes - * description: The HTTP request POST /bulkQuotes is used to request the creation of a bulk quote for the provided financial transactions in the server. - * parameters: body, Accept, Content-Length, Content-Type, Date, X-Forwarded-For, FSPIOP-Source, FSPIOP-Destination, FSPIOP-Encryption, FSPIOP-Signature, FSPIOP-URI, FSPIOP-HTTP-Method - * produces: application/json - * responses: 202, 400, 401, 403, 404, 405, 406, 501, 503 - * operationId: BulkQuotes - */ + * summary: BulkQuotes + * description: The HTTP request POST /bulkQuotes is used to request the creation of a bulk quote for the provided financial transactions in the server. + * parameters: body, Accept, Content-Length, Content-Type, Date, X-Forwarded-For, FSPIOP-Source, FSPIOP-Destination, FSPIOP-Encryption, FSPIOP-Signature, FSPIOP-URI, FSPIOP-HTTP-Method + * produces: application/json + * responses: 202, 400, 401, 403, 404, 405, 406, 501, 503 + * operationId: BulkQuotes + */ post: { 202: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes', operation: 'post', @@ -60,9 +62,9 @@ module.exports = { }, 400: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes', operation: 'post', @@ -71,9 +73,9 @@ module.exports = { }, 401: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes', operation: 'post', @@ -82,9 +84,9 @@ module.exports = { }, 403: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes', operation: 'post', @@ -93,9 +95,9 @@ module.exports = { }, 404: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes', operation: 'post', @@ -104,9 +106,9 @@ module.exports = { }, 405: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes', operation: 'post', @@ -115,9 +117,9 @@ module.exports = { }, 406: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes', operation: 'post', @@ -126,9 +128,9 @@ module.exports = { }, 501: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes', operation: 'post', @@ -137,9 +139,9 @@ module.exports = { }, 503: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes', operation: 'post', diff --git a/src/data/bulkQuotes/{id}.js b/src/data/bulkQuotes/{id}.js index 52b805f4..f9dc63ef 100644 --- a/src/data/bulkQuotes/{id}.js +++ b/src/data/bulkQuotes/{id}.js @@ -39,19 +39,19 @@ const Mockgen = require('../../../test/util/mockgen.js') */ module.exports = { /** - * summary: BulkQuotesById - * description: The HTTP request GET /bulkQuotes/<id> is used to get information regarding an earlier created or requested bulk quote. The <id> 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 - * operationId: BulkQuotesById - */ + * summary: BulkQuotesById + * description: The HTTP request GET /bulkQuotes/<id> is used to get information regarding an earlier created or requested bulk quote. The <id> 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 + * operationId: BulkQuotesById + */ get: { 202: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}', operation: 'get', @@ -60,9 +60,9 @@ module.exports = { }, 400: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}', operation: 'get', @@ -71,9 +71,9 @@ module.exports = { }, 401: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}', operation: 'get', @@ -82,9 +82,9 @@ module.exports = { }, 403: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}', operation: 'get', @@ -93,9 +93,9 @@ module.exports = { }, 404: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}', operation: 'get', @@ -104,9 +104,9 @@ module.exports = { }, 405: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}', operation: 'get', @@ -115,9 +115,9 @@ module.exports = { }, 406: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}', operation: 'get', @@ -126,9 +126,9 @@ module.exports = { }, 501: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}', operation: 'get', @@ -137,9 +137,9 @@ module.exports = { }, 503: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}', operation: 'get', @@ -148,19 +148,19 @@ module.exports = { } }, /** - * summary: BulkQuotesById - * description: The callback PUT /bulkQuotes/<id> is used to inform the client of a requested or created bulk quote. The <id> in the URI should contain the bulkQuoteId that was used for the creation of the bulk quote, or the <id> that was used in the GET /bulkQuotes/<id>. - * parameters: body, Content-Length - * produces: application/json - * responses: 200, 400, 401, 403, 404, 405, 406, 501, 503 - * operationId: BulkQuotesById1 - */ + * summary: BulkQuotesById + * description: The callback PUT /bulkQuotes/<id> is used to inform the client of a requested or created bulk quote. The <id> in the URI should contain the bulkQuoteId that was used for the creation of the bulk quote, or the <id> that was used in the GET /bulkQuotes/<id>. + * parameters: body, Content-Length + * produces: application/json + * responses: 200, 400, 401, 403, 404, 405, 406, 501, 503 + * operationId: BulkQuotesById1 + */ put: { 200: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}', operation: 'put', @@ -169,9 +169,9 @@ module.exports = { }, 400: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}', operation: 'put', @@ -180,9 +180,9 @@ module.exports = { }, 401: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}', operation: 'put', @@ -191,9 +191,9 @@ module.exports = { }, 403: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}', operation: 'put', @@ -202,9 +202,9 @@ module.exports = { }, 404: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}', operation: 'put', @@ -213,9 +213,9 @@ module.exports = { }, 405: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}', operation: 'put', @@ -224,9 +224,9 @@ module.exports = { }, 406: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}', operation: 'put', @@ -235,9 +235,9 @@ module.exports = { }, 501: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}', operation: 'put', @@ -246,9 +246,9 @@ module.exports = { }, 503: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}', operation: 'put', diff --git a/src/data/bulkQuotes/{id}/error.js b/src/data/bulkQuotes/{id}/error.js index 63990727..a7509800 100644 --- a/src/data/bulkQuotes/{id}/error.js +++ b/src/data/bulkQuotes/{id}/error.js @@ -39,19 +39,19 @@ const Mockgen = require('../../../../test/util/mockgen.js') */ module.exports = { /** - * summary: BulkQuotesErrorById - * description: If the server is unable to find or create a bulk quote, or another processing error occurs, the error callback PUT /bulkQuotes/<id>/error is used. The <id> in the URI should contain the bulkQuoteId that was used for the creation of the bulk quote, or the <id> that was used in the GET /bulkQuotes/<id>. - * parameters: id, body, Content-Length, Content-Type, Date, X-Forwarded-For, FSPIOP-Source, FSPIOP-Destination, FSPIOP-Encryption, FSPIOP-Signature, FSPIOP-URI, FSPIOP-HTTP-Method - * produces: application/json - * responses: 200, 400, 401, 403, 404, 405, 406, 501, 503 - * operationId: BulkQuotesErrorById - */ + * summary: BulkQuotesErrorById + * description: If the server is unable to find or create a bulk quote, or another processing error occurs, the error callback PUT /bulkQuotes/<id>/error is used. The <id> in the URI should contain the bulkQuoteId that was used for the creation of the bulk quote, or the <id> that was used in the GET /bulkQuotes/<id>. + * parameters: id, body, Content-Length, Content-Type, Date, X-Forwarded-For, FSPIOP-Source, FSPIOP-Destination, FSPIOP-Encryption, FSPIOP-Signature, FSPIOP-URI, FSPIOP-HTTP-Method + * produces: application/json + * responses: 200, 400, 401, 403, 404, 405, 406, 501, 503 + * operationId: BulkQuotesErrorById + */ put: { 200: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}/error', operation: 'put', @@ -60,9 +60,9 @@ module.exports = { }, 400: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}/error', operation: 'put', @@ -71,9 +71,9 @@ module.exports = { }, 401: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}/error', operation: 'put', @@ -82,9 +82,9 @@ module.exports = { }, 403: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}/error', operation: 'put', @@ -93,9 +93,9 @@ module.exports = { }, 404: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}/error', operation: 'put', @@ -104,9 +104,9 @@ module.exports = { }, 405: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}/error', operation: 'put', @@ -115,9 +115,9 @@ module.exports = { }, 406: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}/error', operation: 'put', @@ -126,9 +126,9 @@ module.exports = { }, 501: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}/error', operation: 'put', @@ -137,9 +137,9 @@ module.exports = { }, 503: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/bulkQuotes/{id}/error', operation: 'put', diff --git a/src/data/cachedDatabase.js b/src/data/cachedDatabase.js index dcf7b30b..e2169e95 100644 --- a/src/data/cachedDatabase.js +++ b/src/data/cachedDatabase.js @@ -49,7 +49,7 @@ class CachedDatabase extends Database { this.cache = new Cache() } - /* + /** * The following enum lookup functions override those in the superclass with * versions that use an in-memory cache. */ @@ -140,30 +140,30 @@ class CachedDatabase extends Database { } /** - * Adds or replaces a value in the cache. - * - * @returns {undefined} - */ + * Adds or replaces a value in the cache. + * + * @returns {undefined} + */ cachePut (type, params, value, ttl) { const key = this.getCacheKey(type, params) this.cache.put(key, value, ttl) } /** - * Gets a value from the cache, or null if it is not present - * - * @returns {undefined} - */ + * Gets a value from the cache, or null if it is not present + * + * @returns {undefined} + */ cacheGet (type, params) { const key = this.getCacheKey(type, params) return this.cache.get(key) } /** - * Calculates a cache key for the given type and parameters - * - * @returns {undefined} - */ + * Calculates a cache key for the given type and parameters + * + * @returns {undefined} + */ getCacheKey (type, params) { return `${type}_${params.join('__')}` } diff --git a/src/data/database.js b/src/data/database.js index bd635220..6d00413b 100644 --- a/src/data/database.js +++ b/src/data/database.js @@ -55,10 +55,10 @@ class Database { } /** - * Connects to the database and returns a self reference - * - * @returns {promise} - */ + * Connects to the database and returns a self reference + * + * @returns {promise} + */ async connect () { this.queryBuilder = new Knex(this.config.database) @@ -70,10 +70,10 @@ class Database { } /** - * async utility for getting a transaction object from knex - * - * @returns {undefined} - */ + * async utility for getting a transaction object from knex + * + * @returns {undefined} + */ async newTransaction () { return new Promise((resolve, reject) => { try { @@ -87,10 +87,10 @@ class Database { } /** - * Check whether the database connection has basic functionality - * - * @returns {boolean} - */ + * Check whether the database connection has basic functionality + * + * @returns {boolean} + */ async isConnected () { try { const result = await this.queryBuilder.raw('SELECT 1 + 1 AS result') @@ -104,10 +104,10 @@ class Database { } /** - * Gets the id of the specified transaction initiator type - * - * @returns {promise} - id of the transactionInitiatorType - */ + * Gets the id of the specified transaction initiator type + * + * @returns {promise} - id of the transactionInitiatorType + */ async getInitiatorType (initiatorType) { try { const rows = await this.queryBuilder('transactionInitiatorType') @@ -126,10 +126,10 @@ class Database { } /** - * Gets the id of the specified transaction initiator - * - * @returns {promise} - id of the transactionInitiator - */ + * Gets the id of the specified transaction initiator + * + * @returns {promise} - id of the transactionInitiator + */ async getInitiator (initiator) { try { const rows = await this.queryBuilder('transactionInitiator') @@ -148,10 +148,10 @@ class Database { } /** - * Gets the id of the specified transaction scenario - * - * @returns {promise} - id of the transactionScenario - */ + * Gets the id of the specified transaction scenario + * + * @returns {promise} - id of the transactionScenario + */ async getScenario (scenario) { try { const rows = await this.queryBuilder('transactionScenario') @@ -170,10 +170,10 @@ class Database { } /** - * Gets the id of the specified transaction sub-scenario - * - * @returns {promise} - id of the transactionSubScenario - */ + * Gets the id of the specified transaction sub-scenario + * + * @returns {promise} - id of the transactionSubScenario + */ async getSubScenario (subScenario) { try { const rows = await this.queryBuilder('transactionSubScenario') @@ -192,10 +192,10 @@ class Database { } /** - * Gets the id of the specified amount type - * - * @returns {promise} - id of the amountType - */ + * Gets the id of the specified amount type + * + * @returns {promise} - id of the amountType + */ async getAmountType (amountType) { try { const rows = await this.queryBuilder('amountType') @@ -214,10 +214,10 @@ class Database { } /** - * Creates a transaction reference in the database - * - * @returns {promise} - */ + * Creates a transaction reference in the database + * + * @returns {promise} + */ async createTransactionReference (txn, quoteId, transactionReferenceId) { try { await this.queryBuilder('transactionReference') @@ -236,10 +236,10 @@ class Database { } /** - * Creates an entry in quoteDuplicateCheck - * - * @returns {promise} - quoteId - */ + * Creates an entry in quoteDuplicateCheck + * + * @returns {promise} - quoteId + */ async createQuoteDuplicateCheck (txn, quoteId, hash) { try { await this.queryBuilder('quoteDuplicateCheck') @@ -258,10 +258,10 @@ class Database { } /** - * Creates an entry in quoteResponseDuplicateCheck - * - * @returns {promise} - quoteResponseId - */ + * Creates an entry in quoteResponseDuplicateCheck + * + * @returns {promise} - quoteResponseId + */ async createQuoteUpdateDuplicateCheck (txn, quoteId, quoteResponseId, hash) { try { await this.queryBuilder('quoteResponseDuplicateCheck') @@ -281,10 +281,10 @@ class Database { } /** - * Gets the id of the specified party type - * - * @returns {promise} - id of the partyType - */ + * Gets the id of the specified party type + * + * @returns {promise} - id of the partyType + */ async getPartyType (partyType) { try { const rows = await this.queryBuilder('partyType') @@ -304,10 +304,10 @@ class Database { } /** - * Gets the id of the specified party identifier type - * - * @returns {promise} - id of the partyIdentifierType - */ + * Gets the id of the specified party identifier type + * + * @returns {promise} - id of the partyIdentifierType + */ async getPartyIdentifierType (partyIdentifierType) { try { const rows = await this.queryBuilder('partyIdentifierType') @@ -327,10 +327,10 @@ class Database { } /** - * Gets the id of the specified participant - * - * @returns {promise} - id of the participant - */ + * Gets the id of the specified participant + * + * @returns {promise} - id of the participant + */ async getParticipant (participantName, participantType, currencyId, ledgerAccountTypeId = Enum.Accounts.LedgerAccountType.POSITION) { try { const rows = await this.queryBuilder('participant') @@ -364,10 +364,10 @@ class Database { } /** - * Gets the id of the specified participant name - * - * @returns {promise} - id of the participant - */ + * Gets the id of the specified participant name + * + * @returns {promise} - id of the participant + */ async getParticipantByName (participantName, participantType) { try { const rows = await this.queryBuilder('participant') @@ -396,10 +396,10 @@ class Database { } /** - * Gets the id of the specified transfer participant role type - * - * @returns {promise} - id of the transfer participant role type - */ + * Gets the id of the specified transfer participant role type + * + * @returns {promise} - id of the transfer participant role type + */ async getTransferParticipantRoleType (name) { try { const rows = await this.queryBuilder('transferParticipantRoleType') @@ -422,10 +422,10 @@ class Database { } /** - * Gets the id of the specified ledger entry type - * - * @returns {promise} - id of the ledger entry type - */ + * Gets the id of the specified ledger entry type + * + * @returns {promise} - id of the ledger entry type + */ async getLedgerEntryType (name) { try { const rows = await this.queryBuilder('ledgerEntryType') @@ -448,30 +448,30 @@ class Database { } /** - * Creates a payer quote party - * - * @returns {promise} - */ + * Creates a payer quote party + * + * @returns {promise} + */ async createPayerQuoteParty (txn, quoteId, party, amount, currency, enumVals) { // note amount is negative for payee and positive for payer return this.createQuoteParty(txn, quoteId, LOCAL_ENUM.PAYER, party, amount, currency, enumVals) } /** - * Creates a payee quote party - * - * @returns {promise} - */ + * Creates a payee quote party + * + * @returns {promise} + */ async createPayeeQuoteParty (txn, quoteId, party, amount, currency, enumVals) { // note amount is negative for payee and positive for payer return this.createQuoteParty(txn, quoteId, LOCAL_ENUM.PAYEE, party, -amount, currency, enumVals) } /** - * Creates a quote party - * - * @returns {integer} - id of created quoteParty - */ + * Creates a quote party + * + * @returns {integer} - id of created quoteParty + */ async createQuoteParty (txn, quoteId, partyType, party, amount, currency, enumVals) { try { const refs = {} @@ -540,10 +540,10 @@ class Database { } /** - * Creates the specific party and returns its id - * - * @returns {promise} - id of party - */ + * Creates the specific party and returns its id + * + * @returns {promise} - id of party + */ async createParty (txn, quotePartyId, party) { try { const newParty = { @@ -564,10 +564,10 @@ class Database { } /** - * Creates a quote in the database - * - * @returns {promise} - */ + * Creates a quote in the database + * + * @returns {promise} + */ async createQuote (txn, quote) { try { await this.queryBuilder('quote') @@ -615,10 +615,10 @@ class Database { } /** - * Gets the specified party for the specified quote - * - * @returns {object} - */ + * Gets the specified party for the specified quote + * + * @returns {object} + */ async getQuoteParty (quoteId, partyType) { try { const rows = await this.queryBuilder('quoteParty') @@ -667,10 +667,10 @@ class Database { } /** - * Gets the specified endpoint of the specified type for the specified participant - * - * @returns {promise} - resolves to the endpoint base url - */ + * Gets the specified endpoint of the specified type for the specified participant + * + * @returns {promise} - resolves to the endpoint base url + */ async getParticipantEndpoint (participantName, endpointType) { try { const rows = await this.queryBuilder('participantEndpoint') @@ -693,10 +693,10 @@ class Database { } /** - * Gets a quote duplicate check row - * - * @returns {object} - quote duplicate check or null if none found - */ + * Gets a quote duplicate check row + * + * @returns {object} - quote duplicate check or null if none found + */ async getQuoteDuplicateCheck (quoteId) { try { const rows = await this.queryBuilder('quoteDuplicateCheck') @@ -717,10 +717,10 @@ class Database { } /** - * Gets a quote response duplicate check row - * - * @returns {object} - quote duplicate check or null if none found - */ + * Gets a quote response duplicate check row + * + * @returns {object} - quote duplicate check or null if none found + */ async getQuoteResponseDuplicateCheck (quoteId) { try { const rows = await this.queryBuilder('quoteResponseDuplicateCheck') @@ -741,10 +741,10 @@ class Database { } /** - * Creates a quoteResponse object in the database - * - * @returns {object} - created object - */ + * Creates a quoteResponse object in the database + * + * @returns {object} - created object + */ async createQuoteResponse (txn, quoteId, quoteResponse) { try { const newQuoteResponse = { @@ -777,10 +777,10 @@ class Database { } /** - * Creates a new quote response ILP packet row - * - * @returns {object} - */ + * Creates a new quote response ILP packet row + * + * @returns {object} + */ async createQuoteResponseIlpPacket (txn, quoteResponseId, ilpPacket) { try { const newPacket = { @@ -801,10 +801,10 @@ class Database { } /** - * Creates a new geoCode row - * - * @returns {object} - */ + * Creates a new geoCode row + * + * @returns {object} + */ async createGeoCode (txn, geoCode) { try { const newGeoCode = { @@ -828,10 +828,10 @@ class Database { } /** - * Creates a new quoteError row - * - * @returns {object} - */ + * Creates a new quoteError row + * + * @returns {object} + */ async createQuoteError (txn, error) { try { const newError = { @@ -855,11 +855,11 @@ class Database { } /** - * Creates quoteExtensions rows - * - * @returns {object} - * @param {Array[{object}]} extensions - array of extension objects with quoteId, key and value properties - */ + * Creates quoteExtensions rows + * + * @returns {object} + * @param {Array[{object}]} extensions - array of extension objects with quoteId, key and value properties + */ async createQuoteExtensions (txn, extensions, quoteId, transactionId = undefined, quoteResponseId = undefined) { try { const newExtensions = extensions.map(({ key, value }) => ({ diff --git a/src/data/quotes.js b/src/data/quotes.js index 042f1080..c4ecb76a 100644 --- a/src/data/quotes.js +++ b/src/data/quotes.js @@ -41,19 +41,19 @@ const Mockgen = require('../../test/util/mockgen.js') */ module.exports = { /** - * summary: Quotes - * description: The HTTP request POST /quotes is used to request the creation of a quote for the provided financial transaction in the server. - * parameters: body, Accept, Content-Length, Content-Type, Date, X-Forwarded-For, FSPIOP-Source, FSPIOP-Destination, FSPIOP-Encryption, FSPIOP-Signature, FSPIOP-URI, FSPIOP-HTTP-Method - * produces: application/json - * responses: 202, 400, 401, 403, 404, 405, 406, 501, 503 - * operationId: Quotes - */ + * summary: Quotes + * description: The HTTP request POST /quotes is used to request the creation of a quote for the provided financial transaction in the server. + * parameters: body, Accept, Content-Length, Content-Type, Date, X-Forwarded-For, FSPIOP-Source, FSPIOP-Destination, FSPIOP-Encryption, FSPIOP-Signature, FSPIOP-URI, FSPIOP-HTTP-Method + * produces: application/json + * responses: 202, 400, 401, 403, 404, 405, 406, 501, 503 + * operationId: Quotes + */ post: { 202: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes', operation: 'post', @@ -62,9 +62,9 @@ module.exports = { }, 400: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes', operation: 'post', @@ -73,9 +73,9 @@ module.exports = { }, 401: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes', operation: 'post', @@ -84,9 +84,9 @@ module.exports = { }, 403: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes', operation: 'post', @@ -95,9 +95,9 @@ module.exports = { }, 404: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes', operation: 'post', @@ -106,9 +106,9 @@ module.exports = { }, 405: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes', operation: 'post', @@ -117,9 +117,9 @@ module.exports = { }, 406: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes', operation: 'post', @@ -128,9 +128,9 @@ module.exports = { }, 501: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes', operation: 'post', @@ -139,9 +139,9 @@ module.exports = { }, 503: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes', operation: 'post', diff --git a/src/data/quotes/{id}.js b/src/data/quotes/{id}.js index 416068cf..6be388b7 100644 --- a/src/data/quotes/{id}.js +++ b/src/data/quotes/{id}.js @@ -33,25 +33,27 @@ /* istanbul ignore file */ 'use strict' + const Mockgen = require('../../../test/util/mockgen.js') + /** * Operations on /quotes/{id} */ module.exports = { /** - * summary: QuotesById - * description: The HTTP request GET /quotes/<id> is used to get information regarding an earlier created or requested quote. The <id> in the URI should contain the quoteId that was used for the creation of the quote. - * parameters: Accept - * produces: application/json - * responses: 202, 400, 401, 403, 404, 405, 406, 501, 503 - * operationId: QuotesById - */ + * summary: QuotesById + * description: The HTTP request GET /quotes/<id> is used to get information regarding an earlier created or requested quote. The <id> in the URI should contain the quoteId that was used for the creation of the quote. + * parameters: Accept + * produces: application/json + * responses: 202, 400, 401, 403, 404, 405, 406, 501, 503 + * operationId: QuotesById + */ get: { 202: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}', operation: 'get', @@ -60,9 +62,9 @@ module.exports = { }, 400: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}', operation: 'get', @@ -71,9 +73,9 @@ module.exports = { }, 401: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}', operation: 'get', @@ -82,9 +84,9 @@ module.exports = { }, 403: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}', operation: 'get', @@ -93,9 +95,9 @@ module.exports = { }, 404: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}', operation: 'get', @@ -104,9 +106,9 @@ module.exports = { }, 405: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}', operation: 'get', @@ -115,9 +117,9 @@ module.exports = { }, 406: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}', operation: 'get', @@ -126,9 +128,9 @@ module.exports = { }, 501: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}', operation: 'get', @@ -137,9 +139,9 @@ module.exports = { }, 503: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}', operation: 'get', @@ -148,19 +150,19 @@ module.exports = { } }, /** - * summary: QuotesById - * description: The callback PUT /quotes/<id> is used to inform the client of a requested or created quote. The <id> in the URI should contain the quoteId that was used for the creation of the quote, or the <id> that was used in the GET /quotes/<id>GET /quotes/<id>. - * parameters: body, Content-Length - * produces: application/json - * responses: 200, 400, 401, 403, 404, 405, 406, 501, 503 - * operationId: QuotesById1 - */ + * summary: QuotesById + * description: The callback PUT /quotes/<id> is used to inform the client of a requested or created quote. The <id> in the URI should contain the quoteId that was used for the creation of the quote, or the <id> that was used in the GET /quotes/<id>GET /quotes/<id>. + * parameters: body, Content-Length + * produces: application/json + * responses: 200, 400, 401, 403, 404, 405, 406, 501, 503 + * operationId: QuotesById1 + */ put: { 200: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}', operation: 'put', @@ -169,9 +171,9 @@ module.exports = { }, 400: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}', operation: 'put', @@ -180,9 +182,9 @@ module.exports = { }, 401: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}', operation: 'put', @@ -191,9 +193,9 @@ module.exports = { }, 403: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}', operation: 'put', @@ -202,9 +204,9 @@ module.exports = { }, 404: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}', operation: 'put', @@ -213,9 +215,9 @@ module.exports = { }, 405: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}', operation: 'put', @@ -224,9 +226,9 @@ module.exports = { }, 406: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}', operation: 'put', @@ -235,9 +237,9 @@ module.exports = { }, 501: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}', operation: 'put', @@ -246,9 +248,9 @@ module.exports = { }, 503: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}', operation: 'put', diff --git a/src/data/quotes/{id}/error.js b/src/data/quotes/{id}/error.js index fd770bc2..a73fc056 100644 --- a/src/data/quotes/{id}/error.js +++ b/src/data/quotes/{id}/error.js @@ -33,25 +33,27 @@ /* istanbul ignore file */ 'use strict' + const Mockgen = require('../../../../test/util/mockgen.js') + /** * Operations on /quotes/{id}/error */ module.exports = { /** - * summary: QuotesByIdAndError - * description: If the server is unable to find or create a quote, or some other processing error occurs, the error callback PUT /quotes/<id>/error is used. The <id> in the URI should contain the quoteId that was used for the creation of the quote, or the <id> that was used in the GET /quotes/<id>. - * parameters: id, body, Content-Length, Content-Type, Date, X-Forwarded-For, FSPIOP-Source, FSPIOP-Destination, FSPIOP-Encryption, FSPIOP-Signature, FSPIOP-URI, FSPIOP-HTTP-Method - * produces: application/json - * responses: 200, 400, 401, 403, 404, 405, 406, 501, 503 - * operationId: QuotesByIdAndError - */ + * summary: QuotesByIdAndError + * description: If the server is unable to find or create a quote, or some other processing error occurs, the error callback PUT /quotes/<id>/error is used. The <id> in the URI should contain the quoteId that was used for the creation of the quote, or the <id> that was used in the GET /quotes/<id>. + * parameters: id, body, Content-Length, Content-Type, Date, X-Forwarded-For, FSPIOP-Source, FSPIOP-Destination, FSPIOP-Encryption, FSPIOP-Signature, FSPIOP-URI, FSPIOP-HTTP-Method + * produces: application/json + * responses: 200, 400, 401, 403, 404, 405, 406, 501, 503 + * operationId: QuotesByIdAndError + */ put: { 200: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}/error', operation: 'put', @@ -60,9 +62,9 @@ module.exports = { }, 400: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}/error', operation: 'put', @@ -71,9 +73,9 @@ module.exports = { }, 401: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}/error', operation: 'put', @@ -82,9 +84,9 @@ module.exports = { }, 403: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}/error', operation: 'put', @@ -93,9 +95,9 @@ module.exports = { }, 404: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}/error', operation: 'put', @@ -104,9 +106,9 @@ module.exports = { }, 405: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}/error', operation: 'put', @@ -115,9 +117,9 @@ module.exports = { }, 406: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}/error', operation: 'put', @@ -126,9 +128,9 @@ module.exports = { }, 501: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}/error', operation: 'put', @@ -137,9 +139,9 @@ module.exports = { }, 503: function (req, res, callback) { /** - * Using mock data generator module. - * Replace this by actual data for the api. - */ + * Using mock data generator module. + * Replace this by actual data for the api. + */ Mockgen().responses({ path: '/quotes/{id}/error', operation: 'put', diff --git a/src/lib/http.js b/src/lib/http.js index ea0ae2fe..60a3dc58 100644 --- a/src/lib/http.js +++ b/src/lib/http.js @@ -44,9 +44,6 @@ const { getStackOrInspect } = require('../lib/util') axios.defaults.httpAgent = new http.Agent({ keepAlive: true }) axios.defaults.httpAgent.toJSON = () => ({}) -// TODO: where httpRequest is called, there's a pretty common pattern of obtaining an endpoint from -// the database, specialising a template string with that endpoint, then calling httpRequest. Is -// there common functionality in these places than can reasonably be factored out? /** * Encapsulates making an HTTP request and translating any error response into a domain-specific * error type. diff --git a/src/lib/util.js b/src/lib/util.js index 672e5133..84b3f454 100644 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -27,6 +27,9 @@ * ModusBox - Georgi Georgiev + + * Infitx + - Steven Oderayi -------------- ******/ @@ -194,7 +197,6 @@ function generateRequestHeadersForJWS ( const ret = Object.fromEntries( Object.entries(mappedHeaders).map(([key, value]) => [key.toLowerCase(), value]) ) - // todo: clarify if we need additionalHeaders here (see generateRequestHeaders fn) return removeEmptyKeys(ret) } @@ -210,71 +212,93 @@ function calculateRequestHash (request) { return crypto.createHash('sha256').update(requestStr).digest('hex') } -// Add caching to the participant endpoint +/** + * Fetches participant information for both the source and destination participants + * and returns the information in the format: + * + * { + * payer: { + * name: 'payerName', + * id: 'payerId', + * isActive: 1, + * links: { self: 'payerUrl' }, + * accounts: [], + * proxiedParticipant: boolean + * }, + * payee: { + * name: 'payeeName', + * id: 'payeeId', + * isActive: 1, + * links: { self: 'payeeUrl' }, + * accounts: [], + * proxiedParticipant: boolean + * } + * } + * + * If a cache is provided, it will be used to get and store the participant information for future use. + * If a proxyClient is provided, it will be used to check if the participant is a proxy participant. + * + * @param {string} source - the source participant ID + * @param {string} destination - the destination participant ID + * @param {Cache} cache - the cache to use for getting or storing participant information + * @param {ProxyClient} proxyClient - the proxy client to use for checking if a participant is a proxy participant + * @returns {object} - the participant information for the source and destination participants + */ const fetchParticipantInfo = async (source, destination, cache, proxyClient) => { - // Get quote participants from central ledger admin const { switchEndpoint } = config const url = `${switchEndpoint}/participants` - let requestPayer - let requestPayee + + const [payer, payee] = await Promise.all([ + getParticipantData(source, cache, proxyClient, url), + getParticipantData(destination, cache, proxyClient, url) + ]) + + return { payer, payee } +} + +const getParticipantData = async (participant, cache, proxyClient, url) => { + let requestParticipant = null if (proxyClient) { - if (!proxyClient.isConnected) await proxyClient.connect() - const proxyIdSource = await proxyClient.lookupProxyByDfspId(source) - const proxyIdDestination = await proxyClient.lookupProxyByDfspId(destination) - if (proxyIdSource) { - // construct participant adjacent data structure that uses the original - // participant when they are proxied and out of scheme - requestPayer = { - data: { - name: source, - id: '', - // assume source is active - isActive: 1, - links: { self: '' }, - accounts: [], - proxiedParticipant: true - } - } - } - if (proxyIdDestination) { - // construct participant adjacent data structure that uses the original - // participant when they are proxied and out of scheme - requestPayee = { - data: { - name: destination, - id: '', - // assume destination is active - isActive: 1, - links: { self: '' }, - accounts: [], - proxiedParticipant: true - } - } - } + requestParticipant = await getProxiedParticipant(participant, proxyClient) } - const cachedPayer = cache && !requestPayer && cache.get(`fetchParticipantInfo_${source}`) - const cachedPayee = cache && !requestPayee && cache.get(`fetchParticipantInfo_${destination}`) + const cachedParticipant = cache && !requestParticipant && cache.get(`fetchParticipantInfo_${participant}`) - if (!cachedPayer && !requestPayer) { - requestPayer = await axios.request({ url: `${url}/${source}` }) - cache && cache.put(`fetchParticipantInfo_${source}`, requestPayer, Config.participantDataCacheExpiresInMs) - Logger.isDebugEnabled && Logger.debug(`[fetchParticipantInfo]: cache miss for payer ${source}`) + if (!cachedParticipant && !requestParticipant) { + requestParticipant = await fetchParticipantFromServer(participant, url, cache) } else { - Logger.isDebugEnabled && Logger.debug(`[fetchParticipantInfo]: cache hit for payer ${source}`) + Logger.isDebugEnabled && Logger.debug(`[fetchParticipantInfo]: cache hit for participant ${participant}`) } - if (!cachedPayee && !requestPayee) { - requestPayee = await axios.request({ url: `${url}/${destination}` }) - cache && cache.put(`fetchParticipantInfo_${destination}`, requestPayee, Config.participantDataCacheExpiresInMs) - Logger.isDebugEnabled && Logger.debug(`[fetchParticipantInfo]: cache miss for payee ${destination}`) - } else { - Logger.isDebugEnabled && Logger.debug(`[fetchParticipantInfo]: cache hit for payee ${destination}`) + + return cachedParticipant || requestParticipant.data +} + +const getProxiedParticipant = async (participant, proxyClient) => { + if (!proxyClient.isConnected) await proxyClient.connect() + const proxyId = await proxyClient.lookupProxyByDfspId(participant) + + if (proxyId) { + return { + data: { + name: participant, + id: '', + isActive: 1, + links: { self: '' }, + accounts: [], + proxiedParticipant: true + } + } } - const payer = cachedPayer || requestPayer.data - const payee = cachedPayee || requestPayee.data - return { payer, payee } + return null +} + +const fetchParticipantFromServer = async (participant, url, cache) => { + const response = await axios.request({ url: `${url}/${participant}` }) + cache && cache.put(`fetchParticipantInfo_${participant}`, response, Config.participantDataCacheExpiresInMs) + Logger.isDebugEnabled && Logger.debug(`[fetchParticipantInfo]: cache miss for participant ${participant}`) + return response } const getParticipantEndpoint = async ({ fspId, db, loggerFn, endpointType, proxyClient = null }) => { diff --git a/src/model/fxQuotes.js b/src/model/fxQuotes.js index 164a0df5..545b5bcc 100644 --- a/src/model/fxQuotes.js +++ b/src/model/fxQuotes.js @@ -287,8 +287,12 @@ class FxQuotesModel { } /** - * Logic for handling fxQuote update requests e.g. PUT /fxQuotes/{id} requests + * Handles an fxQuote response i.e PUT /fxQuotes/{id} requests * + * @param {object} headers + * @param {string} conversionRequestId + * @param {object} fxQuoteUpdateRequest + * @param {object} span * @returns {undefined} */ async handleFxQuoteUpdate (headers, conversionRequestId, fxQuoteUpdateRequest, span) { @@ -298,79 +302,22 @@ class FxQuotesModel { ['success', 'queryName'] ).startTimer() - let txn const fspiopSource = headers[ENUM.Http.Headers.FSPIOP.SOURCE] const childSpan = span.getChild('qs_fxQuote_forwardFxQuoteUpdate') + let txn try { await childSpan.audit({ headers, params: { conversionRequestId }, payload: fxQuoteUpdateRequest }, EventSdk.AuditEventAction.start) - if ('accept' in headers) { - throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, - `Update for fx quote ${conversionRequestId} failed: "accept" header should not be sent in callbacks.`, null, headers['fspiop-source']) - } + + this.validateHeaders(headers, conversionRequestId) if (!this.envConfig.simpleRoutingMode) { - // check if this is a resend or an erroneous duplicate const dupe = await this.checkDuplicateFxQuoteResponse(conversionRequestId, fxQuoteUpdateRequest) - // fail fast on duplicate - if (dupe.isDuplicateId && (!dupe.isResend)) { - // internal-error - // same conversionRequestId but a different request, this is an error! - throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.MODIFIED_REQUEST, - `Update for fxQuote ${conversionRequestId} is a duplicate but hashes don't match`, null, fspiopSource) - } - - if (dupe.isResend && dupe.isDuplicateId) { - // this is a resend - // See section 3.2.5.1 in "API Definition v1.0.docx" API specification document. - return this.handleFxQuoteUpdateResend( - headers, - conversionRequestId, - fxQuoteUpdateRequest, - span - ) - } + this.handleDuplicate(dupe, conversionRequestId, fspiopSource, headers, fxQuoteUpdateRequest, span) - // do everything in a transaction so we can rollback multiple operations if something goes wrong txn = await this.db.newTransaction() - - // create the fxQuote response row in the db - const newFxQuoteResponse = await this.db.createFxQuoteResponse( - txn, - conversionRequestId, - fxQuoteUpdateRequest - ) - - await this.db.createFxQuoteResponseConversionTerms( - txn, - conversionRequestId, - newFxQuoteResponse.fxQuoteResponseId, - fxQuoteUpdateRequest.conversionTerms - ) - - if (fxQuoteUpdateRequest.conversionTerms.charges && - Array.isArray(fxQuoteUpdateRequest.conversionTerms.charges)) { - await this.db.createFxQuoteResponseFxCharge( - txn, - fxQuoteUpdateRequest.conversionTerms.conversionId, - fxQuoteUpdateRequest.conversionTerms.charges) - } - - if (fxQuoteUpdateRequest.conversionTerms.extensionList && - Array.isArray(fxQuoteUpdateRequest.conversionTerms.extensionList.extension)) { - await this.db.createFxQuoteResponseConversionTermsExtension( - txn, - fxQuoteUpdateRequest.conversionTerms.conversionId, - fxQuoteUpdateRequest.conversionTerms.extensionList.extension - ) - } - - // if we get here we need to create a duplicate check row - const hash = calculateRequestHash(fxQuoteUpdateRequest) - await this.db.createFxQuoteResponseDuplicateCheck(txn, newFxQuoteResponse.fxQuoteResponseId, conversionRequestId, hash) - - await txn.commit() + await this.processFxQuoteUpdate(txn, conversionRequestId, fxQuoteUpdateRequest) } await this.forwardFxQuoteUpdate(headers, conversionRequestId, fxQuoteUpdateRequest, childSpan) @@ -378,7 +325,6 @@ class FxQuotesModel { } catch (err) { histTimer({ success: false, queryName: 'handleFxQuoteUpdate' }) this.log.error('error in handleFxQuoteUpdate', err) - const fspiopSource = headers[ENUM.Http.Headers.FSPIOP.SOURCE] if (txn) { await txn.rollback().catch(() => {}) } @@ -390,6 +336,43 @@ class FxQuotesModel { } } + validateHeaders (headers, conversionRequestId) { + if ('accept' in headers) { + throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, + `Update for fx quote ${conversionRequestId} failed: "accept" header should not be sent in callbacks.`, null, headers['fspiop-source']) + } + } + + handleDuplicate (dupe, conversionRequestId, fspiopSource, headers, fxQuoteUpdateRequest, span) { + if (dupe.isDuplicateId && (!dupe.isResend)) { + throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.MODIFIED_REQUEST, + `Update for fxQuote ${conversionRequestId} is a duplicate but hashes don't match`, null, fspiopSource) + } + + if (dupe.isResend && dupe.isDuplicateId) { + return this.handleFxQuoteUpdateResend(headers, conversionRequestId, fxQuoteUpdateRequest, span) + } + } + + async processFxQuoteUpdate (txn, conversionRequestId, fxQuoteUpdateRequest) { + const newFxQuoteResponse = await this.db.createFxQuoteResponse(txn, conversionRequestId, fxQuoteUpdateRequest) + + await this.db.createFxQuoteResponseConversionTerms(txn, conversionRequestId, newFxQuoteResponse.fxQuoteResponseId, fxQuoteUpdateRequest.conversionTerms) + + if (fxQuoteUpdateRequest.conversionTerms.charges && Array.isArray(fxQuoteUpdateRequest.conversionTerms.charges)) { + await this.db.createFxQuoteResponseFxCharge(txn, fxQuoteUpdateRequest.conversionTerms.conversionId, fxQuoteUpdateRequest.conversionTerms.charges) + } + + if (fxQuoteUpdateRequest.conversionTerms.extensionList && Array.isArray(fxQuoteUpdateRequest.conversionTerms.extensionList.extension)) { + await this.db.createFxQuoteResponseConversionTermsExtension(txn, fxQuoteUpdateRequest.conversionTerms.conversionId, fxQuoteUpdateRequest.conversionTerms.extensionList.extension) + } + + const hash = calculateRequestHash(fxQuoteUpdateRequest) + await this.db.createFxQuoteResponseDuplicateCheck(txn, newFxQuoteResponse.fxQuoteResponseId, conversionRequestId, hash) + + await txn.commit() + } + /** * Forwards an fxQuote response to a payer DFSP for processing * diff --git a/src/model/index.js b/src/model/index.js index 458afd71..cc1023f5 100644 --- a/src/model/index.js +++ b/src/model/index.js @@ -2,7 +2,6 @@ const QuotesModel = require('./quotes') const BulkQuotesModel = require('./bulkQuotes') const FxQuotesModel = require('./fxQuotes') -// todo: get models only through these factories module.exports = (db, proxyClient) => ({ quotesModelFactory: (requestId) => new QuotesModel({ db, requestId, proxyClient }), bulkQuotesModelFactory: (requestId) => new BulkQuotesModel({ db, requestId, proxyClient }), diff --git a/src/model/quotes.js b/src/model/quotes.js index 7c90d4dc..fdbd2213 100644 --- a/src/model/quotes.js +++ b/src/model/quotes.js @@ -666,22 +666,10 @@ class QuotesModel { txn, quoteUpdateRequest.extensionList.extension, quoteId, null, refs.quoteResponseId) } - // todo: create any additional quoteParties e.g. for fees, comission etc... - await txn.commit() this.writeLog(`create quote update transaction committed to db: ${util.inspect(refs)}`) /// if we got here, all entities have been created in db correctly to record the quote request - - // check quote response rules - // let test = { ...quoteUpdateRequest }; - - // const failures = await quoteRules.getFailures(test); - // if (failures && failures.length > 0) { - // quote broke business rules, queue up an error callback to the caller - // this.writeLog(`Rules failed for quoteId ${refs.quoteId}: ${util.inspect(failures)}`); - // todo: make error callback - // } } // if we got here rules passed, so we can forward the quote on to the recipient dfsp const childSpan = handleQuoteUpdateSpan.getChild('qs_quote_forwardQuoteUpdate') diff --git a/src/server.js b/src/server.js index 9321b7a3..7dfda972 100644 --- a/src/server.js +++ b/src/server.js @@ -145,18 +145,6 @@ const initServer = async function (config, topicNames) { ops: { interval: 1000 } - // TODO: hapi good is deprecated per https://www.npmjs.com/package/@hapi/good/v/9.0.1 and is - // suggesting we consider another plugin from https://hapi.dev/plugins/#logging - // reporters: { - // console: [{ - // module: 'good-squeeze', - // name: 'Squeeze', - // args: [{ log: '*', response: '*' }] - // }, { - // module: 'good-console', - // args: [{ format: '' }] - // }, 'stdout'] - // } } }, { @@ -171,7 +159,6 @@ const initServer = async function (config, topicNames) { ]) server.route(Routes.APIRoutes(api)) - // TODO: follow instructions https://github.com/anttiviljami/openapi-backend/blob/master/DOCS.md#postresponsehandler-handler // start the server await server.start() diff --git a/test/unit/lib/util.test.js b/test/unit/lib/util.test.js index 423ce6cc..855c5bd5 100644 --- a/test/unit/lib/util.test.js +++ b/test/unit/lib/util.test.js @@ -612,7 +612,7 @@ describe('util', () => { .rejects .toHaveProperty('message', 'foo') - expect(axios.request.mock.calls.length).toBe(1) + expect(axios.request.mock.calls.length).toBe(2) expect(axios.request.mock.calls[0][0]).toEqual({ url: 'http://localhost:3001/participants/' + mockData.headers['fspiop-source'] }) })