account-lookup-service 15.6.0-snapshot.2 → 16.1.0-iso.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/LICENSE.md +3 -4
  3. package/README.md +2 -0
  4. package/audit-ci.jsonc +2 -27
  5. package/config/default.json +1 -0
  6. package/docker/mock-proxy/README.md +21 -0
  7. package/docker/mock-proxy/src/config.ts +7 -0
  8. package/docs/Proxy/Discovery.md +20 -0
  9. package/docs/Proxy/FXAPI_POC_payer_conversion_RECEIVE.plantuml +577 -0
  10. package/docs/Proxy/FXAPI_POC_payer_conversion_SEND.plantuml +1423 -0
  11. package/docs/Proxy/P2P.md +11 -0
  12. package/docs/Proxy/Proxy pattern - Happy path.plantuml +98 -0
  13. package/docs/Proxy/Proxy pattern - Lazy Discovery - No Oracles.plantuml +105 -0
  14. package/docs/Proxy/Proxy pattern - Lazy Discovery - No Oracles.png +0 -0
  15. package/docs/Proxy/Proxy pattern - Lazy Discovery - Oracles.plantuml +130 -0
  16. package/docs/Proxy/Proxy pattern - Lazy Discovery - Oracles.png +0 -0
  17. package/docs/Proxy/Proxy pattern - Lazy Discovery Identifier Cache Invalid.plantuml +72 -0
  18. package/docs/Proxy/Proxy pattern - Lazy Discovery Identifier Cache Invalid.png +0 -0
  19. package/docs/Proxy/Proxy pattern - P2P.plantuml +186 -0
  20. package/docs/Proxy/Proxy pattern - P2P.png +0 -0
  21. package/docs/Proxy/Proxy pattern - Unhappy path.plantuml +103 -0
  22. package/docs/Proxy/Proxy pattern - Unhappy path.png +0 -0
  23. package/docs/Proxy/Proxy pattern - happy path.png +0 -0
  24. package/docs/Proxy/Readme.md +39 -0
  25. package/docs/Proxy/SettingUpProxys.plantuml +31 -0
  26. package/docs/Proxy/SettingUpProxys.png +0 -0
  27. package/package.json +33 -18
  28. package/src/constants.js +2 -1
  29. package/src/domain/oracle/oracle.js +63 -4
  30. package/src/domain/participants/participants.js +246 -129
  31. package/src/domain/parties/getPartiesByTypeAndID.js +28 -7
  32. package/src/domain/parties/parties.js +43 -14
  33. package/src/domain/timeout/index.js +12 -1
  34. package/src/handlers/index.js +4 -4
  35. package/src/handlers/monitoring/index.js +1 -1
  36. package/src/interface/api-swagger-iso20022-parties.yaml +7 -6
  37. package/src/interface/api-swagger.yaml +7 -6
  38. package/src/lib/config.js +2 -1
  39. package/src/lib/db.js +2 -1
  40. package/src/models/currency/currency.js +10 -1
  41. package/src/models/endpointType/endpointType.js +10 -1
  42. package/src/models/oracle/facade.js +42 -16
  43. package/src/models/oracle/oracleEndpoint.js +65 -10
  44. package/src/models/oracle/oracleEndpointCached.js +29 -9
  45. package/src/models/participantEndpoint/facade.js +40 -4
  46. package/src/models/partyIdType/partyIdType.js +10 -1
  47. package/src/plugins.js +8 -21
  48. package/src/server.js +11 -0
  49. package/test/fixtures/index.js +30 -6
  50. package/test/unit/api/health.test.js +3 -0
  51. package/test/unit/api/participants/participants.test.js +5 -7
  52. package/test/unit/api/participants/{Type}/{ID}/{SubId}.test.js +0 -3
  53. package/test/unit/api/participants/{Type}/{ID}.test.js +0 -3
  54. package/test/unit/api/participants.test.js +36 -3
  55. package/test/unit/domain/oracle/oracle.test.js +8 -0
  56. package/test/unit/domain/participants/participants.test.js +83 -48
  57. package/test/unit/domain/parties/parties.test.js +8 -0
  58. package/test/unit/domain/timeout/index.test.js +8 -0
  59. package/test/unit/lib/config.test.js +7 -0
  60. package/test/unit/mocks.js +16 -11
  61. package/test/unit/models/oracle/oracleEndpointCached.test.js +32 -0
  62. package/test/unit/plugins.test.js +2 -2
  63. package/test/util/apiClients/BasicApiClient.js +2 -2
  64. package/src/lib/requestLogger.js +0 -54
  65. package/src/metrics/handler.js +0 -33
  66. package/src/metrics/plugin.js +0 -52
  67. package/src/metrics/routes.js +0 -43
  68. package/test/unit/lib/requestLogger.test.js +0 -115
@@ -87,19 +87,24 @@ const getPartiesByTypeAndID = async (headers, params, method, query, span, cache
87
87
  'Get party by Type and Id',
88
88
  ['success']
89
89
  ).startTimer()
90
+ const errorCounter = Metrics.getCounter('errorCount')
90
91
  const proxyEnabled = !!(Config.PROXY_CACHE_CONFIG.enabled && proxyCache)
91
92
  const type = params.Type
92
93
  const partySubId = params.SubId
94
+ const callbackEndpointType = utils.getPartyCbType(partySubId)
93
95
  const source = headers[Headers.FSPIOP.SOURCE]
94
96
  const proxy = proxyEnabled && headers[Headers.FSPIOP.PROXY]
95
- const callbackEndpointType = utils.getPartyCbType(partySubId)
97
+ let destination = headers[Headers.FSPIOP.DESTINATION]
98
+ // see https://github.com/mojaloop/design-authority/issues/79
99
+ // the requester has specified a destination routing header. We should respect that and forward the request directly to the destination
100
+ // without consulting any oracles.
96
101
 
97
102
  const childSpan = span ? span.getChild('getPartiesByTypeAndID') : undefined
98
- log.info('parties::getPartiesByTypeAndID::begin', { source, proxy, params })
103
+ log.info('parties::getPartiesByTypeAndID::begin', { source, destination, proxy, params })
99
104
 
100
105
  let requester
101
106
  let fspiopError
102
-
107
+ let step
103
108
  try {
104
109
  requester = await validateRequester({ source, proxy, proxyCache })
105
110
 
@@ -109,22 +114,22 @@ const getPartiesByTypeAndID = async (headers, params, method, query, span, cache
109
114
  ...(partySubId && { partySubIdOrType: partySubId })
110
115
  }
111
116
 
112
- let destination = headers[Headers.FSPIOP.DESTINATION]
113
- // see https://github.com/mojaloop/design-authority/issues/79
114
- // the requester has specified a destination routing header. We should respect that and forward the request directly to the destination
115
- // without consulting any oracles.
116
117
  if (destination) {
118
+ step = 'validateParticipant-1'
117
119
  const destParticipantModel = await participant.validateParticipant(destination)
118
120
  if (!destParticipantModel) {
121
+ step = 'lookupProxyByDfspId-2'
119
122
  const proxyId = proxyEnabled && await proxyCache.lookupProxyByDfspId(destination)
120
123
 
121
124
  if (!proxyId) {
125
+ log.warn('no destination participant, and no dfsp-to-proxy mapping', { destination })
122
126
  const errMessage = ERROR_MESSAGES.partyDestinationFspNotFound
123
127
  throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
124
128
  }
125
129
  destination = proxyId
126
130
  }
127
131
  // all ok, go ahead and forward the request
132
+ step = 'sendRequest-3'
128
133
  await participant.sendRequest(headers, destination, callbackEndpointType, RestMethods.GET, undefined, options, childSpan)
129
134
 
130
135
  histTimerEnd({ success: true })
@@ -132,6 +137,7 @@ const getPartiesByTypeAndID = async (headers, params, method, query, span, cache
132
137
  return
133
138
  }
134
139
 
140
+ step = 'oracleRequest-4'
135
141
  const response = await oracle.oracleRequest(headers, method, params, query, undefined, cache)
136
142
  if (Array.isArray(response?.data?.partyList) && response.data.partyList.length > 0) {
137
143
  // Oracle's API is a standard rest-style end-point Thus a GET /party on the oracle will return all participant-party records. We must filter the results based on the callbackEndpointType to make sure we remove records containing partySubIdOrType when we are in FSPIOP_CALLBACK_URL_PARTIES_GET mode:
@@ -159,6 +165,7 @@ const getPartiesByTypeAndID = async (headers, params, method, query, span, cache
159
165
  if (!destination) {
160
166
  clonedHeaders[Headers.FSPIOP.DESTINATION] = fspId
161
167
  }
168
+ step = 'validateParticipant-5'
162
169
  const schemeParticipant = await participant.validateParticipant(fspId)
163
170
  if (schemeParticipant) {
164
171
  sentCount++
@@ -169,6 +176,7 @@ const getPartiesByTypeAndID = async (headers, params, method, query, span, cache
169
176
  // If the participant is not in the scheme and proxy routing is enabled,
170
177
  // we should check if there is a proxy for it and send the request to the proxy
171
178
  if (proxyEnabled) {
179
+ step = 'lookupProxyByDfspId-6'
172
180
  const proxyName = await proxyCache.lookupProxyByDfspId(fspId)
173
181
  if (!proxyName) {
174
182
  log.warn('no proxyMapping for participant! TODO: Delete reference in oracle...', { fspId })
@@ -180,6 +188,7 @@ const getPartiesByTypeAndID = async (headers, params, method, query, span, cache
180
188
  }
181
189
  }
182
190
  })
191
+ step = 'sendRequests-7'
183
192
  await Promise.all(sending)
184
193
  log.info('participant.sendRequests to filtered oracle partyList are done', { sentCount })
185
194
  // todo: think what if sentCount === 0 here
@@ -188,6 +197,7 @@ const getPartiesByTypeAndID = async (headers, params, method, query, span, cache
188
197
  let filteredProxyNames = []
189
198
 
190
199
  if (proxyEnabled) {
200
+ step = 'getAllProxiesNames-8'
191
201
  const proxyNames = await Util.proxies.getAllProxiesNames(Config.SWITCH_ENDPOINT)
192
202
  filteredProxyNames = proxyNames.filter(name => name !== proxy)
193
203
  }
@@ -203,16 +213,19 @@ const getPartiesByTypeAndID = async (headers, params, method, query, span, cache
203
213
  })
204
214
  fspiopError = ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.PARTY_NOT_FOUND)
205
215
  const errorCallbackEndpointType = utils.errorPartyCbType(partySubId)
216
+ step = 'sendErrorToParticipant-9'
206
217
  await participant.sendErrorToParticipant(requester, errorCallbackEndpointType,
207
218
  fspiopError.toApiErrorObject(config.ERROR_HANDLING), callbackHeaders, params, childSpan)
208
219
  } else {
209
220
  const alsReq = utils.alsRequestDto(source, params)
210
221
  log.info('starting setSendToProxiesList flow: ', { filteredProxyNames, alsReq, proxyCacheTtlSec })
222
+ step = 'setSendToProxiesList-10'
211
223
  const isCached = await proxyCache.setSendToProxiesList(alsReq, filteredProxyNames, proxyCacheTtlSec)
212
224
  if (!isCached) {
213
225
  throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, ERROR_MESSAGES.failedToCacheSendToProxiesList)
214
226
  }
215
227
 
228
+ step = 'sending-11'
216
229
  const sending = filteredProxyNames.map(
217
230
  proxyName => participant.sendRequest(headers, proxyName, callbackEndpointType, RestMethods.GET, undefined, options, childSpan)
218
231
  .then(({ status, data } = {}) => ({ status, data }))
@@ -232,6 +245,14 @@ const getPartiesByTypeAndID = async (headers, params, method, query, span, cache
232
245
  } catch (err) {
233
246
  fspiopError = await handleErrorOnSendingCallback(err, headers, params, requester)
234
247
  histTimerEnd({ success: false })
248
+ const extensions = err.extensions || []
249
+ const system = extensions.find((element) => element.key === 'system')?.value || ''
250
+ errorCounter.inc({
251
+ code: fspiopError?.apiErrorCode?.code,
252
+ system,
253
+ operation: 'getPartiesByTypeAndID',
254
+ step
255
+ })
235
256
  } finally {
236
257
  await utils.finishSpanWithError(childSpan, fspiopError)
237
258
  }
@@ -38,13 +38,13 @@ const Metrics = require('@mojaloop/central-services-metrics')
38
38
  const oracle = require('../../models/oracle/facade')
39
39
  const participant = require('../../models/participantEndpoint/facade')
40
40
  const { ERROR_MESSAGES } = require('../../constants')
41
- const { loggerFactory } = require('../../lib')
41
+ const { logger } = require('../../lib')
42
42
  const Config = require('../../lib/config')
43
43
  const utils = require('./utils')
44
44
  const getPartiesByTypeAndID = require('./getPartiesByTypeAndID')
45
45
 
46
- const logger = loggerFactory('domain:put-parties')
47
- const handleErrorOnSendingCallback = utils.createErrorHandlerOnSendingCallback(Config, logger)
46
+ const log = logger.child('domain:put-parties')
47
+ const handleErrorOnSendingCallback = utils.createErrorHandlerOnSendingCallback(Config, log)
48
48
 
49
49
  /**
50
50
  * @function putPartiesByTypeAndID
@@ -65,44 +65,51 @@ const putPartiesByTypeAndID = async (headers, params, method, payload, dataUri,
65
65
  'Put parties by type and id',
66
66
  ['success']
67
67
  ).startTimer()
68
+ const errorCounter = Metrics.getCounter('errorCount')
68
69
  const type = params.Type
69
70
  const partySubId = params.SubId
70
71
  const source = headers[Headers.FSPIOP.SOURCE]
71
72
  const destination = headers[Headers.FSPIOP.DESTINATION]
72
73
  const proxy = headers[Headers.FSPIOP.PROXY]
73
74
  const proxyEnabled = !!(Config.PROXY_CACHE_CONFIG.enabled && proxyCache)
74
- logger.info('parties::putPartiesByTypeAndID::begin', { source, destination, proxy, params })
75
+ log.info('parties::putPartiesByTypeAndID::begin', { source, destination, proxy, params })
75
76
 
76
77
  let sendTo
78
+ let step
77
79
  try {
80
+ step = 'validateParticipant-1'
78
81
  const requesterParticipant = await participant.validateParticipant(source)
79
82
  if (!requesterParticipant) {
80
83
  if (!proxyEnabled || !proxy) {
81
84
  const errMessage = ERROR_MESSAGES.partySourceFspNotFound
82
85
  throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
83
86
  }
87
+ step = 'addDfspIdToProxyMapping-1'
84
88
  const isCached = await proxyCache.addDfspIdToProxyMapping(source, proxy)
85
89
  // think,if we should throw error if isCached === false?
86
- logger.info('addDfspIdToProxyMapping is done', { source, proxy, isCached })
90
+ log.info('addDfspIdToProxyMapping is done', { source, proxy, isCached })
87
91
  }
88
92
 
89
93
  if (proxyEnabled && proxy) {
90
94
  const alsReq = utils.alsRequestDto(destination, params) // or source?
95
+ step = 'receivedSuccessResponse-2'
91
96
  const isExists = await proxyCache.receivedSuccessResponse(alsReq)
92
97
  if (!isExists) {
93
- logger.warn('destination is NOT in scheme, and no cached sendToProxiesList', { destination, alsReq })
98
+ log.warn('destination is NOT in scheme, and no cached sendToProxiesList', { destination, alsReq })
94
99
  // think, if we need to throw an error here
95
100
  } else {
96
101
  const mappingPayload = {
97
102
  fspId: source
98
103
  }
104
+ step = 'oracleRequest-3'
99
105
  await oracle.oracleRequest(headers, RestMethods.POST, params, null, mappingPayload, cache)
100
- logger.info('oracle was updated with mappingPayload', { mappingPayload, params })
106
+ log.info('oracle was updated with mappingPayload', { mappingPayload, params })
101
107
  }
102
108
  }
103
-
109
+ step = 'validateParticipant-4'
104
110
  const destinationParticipant = await participant.validateParticipant(destination)
105
111
  if (!destinationParticipant) {
112
+ step = 'lookupProxyByDfspId-5'
106
113
  const proxyName = proxyEnabled && await proxyCache.lookupProxyByDfspId(destination)
107
114
  if (!proxyName) {
108
115
  const errMessage = ERROR_MESSAGES.partyDestinationFspNotFound
@@ -120,12 +127,21 @@ const putPartiesByTypeAndID = async (headers, params, method, payload, dataUri,
120
127
  partyIdentifier: params.ID,
121
128
  ...(partySubId && { partySubIdOrType: partySubId })
122
129
  }
130
+ step = 'sendRequest-6'
123
131
  await participant.sendRequest(headers, sendTo, callbackEndpointType, RestMethods.PUT, decodedPayload.body.toString(), options)
124
132
 
125
- logger.info('parties::putPartiesByTypeAndID::callback was sent', { sendTo, options })
133
+ log.info('parties::putPartiesByTypeAndID::callback was sent', { sendTo, options })
126
134
  histTimerEnd({ success: true })
127
135
  } catch (err) {
128
- await handleErrorOnSendingCallback(err, headers, params, sendTo)
136
+ const fspiopError = await handleErrorOnSendingCallback(err, headers, params, sendTo)
137
+ const extensions = err.extensions || []
138
+ const system = extensions.find((element) => element.key === 'system')?.value || ''
139
+ errorCounter.inc({
140
+ code: fspiopError?.apiErrorCode?.code,
141
+ system,
142
+ operation: 'putPartiesByTypeAndID',
143
+ step
144
+ })
129
145
  histTimerEnd({ success: false })
130
146
  }
131
147
  }
@@ -149,6 +165,7 @@ const putPartiesErrorByTypeAndID = async (headers, params, payload, dataUri, spa
149
165
  'Put parties error by type and id',
150
166
  ['success']
151
167
  ).startTimer()
168
+ const errorCounter = Metrics.getCounter('errorCount')
152
169
  const partySubId = params.SubId
153
170
  const destination = headers[Headers.FSPIOP.DESTINATION]
154
171
  const callbackEndpointType = utils.errorPartyCbType(partySubId)
@@ -158,33 +175,37 @@ const putPartiesErrorByTypeAndID = async (headers, params, payload, dataUri, spa
158
175
 
159
176
  let sendTo
160
177
  let fspiopError
178
+ let step
161
179
 
162
180
  try {
163
181
  const proxy = proxyEnabled && headers[Headers.FSPIOP.PROXY]
164
182
  if (proxy) {
165
183
  if (isNotValidPayeeCase(payload)) {
166
184
  const swappedHeaders = utils.swapSourceDestinationHeaders(headers)
185
+ step = 'oracleRequest-1'
167
186
  await oracle.oracleRequest(swappedHeaders, RestMethods.DELETE, params, null, null, cache)
168
187
  getPartiesByTypeAndID(swappedHeaders, params, RestMethods.GET, {}, span, cache, proxyCache)
169
188
  // todo: - think if we need to send errorCallback?
170
189
  // - or sentCallback after getPartiesByTypeAndID is done
171
- logger.info('notValidPayee case - deleted Participants and run getPartiesByTypeAndID:', { proxy, params, payload })
190
+ log.info('notValidPayee case - deleted Participants and run getPartiesByTypeAndID:', { proxy, params, payload })
172
191
  return
173
192
  }
174
193
 
175
194
  const alsReq = utils.alsRequestDto(destination, params) // or source?
195
+ step = 'receivedErrorResponse-2'
176
196
  const isLast = await proxyCache.receivedErrorResponse(alsReq, proxy)
177
197
  if (!isLast) {
178
- logger.info('got NOT last error callback from proxy:', { proxy, alsReq })
198
+ log.info('got NOT last error callback from proxy:', { proxy, alsReq })
179
199
  return
180
200
  }
181
201
  }
182
-
202
+ step = 'validateParticipant-3'
183
203
  const destinationParticipant = await participant.validateParticipant(destination)
184
204
 
185
205
  if (destinationParticipant) {
186
206
  sendTo = destination
187
207
  } else {
208
+ step = 'lookupProxyByDfspId-4'
188
209
  const proxyName = proxyEnabled && await proxyCache.lookupProxyByDfspId(destination)
189
210
  if (!proxyName) {
190
211
  const errMessage = ERROR_MESSAGES.partyDestinationFspNotFound
@@ -195,10 +216,18 @@ const putPartiesErrorByTypeAndID = async (headers, params, payload, dataUri, spa
195
216
  const decodedPayload = decodePayload(dataUri, { asParsed: false })
196
217
  await participant.sendErrorToParticipant(sendTo, callbackEndpointType, decodedPayload.body.toString(), headers, params, childSpan)
197
218
 
198
- logger.info('putPartiesErrorByTypeAndID callback was sent', { sendTo })
219
+ log.info('putPartiesErrorByTypeAndID callback was sent', { sendTo })
199
220
  histTimerEnd({ success: true })
200
221
  } catch (err) {
201
222
  fspiopError = await handleErrorOnSendingCallback(err, headers, params, sendTo)
223
+ const extensions = err.extensions || []
224
+ const system = extensions.find((element) => element.key === 'system')?.value || ''
225
+ errorCounter.inc({
226
+ code: fspiopError?.apiErrorCode?.code,
227
+ system,
228
+ operation: 'putPartiesErrorByTypeAndID',
229
+ step
230
+ })
202
231
  histTimerEnd({ success: false })
203
232
  } finally {
204
233
  await utils.finishSpanWithError(childSpan, fspiopError)
@@ -57,20 +57,31 @@ const sendTimeoutCallback = async (cacheKey) => {
57
57
  'Egress - Interscheme parties lookup timeout callback',
58
58
  ['success']
59
59
  ).startTimer()
60
-
60
+ const errorCounter = Metrics.getCounter('errorCount')
61
+ let step
61
62
  const [, destination, partyType, partyId] = cacheKey.split(':')
62
63
  const { errorInformation, params, headers, endpointType, span } = await timeoutCallbackDto({ destination, partyId, partyType })
63
64
  logger.debug('sendTimeoutCallback details:', { destination, partyType, partyId, cacheKey })
64
65
 
65
66
  try {
67
+ step = 'validateParticipant-1'
66
68
  await validateParticipant(destination)
67
69
  await span.audit({ headers, errorInformation }, AuditEventAction.start)
70
+ step = 'sendErrorToParticipant-2'
68
71
  await Participant.sendErrorToParticipant(destination, endpointType, errorInformation, headers, params, undefined, span)
69
72
  histTimerEnd({ success: true })
70
73
  } catch (err) {
71
74
  logger.warn('error in sendTimeoutCallback: ', err)
72
75
  histTimerEnd({ success: false })
73
76
  const fspiopError = reformatFSPIOPError(err)
77
+ const extensions = err.extensions || []
78
+ const system = extensions.find((element) => element.key === 'system')?.value || ''
79
+ errorCounter.inc({
80
+ code: fspiopError?.apiErrorCode?.code,
81
+ system,
82
+ operation: 'sendTimeoutCallback',
83
+ step
84
+ })
74
85
  await finishSpan(span, fspiopError)
75
86
  throw fspiopError
76
87
  }
@@ -35,7 +35,7 @@ const Package = require('../../package.json')
35
35
  const Server = require('../server')
36
36
  const { HANDLER_TYPES } = require('../constants')
37
37
  const Config = require('../lib/config')
38
- const logger = require('../lib').loggerFactory('ALS-timeout-handler')
38
+ const log = require('../lib').logger.child('ALS-timeout-handler')
39
39
 
40
40
  const Program = new Command()
41
41
 
@@ -51,16 +51,16 @@ Program.command('handlers')
51
51
  const handlers = []
52
52
 
53
53
  if (args.timeout) {
54
- logger.debug('CLI: Executing --timeout')
54
+ log.debug('CLI: Executing --timeout')
55
55
  handlers.push(HANDLER_TYPES.TIMEOUT)
56
56
  }
57
57
 
58
58
  if (handlers.length === 0) {
59
- logger.debug('CLI: No handlers specified')
59
+ log.debug('CLI: No handlers specified')
60
60
  return
61
61
  }
62
62
 
63
- module.exports = await Server.initializeHandlers(handlers, Config, logger)
63
+ module.exports = await Server.initializeHandlers(handlers, Config, log)
64
64
  })
65
65
 
66
66
  if (Array.isArray(process.argv) && process.argv.length > 2) {
@@ -18,7 +18,7 @@
18
18
  const Hapi = require('@hapi/hapi')
19
19
  const Metrics = require('@mojaloop/central-services-metrics')
20
20
  const { plugin: HealthPlugin } = require('./plugins/health')
21
- const { plugin: MetricsPlugin } = require('./plugins/metrics')
21
+ const MetricsPlugin = require('@mojaloop/central-services-metrics').plugin
22
22
  const { logger } = require('../../lib')
23
23
 
24
24
  let server
@@ -890,13 +890,14 @@ components:
890
890
  schemas:
891
891
  CorrelationId:
892
892
  title: CorrelationId
893
- pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
894
893
  type: string
895
- description: Identifier that correlates all messages of the same sequence. The
896
- API data type UUID (Universally Unique Identifier) is a JSON String in canonical
897
- format, conforming to RFC 4122, that is restricted by a regular expression
898
- for interoperability reasons. An UUID is always 36 characters long, 32 hexadecimal
899
- symbols and 4 dashes (‘-‘).
894
+ pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[1-7][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$|^[0-9A-HJKMNP-TV-Z]{26}$
895
+ description: >-
896
+ Identifier that correlates all messages of the same sequence.
897
+ The supported identifiers formats are for
898
+ lowercase [UUID](https://datatracker.ietf.org/doc/html/rfc9562) and
899
+ uppercase [ULID](https://github.com/ulid/spec)
900
+ example: 'b51ec534-ee48-4575-b6a9-ead2955b8069'
900
901
  Currency:
901
902
  title: CurrencyEnum
902
903
  maxLength: 3
@@ -889,13 +889,14 @@ components:
889
889
  schemas:
890
890
  CorrelationId:
891
891
  title: CorrelationId
892
- pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
893
892
  type: string
894
- description: Identifier that correlates all messages of the same sequence. The
895
- API data type UUID (Universally Unique Identifier) is a JSON String in canonical
896
- format, conforming to RFC 4122, that is restricted by a regular expression
897
- for interoperability reasons. An UUID is always 36 characters long, 32 hexadecimal
898
- symbols and 4 dashes (‘-‘).
893
+ pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[1-7][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$|^[0-9A-HJKMNP-TV-Z]{26}$
894
+ description: >-
895
+ Identifier that correlates all messages of the same sequence.
896
+ The supported identifiers formats are for
897
+ lowercase [UUID](https://datatracker.ietf.org/doc/html/rfc9562) and
898
+ uppercase [ULID](https://github.com/ulid/spec)
899
+ example: 'b51ec534-ee48-4575-b6a9-ead2955b8069'
899
900
  Currency:
900
901
  title: CurrencyEnum
901
902
  maxLength: 3
package/src/lib/config.js CHANGED
@@ -117,6 +117,7 @@ const config = {
117
117
  password: RC.DATABASE.PASSWORD,
118
118
  database: RC.DATABASE.DATABASE
119
119
  },
120
+ maxPendingAcquire: RC.DATABASE.MAX_PENDING_ACQUIRE,
120
121
  pool: {
121
122
  // minimum size
122
123
  min: getOrDefault(RC.DATABASE.POOL_MIN_SIZE, 2),
@@ -167,7 +168,7 @@ const config = {
167
168
  INSTRUMENTATION_METRICS_LABELS: RC.INSTRUMENTATION.METRICS.labels,
168
169
  INSTRUMENTATION_METRICS_CONFIG: RC.INSTRUMENTATION.METRICS.config,
169
170
  JWS_SIGN: RC.ENDPOINT_SECURITY.JWS.JWS_SIGN,
170
- FSPIOP_SOURCE_TO_SIGN: RC.HUB_NAME,
171
+ FSPIOP_SOURCE_TO_SIGN: RC.HUB_PARTICIPANT.NAME,
171
172
  JWS_SIGNING_KEY_PATH: RC.ENDPOINT_SECURITY.JWS.JWS_SIGNING_KEY_PATH,
172
173
  API_DOC_ENDPOINTS_ENABLED: RC.API_DOC_ENDPOINTS_ENABLED || false,
173
174
  FEATURE_ENABLE_EXTENDED_PARTY_ID_TYPE: RC.FEATURE_ENABLE_EXTENDED_PARTY_ID_TYPE || false,
package/src/lib/db.js CHANGED
@@ -23,4 +23,5 @@
23
23
  ******/
24
24
  'use strict'
25
25
 
26
- module.exports = require('@mojaloop/database-lib').Db
26
+ const Database = require('@mojaloop/database-lib/src/database')
27
+ module.exports = new Database()
@@ -39,7 +39,16 @@ const getCurrencyById = async (currencyId) => {
39
39
  try {
40
40
  return Db.from('currency').findOne({ currencyId, isActive: true })
41
41
  } catch (err) {
42
- throw ErrorHandler.Factory.reformatFSPIOPError(err)
42
+ const extensions = [{
43
+ key: 'system',
44
+ value: '["db"]'
45
+ }]
46
+ throw ErrorHandler.Factory.reformatFSPIOPError(
47
+ err,
48
+ undefined,
49
+ undefined,
50
+ extensions
51
+ )
43
52
  }
44
53
  }
45
54
 
@@ -39,7 +39,16 @@ const getEndpointTypeByType = async (type) => {
39
39
  try {
40
40
  return Db.from('endpointType').findOne({ type, isActive: true })
41
41
  } catch (err) {
42
- throw ErrorHandler.Factory.reformatFSPIOPError(err)
42
+ const extensions = [{
43
+ key: 'system',
44
+ value: '["db"]'
45
+ }]
46
+ throw ErrorHandler.Factory.reformatFSPIOPError(
47
+ err,
48
+ undefined,
49
+ undefined,
50
+ extensions
51
+ )
43
52
  }
44
53
  }
45
54
 
@@ -47,10 +47,11 @@ const { hubNameRegex } = require('../../lib/util').hubNameConfig
47
47
  * @param {object} params - uri parameters of the http request
48
48
  * @param {object} query - the query parameter on the uri of the http request
49
49
  * @param {object} payload - payload of the request being sent out
50
+ * @param {object} assertPendingAcquire - flag to check DB pool pending acquire limit
50
51
  *
51
52
  * @returns {object} returns the response from the oracle
52
53
  */
53
- exports.oracleRequest = async (headers, method, params = {}, query = {}, payload = undefined, cache) => {
54
+ exports.oracleRequest = async (headers, method, params = {}, query = {}, payload = undefined, cache, assertPendingAcquire) => {
54
55
  try {
55
56
  let url
56
57
  const partyIdType = params.Type
@@ -59,13 +60,13 @@ exports.oracleRequest = async (headers, method, params = {}, query = {}, payload
59
60
  const partySubIdOrType = (params && params.SubId) ? params.SubId : (query && query.partySubIdOrType) ? query.partySubIdOrType : undefined
60
61
  const isGetRequest = method.toUpperCase() === Enums.Http.RestMethods.GET
61
62
  if (currency && partySubIdOrType && isGetRequest) {
62
- url = await _getOracleEndpointByTypeCurrencyAndSubId(partyIdType, partyIdentifier, currency, partySubIdOrType)
63
+ url = await _getOracleEndpointByTypeCurrencyAndSubId(partyIdType, partyIdentifier, currency, partySubIdOrType, assertPendingAcquire)
63
64
  } else if (currency && isGetRequest) {
64
- url = await _getOracleEndpointByTypeAndCurrency(partyIdType, partyIdentifier, currency)
65
+ url = await _getOracleEndpointByTypeAndCurrency(partyIdType, partyIdentifier, currency, assertPendingAcquire)
65
66
  } else if (partySubIdOrType && isGetRequest) {
66
- url = await _getOracleEndpointByTypeAndSubId(partyIdType, partyIdentifier, partySubIdOrType)
67
+ url = await _getOracleEndpointByTypeAndSubId(partyIdType, partyIdentifier, partySubIdOrType, assertPendingAcquire)
67
68
  } else {
68
- url = await _getOracleEndpointByType(partyIdType, partyIdentifier)
69
+ url = await _getOracleEndpointByType(partyIdType, partyIdentifier, assertPendingAcquire)
69
70
  if (partySubIdOrType) {
70
71
  payload = { ...payload, partySubIdOrType }
71
72
  }
@@ -123,6 +124,10 @@ exports.oracleRequest = async (headers, method, params = {}, query = {}, payload
123
124
  throw err
124
125
  }
125
126
  } catch (err) {
127
+ const extensions = [{
128
+ key: 'system',
129
+ value: '["@hapi/catbox-memory","http"]'
130
+ }]
126
131
  Logger.isErrorEnabled && Logger.error(`error in oracleRequest: ${err?.stack}`)
127
132
  // If the error was a 400 from the Oracle, we'll modify the error to generate a response to the
128
133
  // initiator of the request.
@@ -131,15 +136,32 @@ exports.oracleRequest = async (headers, method, params = {}, query = {}, payload
131
136
  err.apiErrorCode.code === ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_COMMUNICATION_ERROR.code
132
137
  ) {
133
138
  if (err.extensions.some(ext => (ext.key === 'status' && ext.value === Enums.Http.ReturnCodes.BADREQUEST.CODE))) {
134
- throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.PARTY_NOT_FOUND)
139
+ throw ErrorHandler.Factory.createFSPIOPError(
140
+ ErrorHandler.Enums.FSPIOPErrorCodes.PARTY_NOT_FOUND,
141
+ undefined,
142
+ undefined,
143
+ undefined,
144
+ extensions
145
+ )
135
146
  // Added error 404 to cover a special case of the Mowali implementation
136
147
  // which uses mojaloop/als-oracle-pathfinder and currently returns 404
137
148
  // and in which case the Mowali implementation expects back `DESTINATION_FSP_ERROR`.
138
149
  } else if (err.extensions.some(ext => (ext.key === 'status' && ext.value === Enums.Http.ReturnCodes.NOTFOUND.CODE))) {
139
- throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_FSP_ERROR)
150
+ throw ErrorHandler.Factory.createFSPIOPError(
151
+ ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_FSP_ERROR,
152
+ undefined,
153
+ undefined,
154
+ undefined,
155
+ extensions
156
+ )
140
157
  }
141
158
  }
142
- throw ErrorHandler.Factory.reformatFSPIOPError(err)
159
+ throw ErrorHandler.Factory.reformatFSPIOPError(
160
+ err,
161
+ undefined,
162
+ undefined,
163
+ extensions
164
+ )
143
165
  }
144
166
  }
145
167
 
@@ -151,12 +173,13 @@ exports.oracleRequest = async (headers, method, params = {}, query = {}, payload
151
173
  * @param {string} partyIdType - party ID type (e.g MSISDN)
152
174
  * @param {string} partyIdentifier - party ID
153
175
  * @param {string} currency - currency ID
176
+ * @param {object} assertPendingAcquire - flag to check DB pool pending acquire limit
154
177
  *
155
178
  * @returns {string} returns the endpoint to the oracle
156
179
  */
157
- const _getOracleEndpointByTypeAndCurrency = async (partyIdType, partyIdentifier, currency) => {
180
+ const _getOracleEndpointByTypeAndCurrency = async (partyIdType, partyIdentifier, currency, assertPendingAcquire) => {
158
181
  let url
159
- const oracleEndpointModel = await oracleEndpointCached.getOracleEndpointByTypeAndCurrency(partyIdType, currency)
182
+ const oracleEndpointModel = await oracleEndpointCached.getOracleEndpointByTypeAndCurrency(partyIdType, currency, assertPendingAcquire)
160
183
  if (oracleEndpointModel.length > 0) {
161
184
  if (oracleEndpointModel.length > 1) {
162
185
  const defautOracle = oracleEndpointModel.filter(oracle => oracle.isDefault).pop()
@@ -186,12 +209,13 @@ const _getOracleEndpointByTypeAndCurrency = async (partyIdType, partyIdentifier,
186
209
  *
187
210
  * @param {string} partyIdType - party ID type (e.g MSISDN)
188
211
  * @param {string} partyIdentifier - party ID
212
+ * @param {object} assertPendingAcquire - flag to check DB pool pending acquire limit
189
213
  *
190
214
  * @returns {string} returns the endpoint to the oracle
191
215
  */
192
- const _getOracleEndpointByType = async (partyIdType, partyIdentifier) => {
216
+ const _getOracleEndpointByType = async (partyIdType, partyIdentifier, assertPendingAcquire) => {
193
217
  let url
194
- const oracleEndpointModel = await oracleEndpointCached.getOracleEndpointByType(partyIdType)
218
+ const oracleEndpointModel = await oracleEndpointCached.getOracleEndpointByType(partyIdType, assertPendingAcquire)
195
219
  if (oracleEndpointModel.length > 0) {
196
220
  if (oracleEndpointModel.length > 1) {
197
221
  const defaultOracle = oracleEndpointModel.filter(oracle => oracle.isDefault).pop()
@@ -222,12 +246,13 @@ const _getOracleEndpointByType = async (partyIdType, partyIdentifier) => {
222
246
  * @param {string} partyIdType - party ID type (e.g MSISDN)
223
247
  * @param {string} partyIdentifier - party ID
224
248
  * @param {string} partySubIdOrType - party subId
249
+ * @param {object} assertPendingAcquire - flag to check DB pool pending acquire limit
225
250
  *
226
251
  * @returns {string} returns the endpoint to the oracle
227
252
  */
228
- const _getOracleEndpointByTypeAndSubId = async (partyIdType, partyIdentifier, partySubIdOrType) => {
253
+ const _getOracleEndpointByTypeAndSubId = async (partyIdType, partyIdentifier, partySubIdOrType, assertPendingAcquire) => {
229
254
  let url
230
- const oracleEndpointModel = await oracleEndpointCached.getOracleEndpointByType(partyIdType)
255
+ const oracleEndpointModel = await oracleEndpointCached.getOracleEndpointByType(partyIdType, assertPendingAcquire)
231
256
  if (oracleEndpointModel.length > 0) {
232
257
  if (oracleEndpointModel.length > 1) {
233
258
  const defautOracle = oracleEndpointModel.filter(oracle => oracle.isDefault).pop()
@@ -259,12 +284,13 @@ const _getOracleEndpointByTypeAndSubId = async (partyIdType, partyIdentifier, pa
259
284
  * @param {string} partyIdentifier - party ID
260
285
  * @param {string} currency - currency ID
261
286
  * @param {string} partySubIdOrType - party subId
287
+ * @param {object} assertPendingAcquire - flag to check DB pool pending acquire limit
262
288
  *
263
289
  * @returns {string} returns the endpoint to the oracle
264
290
  */
265
- const _getOracleEndpointByTypeCurrencyAndSubId = async (partyIdType, partyIdentifier, currency, partySubIdOrType) => {
291
+ const _getOracleEndpointByTypeCurrencyAndSubId = async (partyIdType, partyIdentifier, currency, partySubIdOrType, assertPendingAcquire) => {
266
292
  let url
267
- const oracleEndpointModel = await oracleEndpointCached.getOracleEndpointByTypeAndCurrency(partyIdType, currency)
293
+ const oracleEndpointModel = await oracleEndpointCached.getOracleEndpointByTypeAndCurrency(partyIdType, currency, assertPendingAcquire)
268
294
  if (oracleEndpointModel.length > 0) {
269
295
  if (oracleEndpointModel.length > 1) {
270
296
  const defautOracle = oracleEndpointModel.filter(oracle => oracle.isDefault).pop()