@mojaloop/bulk-api-adapter 17.1.6
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.
- package/.circleci/config.yml +11 -0
- package/.dockerignore +17 -0
- package/.editorconfig +10 -0
- package/.env +0 -0
- package/.gitattributes +2 -0
- package/.ncurc.yaml +5 -0
- package/.nvmrc +1 -0
- package/.nycrc.yml +20 -0
- package/.versionrc +15 -0
- package/API.md +2 -0
- package/CHANGELOG.md +257 -0
- package/CODEOWNERS +38 -0
- package/Dockerfile +44 -0
- package/LICENSE.md +10 -0
- package/README.md +40 -0
- package/audit-ci.jsonc +9 -0
- package/config/default.json +164 -0
- package/docker/bulk-api-adapter/default.json +147 -0
- package/docker/central-ledger/default.json +460 -0
- package/docker/kafka/scripts/provision.sh +42 -0
- package/docker/sql-init/01_permissions.sql +2 -0
- package/docker/wait-for/wait-for-bulk-api-adapter.sh +11 -0
- package/docker/wait-for/wait-for-central-ledger.sh +13 -0
- package/docker/wait-for/wait-for-kafka.sh +7 -0
- package/docker/wait-for/wait-for-mockserver.sh +20 -0
- package/docker/wait-for/wait-for-mysql.sh +11 -0
- package/docker/wait-for/wait-for-objstore.sh +7 -0
- package/docker/wait-for/wait-for.env +11 -0
- package/docker/wait-for/wait-for.sh +81 -0
- package/docker-compose.yml +167 -0
- package/jsdoc.json +38 -0
- package/package.json +152 -0
- package/sonar-project.properties +17 -0
- package/src/api/handlers/bulkTransfers/{id}/error.js +71 -0
- package/src/api/handlers/bulkTransfers/{id}.js +98 -0
- package/src/api/handlers/bulkTransfers.js +77 -0
- package/src/api/handlers/endpointcache.js +48 -0
- package/src/api/handlers/health.js +52 -0
- package/src/api/handlers/metrics.js +50 -0
- package/src/api/index.js +42 -0
- package/src/api/routes.js +42 -0
- package/src/domain/bulkTransfer/index.js +219 -0
- package/src/domain/participant/index.js +61 -0
- package/src/domain/participant/lib/cache/participantEndpoint.js +127 -0
- package/src/handlers/api/health/plugin.js +51 -0
- package/src/handlers/api/health/routes.js +42 -0
- package/src/handlers/api/metrics/handler.js +34 -0
- package/src/handlers/api/metrics/plugin.js +52 -0
- package/src/handlers/api/metrics/routes.js +43 -0
- package/src/handlers/index.js +82 -0
- package/src/handlers/notification/index.js +362 -0
- package/src/handlers/register.js +54 -0
- package/src/interface/swagger.yaml +672 -0
- package/src/lib/config.js +100 -0
- package/src/lib/headers.js +78 -0
- package/src/lib/healthCheck/subServiceHealth.js +88 -0
- package/src/models/participant/facade.js +61 -0
- package/src/models/participant/participantEndpoint.js +79 -0
- package/src/shared/plugins.js +111 -0
- package/src/shared/setup.js +204 -0
- package/test/fixtures/.gitkeep +0 -0
- package/test/functional/.gitkeep +0 -0
- package/test/helpers.js +79 -0
- package/test/integration/.gitkeep +0 -0
- package/test/unit/api/handlers/bulkTransfers/{id}/error.test.js +140 -0
- package/test/unit/api/handlers/bulkTransfers/{id}.test.js +209 -0
- package/test/unit/api/handlers/notification/index.test.js +252 -0
- package/test/unit/data/bulkTransfers/{id}/error.js +28 -0
- package/test/unit/data/bulkTransfers/{id}.js +49 -0
- package/test/unit/data/bulkTransfers.js +28 -0
- package/test/unit/data/metrics.js +28 -0
- package/test/unit/data/mockgen.js +13 -0
- package/test/unit/data/transfers/{id}/error.js +28 -0
- package/test/unit/domain/bulkTransfers/index.test.js +181 -0
- package/test/unit/endpointcache.test.js +52 -0
- package/test/unit/handlers/notification/handler.test.js +323 -0
- package/test/unit/health.test.js +75 -0
- package/test/unit/lib/config.test.js +83 -0
- package/test/unit/lib/headers.test.js +111 -0
- package/test/unit/metrics.test.js +71 -0
- package/test-integration.Dockerfile +24 -0
- package/test.Dockerfile +22 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# wait-for-mockserver.sh
|
|
3
|
+
|
|
4
|
+
source /opt/wait-for/wait-for.env
|
|
5
|
+
|
|
6
|
+
function healthCheck() {
|
|
7
|
+
curl -s -X GET "http://$WAIT_FOR_MOCK_HOST:$WAIT_FOR_MOCK_PORT"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function command() {
|
|
11
|
+
curl -s -X PUT "http://$WAIT_FOR_MOCK_HOST:$WAIT_FOR_MOCK_PORT/expectation" -d '{ "httpRequest": { "method": ".*", "path": "/.*transfers.*" }, "times" : { "remainingTimes" : 0, "unlimited" : true }, "timeToLive" : { "unlimited" : true }, "httpResponse": { "statusCode": 200, "body": "{}" } }';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
until healthCheck; do
|
|
15
|
+
>&2 echo "mockserver is unavailable - sleeping"
|
|
16
|
+
sleep 1
|
|
17
|
+
done
|
|
18
|
+
|
|
19
|
+
>&2 echo "mockserver is up - executing command"
|
|
20
|
+
command
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
echo "** STARTUP - Checking for DB connection..."
|
|
4
|
+
|
|
5
|
+
source /opt/wait-for/wait-for.env
|
|
6
|
+
|
|
7
|
+
apk --no-cache add mysql-client
|
|
8
|
+
|
|
9
|
+
until result=$(mysql -h $WAIT_FOR_DB_HOST -P $WAIT_FOR_DB_PORT -u $WAIT_FOR_DB_USER --password=$WAIT_FOR_DB_PASSWORD $WAIT_FOR_DB_DATABASE -ss -N -e 'select 1;') && eval 'echo is_connected=$result' && if [ -z $result ]; then false; fi && if [ $result -ne 1 ]; then false; fi; do echo waiting for MySQL; sleep 2; done;
|
|
10
|
+
|
|
11
|
+
echo "** STARTUP - DB connection successful!"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Environment config for wait-for scripts
|
|
2
|
+
|
|
3
|
+
WAIT_FOR_DB_HOST=mysql
|
|
4
|
+
WAIT_FOR_DB_PORT=3306
|
|
5
|
+
WAIT_FOR_DB_USER=central_ledger
|
|
6
|
+
WAIT_FOR_DB_PASSWORD=password
|
|
7
|
+
WAIT_FOR_DB_DATABASE=central_ledger
|
|
8
|
+
WAIT_FOR_OBJSTORE_SERVER=objstore:27017
|
|
9
|
+
WAIT_FOR_DB_KAFKA_BROKER=kafka:29092
|
|
10
|
+
WAIT_FOR_MOCK_HOST=mockserver
|
|
11
|
+
WAIT_FOR_MOCK_PORT=1080
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
# Script from https://github.com/eficode/wait-for
|
|
4
|
+
|
|
5
|
+
TIMEOUT=15
|
|
6
|
+
QUIET=0
|
|
7
|
+
|
|
8
|
+
echoerr() {
|
|
9
|
+
if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
usage() {
|
|
13
|
+
exitcode="$1"
|
|
14
|
+
cat << USAGE >&2
|
|
15
|
+
Usage:
|
|
16
|
+
$cmdname host:port [-t timeout] [-- command args]
|
|
17
|
+
-q | --quiet Do not output any status messages
|
|
18
|
+
-t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout
|
|
19
|
+
-- COMMAND ARGS Execute command with args after the test finishes
|
|
20
|
+
USAGE
|
|
21
|
+
exit "$exitcode"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
wait_for() {
|
|
25
|
+
for i in `seq $TIMEOUT` ; do
|
|
26
|
+
nc -z "$HOST" "$PORT" > /dev/null 2>&1
|
|
27
|
+
|
|
28
|
+
result=$?
|
|
29
|
+
if [ $result -eq 0 ] ; then
|
|
30
|
+
if [ $# -gt 0 ] ; then
|
|
31
|
+
exec "$@"
|
|
32
|
+
fi
|
|
33
|
+
exit 0
|
|
34
|
+
fi
|
|
35
|
+
sleep 1
|
|
36
|
+
done
|
|
37
|
+
echo "Operation timed out" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
while [ $# -gt 0 ]
|
|
42
|
+
do
|
|
43
|
+
case "$1" in
|
|
44
|
+
*:* )
|
|
45
|
+
HOST=$(printf "%s\n" "$1"| cut -d : -f 1)
|
|
46
|
+
PORT=$(printf "%s\n" "$1"| cut -d : -f 2)
|
|
47
|
+
shift 1
|
|
48
|
+
;;
|
|
49
|
+
-q | --quiet)
|
|
50
|
+
QUIET=1
|
|
51
|
+
shift 1
|
|
52
|
+
;;
|
|
53
|
+
-t)
|
|
54
|
+
TIMEOUT="$2"
|
|
55
|
+
if [ "$TIMEOUT" = "" ]; then break; fi
|
|
56
|
+
shift 2
|
|
57
|
+
;;
|
|
58
|
+
--timeout=*)
|
|
59
|
+
TIMEOUT="${1#*=}"
|
|
60
|
+
shift 1
|
|
61
|
+
;;
|
|
62
|
+
--)
|
|
63
|
+
shift
|
|
64
|
+
break
|
|
65
|
+
;;
|
|
66
|
+
--help)
|
|
67
|
+
usage 0
|
|
68
|
+
;;
|
|
69
|
+
*)
|
|
70
|
+
echoerr "Unknown argument: $1"
|
|
71
|
+
usage 1
|
|
72
|
+
;;
|
|
73
|
+
esac
|
|
74
|
+
done
|
|
75
|
+
|
|
76
|
+
if [ "$HOST" = "" -o "$PORT" = "" ]; then
|
|
77
|
+
echoerr "Error: you need to provide a host and port to test."
|
|
78
|
+
usage 2
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
wait_for "$@"
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
networks:
|
|
2
|
+
ba-mojaloop-net:
|
|
3
|
+
name: ba-mojaloop-net
|
|
4
|
+
|
|
5
|
+
services:
|
|
6
|
+
bulk-api-adapter:
|
|
7
|
+
build:
|
|
8
|
+
context: .
|
|
9
|
+
cache_from:
|
|
10
|
+
- mojaloop/bulk-api-adapter
|
|
11
|
+
- bulk-api-adapter
|
|
12
|
+
container_name: ba_bulk-api-adapter
|
|
13
|
+
user: root
|
|
14
|
+
command:
|
|
15
|
+
- "sh"
|
|
16
|
+
- "-c"
|
|
17
|
+
- "sh /opt/wait-for/wait-for-bulk-api-adapter.sh && node src/api/index.js"
|
|
18
|
+
ports:
|
|
19
|
+
- "3003:3003"
|
|
20
|
+
environment:
|
|
21
|
+
- LOG_LEVEL=info
|
|
22
|
+
- CSL_LOG_TRANSPORT=file
|
|
23
|
+
volumes:
|
|
24
|
+
- ./docker/bulk-api-adapter/default.json:/opt/app/config/default.json
|
|
25
|
+
- ./docker/wait-for:/opt/wait-for
|
|
26
|
+
networks:
|
|
27
|
+
- ba-mojaloop-net
|
|
28
|
+
depends_on:
|
|
29
|
+
- central-ledger
|
|
30
|
+
- kafka
|
|
31
|
+
- objstore
|
|
32
|
+
healthcheck:
|
|
33
|
+
test: ["CMD", "sh", "-c" ,"apk --no-cache add curl", ";", "curl", "http://localhost:3003/health"]
|
|
34
|
+
timeout: 20s
|
|
35
|
+
retries: 10
|
|
36
|
+
interval: 30s
|
|
37
|
+
|
|
38
|
+
objstore:
|
|
39
|
+
image: mongo:8.0
|
|
40
|
+
container_name: ba_objstore
|
|
41
|
+
logging:
|
|
42
|
+
driver: none
|
|
43
|
+
ports:
|
|
44
|
+
- "27017:27017"
|
|
45
|
+
networks:
|
|
46
|
+
- ba-mojaloop-net
|
|
47
|
+
healthcheck:
|
|
48
|
+
test: mongo localhost:27017/test --quiet 1
|
|
49
|
+
interval: 10s
|
|
50
|
+
timeout: 10s
|
|
51
|
+
retries: 5
|
|
52
|
+
start_period: 40s
|
|
53
|
+
|
|
54
|
+
central-ledger:
|
|
55
|
+
image: mojaloop/central-ledger:v18.0.1
|
|
56
|
+
container_name: ba_central-ledger
|
|
57
|
+
user: root
|
|
58
|
+
command:
|
|
59
|
+
- "sh"
|
|
60
|
+
- "-c"
|
|
61
|
+
- "sh /opt/wait-for/wait-for-central-ledger.sh && node src/api/index.js"
|
|
62
|
+
links:
|
|
63
|
+
- mysql
|
|
64
|
+
- kafka
|
|
65
|
+
ports:
|
|
66
|
+
- "3001:3001"
|
|
67
|
+
volumes:
|
|
68
|
+
- ./docker/central-ledger/default.json:/opt/app/config/default.json
|
|
69
|
+
- ./docker/wait-for:/opt/wait-for
|
|
70
|
+
depends_on:
|
|
71
|
+
- mysql
|
|
72
|
+
- kafka
|
|
73
|
+
environment:
|
|
74
|
+
- CLEDG_DATABASE_URI=mysql://central_ledger:password@mysql:3306/central_ledger
|
|
75
|
+
- CLEDG_SIDECAR__DISABLED=true
|
|
76
|
+
networks:
|
|
77
|
+
- ba-mojaloop-net
|
|
78
|
+
healthcheck:
|
|
79
|
+
test: ["CMD", "bash", "-c" ,"apk --no-cache add curl", ";", "curl", "http://localhost:3001/health"]
|
|
80
|
+
timeout: 20s
|
|
81
|
+
retries: 10
|
|
82
|
+
interval: 30s
|
|
83
|
+
|
|
84
|
+
mysql:
|
|
85
|
+
image: mysql/mysql-server:8.0
|
|
86
|
+
container_name: ba_mysql
|
|
87
|
+
logging:
|
|
88
|
+
driver: none
|
|
89
|
+
ports:
|
|
90
|
+
- "3306:3306"
|
|
91
|
+
volumes:
|
|
92
|
+
# this fixes the permissions issue, but docker-compose up will fail on first attempt
|
|
93
|
+
- ./docker/sql-init/:/docker-entrypoint-initdb.d/
|
|
94
|
+
environment:
|
|
95
|
+
- MYSQL_USER=${DBUSER:-central_ledger}
|
|
96
|
+
- MYSQL_PASSWORD=${DBPASS:-password}
|
|
97
|
+
- MYSQL_DATABASE=${DBUSER:-central_ledger}
|
|
98
|
+
- MYSQL_ALLOW_EMPTY_PASSWORD=true
|
|
99
|
+
networks:
|
|
100
|
+
- ba-mojaloop-net
|
|
101
|
+
healthcheck:
|
|
102
|
+
test: ["CMD", "mysqladmin" ,"ping", "-h", "mysql"]
|
|
103
|
+
timeout: 20s
|
|
104
|
+
retries: 10
|
|
105
|
+
start_period: 40s
|
|
106
|
+
interval: 30s
|
|
107
|
+
|
|
108
|
+
kafka:
|
|
109
|
+
image: docker.io/bitnami/kafka:3.9
|
|
110
|
+
container_name: cs_kafka
|
|
111
|
+
networks:
|
|
112
|
+
- ba-mojaloop-net
|
|
113
|
+
ports:
|
|
114
|
+
- "9092:9092"
|
|
115
|
+
environment:
|
|
116
|
+
# BITNAMI_DEBUG: "yes"
|
|
117
|
+
ALLOW_PLAINTEXT_LISTENER: "yes"
|
|
118
|
+
KAFKA_ADVERTISED_HOST_NAME: kafka
|
|
119
|
+
KAFKA_CFG_LISTENERS: CONTROLLER://:9093,LISTENER_DOCKER://:29092,LISTENER_EXTERN://:9092
|
|
120
|
+
KAFKA_CFG_ADVERTISED_LISTENERS: LISTENER_DOCKER://kafka:29092,LISTENER_EXTERN://localhost:9092
|
|
121
|
+
KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,LISTENER_DOCKER:PLAINTEXT,LISTENER_EXTERN:PLAINTEXT
|
|
122
|
+
KAFKA_CFG_INTER_BROKER_LISTENER_NAME: LISTENER_DOCKER
|
|
123
|
+
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
|
|
124
|
+
KAFKA_CFG_MESSAGE_MAX_BYTES: 200000000
|
|
125
|
+
KAFKA_CFG_NODE_ID: 1
|
|
126
|
+
KAFKA_CFG_PROCESS_ROLES: broker,controller
|
|
127
|
+
KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER
|
|
128
|
+
KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 1@127.0.0.1:9093
|
|
129
|
+
KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
|
|
130
|
+
KAFKA_ENABLE_KRAFT: "true"
|
|
131
|
+
healthcheck:
|
|
132
|
+
test: ["CMD" ,"/opt/bitnami/kafka/bin/kafka-broker-api-versions.sh","--bootstrap-server","kafka:29092"]
|
|
133
|
+
timeout: 20s
|
|
134
|
+
retries: 10
|
|
135
|
+
start_period: 40s
|
|
136
|
+
interval: 30s
|
|
137
|
+
|
|
138
|
+
kafka-provisioning:
|
|
139
|
+
container_name: cs_kafka-provisioning
|
|
140
|
+
networks:
|
|
141
|
+
- ba-mojaloop-net
|
|
142
|
+
image: docker.io/bitnami/kafka:3.9
|
|
143
|
+
depends_on:
|
|
144
|
+
- kafka
|
|
145
|
+
volumes:
|
|
146
|
+
- ./docker/kafka/scripts:/tmp/kafka/scripts
|
|
147
|
+
command: bash /tmp/kafka/scripts/provision.sh
|
|
148
|
+
|
|
149
|
+
simulator:
|
|
150
|
+
image: mojaloop/simulator:v12.2.1
|
|
151
|
+
container_name: ba_simulator
|
|
152
|
+
ports:
|
|
153
|
+
- "8444:8444"
|
|
154
|
+
environment:
|
|
155
|
+
- LOG_LEVEL=info
|
|
156
|
+
- TRANSFERS_ENDPOINT=http://host.docker.internal:3000
|
|
157
|
+
- QUOTES_ENDPOINT=http://host.docker.internal:3002
|
|
158
|
+
- PARTIES_ENDPOINT=http://host.docker.internal:4002
|
|
159
|
+
- TRANSFERS_FULFIL_RESPONSE_DISABLED=false
|
|
160
|
+
networks:
|
|
161
|
+
- ba-mojaloop-net
|
|
162
|
+
healthcheck:
|
|
163
|
+
test: ["CMD", "sh", "-c" ,"apk --no-cache add curl", ";", "curl", "http://localhost:8444/health"]
|
|
164
|
+
timeout: 20s
|
|
165
|
+
retries: 10
|
|
166
|
+
interval: 30s
|
|
167
|
+
|
package/jsdoc.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tags": {
|
|
3
|
+
"allowUnknownTags": true
|
|
4
|
+
},
|
|
5
|
+
"source": {
|
|
6
|
+
"include": [
|
|
7
|
+
"src"
|
|
8
|
+
],
|
|
9
|
+
"includePattern": ".js$",
|
|
10
|
+
"excludePattern": "(node_modules/|docs)"
|
|
11
|
+
},
|
|
12
|
+
"plugins": [
|
|
13
|
+
"plugins/markdown"
|
|
14
|
+
],
|
|
15
|
+
"opts": {
|
|
16
|
+
"template": "node_modules/docdash",
|
|
17
|
+
"encoding": "utf8",
|
|
18
|
+
"destination": "docs/",
|
|
19
|
+
"recurse": true,
|
|
20
|
+
"verbose": true
|
|
21
|
+
},
|
|
22
|
+
"markdown": {
|
|
23
|
+
"parser": "gfm",
|
|
24
|
+
"hardwrap": true
|
|
25
|
+
},
|
|
26
|
+
"templates": {
|
|
27
|
+
"cleverLinks": false,
|
|
28
|
+
"monospaceLinks": false,
|
|
29
|
+
"default": {
|
|
30
|
+
"outputSourceFiles": true,
|
|
31
|
+
"includeDate": false
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"docdash": {
|
|
35
|
+
"static": false,
|
|
36
|
+
"sort": true
|
|
37
|
+
}
|
|
38
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mojaloop/bulk-api-adapter",
|
|
3
|
+
"version": "17.1.6",
|
|
4
|
+
"description": "Mojaloop Bulk API Adapter",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"author": "ModusBox",
|
|
7
|
+
"contributors": [
|
|
8
|
+
"Georgi Georgiev <georgi.georgiev@modusbox.com>",
|
|
9
|
+
"Miguel de Barros <miguel.debarros@modusbox.com>",
|
|
10
|
+
"Sam Kummary <sam@mojaloop.io",
|
|
11
|
+
"Steven Oderayi <steven.oderayi@modusbox.com>",
|
|
12
|
+
"Valentin Genev <valentin.genev@modusbox.com>"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git://github.com/mojaloop/bulk-api-adapter.git"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"registry": "https://registry.npmjs.org"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18.x"
|
|
23
|
+
},
|
|
24
|
+
"imports": {
|
|
25
|
+
"#src/*": "./src/*.js",
|
|
26
|
+
"#test/*": "./test/*.js"
|
|
27
|
+
},
|
|
28
|
+
"main": "./src/api/index.js",
|
|
29
|
+
"pre-commit": [
|
|
30
|
+
"lint",
|
|
31
|
+
"dep:check",
|
|
32
|
+
"test"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"start": "npm run start:api",
|
|
36
|
+
"start:api": "node src/api/index.js",
|
|
37
|
+
"watch:api": "npx nodemon src/api/index.js",
|
|
38
|
+
"regenerate": "yo swaggerize:test --framework hapi --apiPath './src/interface/swagger.yaml'",
|
|
39
|
+
"standard": "npx standard",
|
|
40
|
+
"standard:fix": "npx standard --fix",
|
|
41
|
+
"lint": "npm run standard",
|
|
42
|
+
"lint:fix": "npm run standard:fix",
|
|
43
|
+
"test": "npm run test:unit",
|
|
44
|
+
"test:all": "npm run test",
|
|
45
|
+
"test:unit": "tape 'test/unit/**/*.test.js' | tap-spec",
|
|
46
|
+
"test:xunit": "tape 'test/unit/**/*.test.js' | tap-xunit",
|
|
47
|
+
"test:coverage": "npx nyc --reporter=lcov --reporter=text-summary tapes -- 'test/unit/**/**.test.js'",
|
|
48
|
+
"test:coverage-check": "npm run test:coverage && nyc check-coverage",
|
|
49
|
+
"test:functional": "echo 'No functional tests defined'",
|
|
50
|
+
"test:integration": "echo 'No integration tests defined'",
|
|
51
|
+
"docker:build": "docker build --build-arg NODE_VERSION=\"$(cat .nvmrc)-alpine\" -t mojaloop/bulk-api-adapter:local .",
|
|
52
|
+
"docker:up": "docker-compose -f docker-compose.yml up -d",
|
|
53
|
+
"docker:stop": "docker-compose -f docker-compose.yml stop",
|
|
54
|
+
"docker:rm": "docker-compose -f docker-compose.yml rm -f -v",
|
|
55
|
+
"docker:down": "docker-compose -f docker-compose.yml down -v",
|
|
56
|
+
"docker:clean": "docker-compose -f docker-compose.yml down --rmi local",
|
|
57
|
+
"audit:fix": "npm audit fix",
|
|
58
|
+
"audit:check": "npx audit-ci --config ./audit-ci.jsonc",
|
|
59
|
+
"dep:check": "npx ncu -e 2",
|
|
60
|
+
"dep:update": "npx ncu -u",
|
|
61
|
+
"release": "npx standard-version --releaseCommitMessageFormat 'chore(release): {{currentTag}} [skip ci]'",
|
|
62
|
+
"snapshot": "npx standard-version --no-verify --skip.changelog --prerelease snapshot --releaseCommitMessageFormat 'chore(snapshot): {{currentTag}}'"
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"@hapi/basic": "7.0.2",
|
|
66
|
+
"@hapi/boom": "10.0.1",
|
|
67
|
+
"@hapi/catbox": "12.1.1",
|
|
68
|
+
"@hapi/catbox-memory": "6.0.2",
|
|
69
|
+
"@hapi/good": "9.0.1",
|
|
70
|
+
"@hapi/hapi": "21.3.12",
|
|
71
|
+
"@hapi/inert": "7.1.0",
|
|
72
|
+
"@hapi/vision": "7.0.3",
|
|
73
|
+
"@mojaloop/central-services-error-handling": "13.0.6",
|
|
74
|
+
"@mojaloop/central-services-health": "15.0.2",
|
|
75
|
+
"@mojaloop/central-services-logger": "11.5.5",
|
|
76
|
+
"@mojaloop/central-services-metrics": "12.4.5",
|
|
77
|
+
"@mojaloop/central-services-shared": "18.15.1",
|
|
78
|
+
"@mojaloop/central-services-stream": "11.4.3",
|
|
79
|
+
"@mojaloop/sdk-standard-components": "19.6.4",
|
|
80
|
+
"@mojaloop/event-sdk": "14.1.3",
|
|
81
|
+
"@mojaloop/object-store-lib": "12.0.5",
|
|
82
|
+
"@now-ims/hapi-now-auth": "2.1.0",
|
|
83
|
+
"axios": "1.7.9",
|
|
84
|
+
"blipp": "4.0.2",
|
|
85
|
+
"commander": "13.1.0",
|
|
86
|
+
"hapi-auth-bearer-token": "8.0.0",
|
|
87
|
+
"hapi-openapi": "3.0.0",
|
|
88
|
+
"hapi-swagger": "17.3.2",
|
|
89
|
+
"immutable": "5.0.3",
|
|
90
|
+
"joi": "^17.13.3",
|
|
91
|
+
"mongo-uri-builder": "^4.0.0",
|
|
92
|
+
"mustache": "4.2.0",
|
|
93
|
+
"parse-strings-in-object": "2.0.0",
|
|
94
|
+
"rc": "1.2.8",
|
|
95
|
+
"uuid4": "2.0.3"
|
|
96
|
+
},
|
|
97
|
+
"overrides": {
|
|
98
|
+
"ansi-regex": "5.0.1",
|
|
99
|
+
"postcss": {
|
|
100
|
+
"nanoid": "^3.3.8"
|
|
101
|
+
},
|
|
102
|
+
"swagmock": {
|
|
103
|
+
"validator": "13.12.0"
|
|
104
|
+
},
|
|
105
|
+
"shins": {
|
|
106
|
+
"ajv": "6.12.3",
|
|
107
|
+
"ejs": "3.1.10",
|
|
108
|
+
"path-to-regexp": "0.1.12",
|
|
109
|
+
"sanitize-html": "2.12.1",
|
|
110
|
+
"markdown-it": "12.3.2",
|
|
111
|
+
"undici": "6.21.1"
|
|
112
|
+
},
|
|
113
|
+
"widdershins": {
|
|
114
|
+
"swagger2openapi": "7.0.8",
|
|
115
|
+
"markdown-it": "12.3.2"
|
|
116
|
+
},
|
|
117
|
+
"request": {
|
|
118
|
+
"tough-cookie": "4.1.3"
|
|
119
|
+
},
|
|
120
|
+
"jsonwebtoken": "9.0.0",
|
|
121
|
+
"jsonpointer": "5.0.0",
|
|
122
|
+
"cross-spawn": "7.0.6",
|
|
123
|
+
"trim": "0.0.3",
|
|
124
|
+
"yargs-parser": "21.1.1"
|
|
125
|
+
},
|
|
126
|
+
"devDependencies": {
|
|
127
|
+
"audit-ci": "^7.1.0",
|
|
128
|
+
"nyc": "17.1.0",
|
|
129
|
+
"nodemon": "3.1.9",
|
|
130
|
+
"npm-check-updates": "17.1.14",
|
|
131
|
+
"pre-commit": "1.2.2",
|
|
132
|
+
"proxyquire": "2.1.3",
|
|
133
|
+
"replace": "^1.2.2",
|
|
134
|
+
"rewire": "7.0.0",
|
|
135
|
+
"sinon": "19.0.2",
|
|
136
|
+
"standard": "17.1.2",
|
|
137
|
+
"standard-version": "^9.5.0",
|
|
138
|
+
"swagmock": "1.0.0",
|
|
139
|
+
"tap-spec": "^5.0.0",
|
|
140
|
+
"tap-xunit": "2.4.1",
|
|
141
|
+
"tape": "5.9.0",
|
|
142
|
+
"tapes": "4.1.0"
|
|
143
|
+
},
|
|
144
|
+
"generator-swaggerize": {
|
|
145
|
+
"version": "4.1.0"
|
|
146
|
+
},
|
|
147
|
+
"standard-version": {
|
|
148
|
+
"scripts": {
|
|
149
|
+
"postchangelog": "replace '\\[mojaloop/#(\\d+)\\]\\(https://github.com/mojaloop/(.*)/issues/(\\d+)\\)' '[mojaloop/#$1](https://github.com/mojaloop/project/issues/$1)' CHANGELOG.md"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Required metadata
|
|
2
|
+
sonar.projectKey=bulk-api-adapter
|
|
3
|
+
sonar.projectName=bulk-api-adapter
|
|
4
|
+
sonar.projectVersion=1.0
|
|
5
|
+
|
|
6
|
+
# Comma-separated paths to directories with sources (required)
|
|
7
|
+
sonar.sources=src,test
|
|
8
|
+
|
|
9
|
+
# Language
|
|
10
|
+
sonar.language=js
|
|
11
|
+
|
|
12
|
+
# Encoding of the source files
|
|
13
|
+
sonar.sourceEncoding=UTF-8
|
|
14
|
+
sonar.exclusions=migrations
|
|
15
|
+
|
|
16
|
+
# To import the LCOV report
|
|
17
|
+
sonar.javascript.lcov.reportPath=lcov.info
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*****
|
|
2
|
+
License
|
|
3
|
+
--------------
|
|
4
|
+
Copyright © 2020-2025 Mojaloop Foundation
|
|
5
|
+
The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
10
|
+
|
|
11
|
+
Contributors
|
|
12
|
+
--------------
|
|
13
|
+
This is the official list of the Mojaloop project contributors for this file.
|
|
14
|
+
Names of the original copyright holders (individuals or organizations)
|
|
15
|
+
should be listed with a '*' in the first column. People who have
|
|
16
|
+
contributed from an organization can be listed under the organization
|
|
17
|
+
that actually holds the copyright for their contributions (see the
|
|
18
|
+
Mojaloop Foundation organization for an example). Those individuals should have
|
|
19
|
+
their names indented and be marked with a '-'. Email address can be added
|
|
20
|
+
optionally within square brackets <email>.
|
|
21
|
+
|
|
22
|
+
* Mojaloop Foundation
|
|
23
|
+
- Name Surname <name.surname@mojaloop.io>
|
|
24
|
+
|
|
25
|
+
* ModusBox
|
|
26
|
+
- Steven Oderayi <steven.oderayi@modusbox.com>
|
|
27
|
+
--------------
|
|
28
|
+
******/
|
|
29
|
+
|
|
30
|
+
'use strict'
|
|
31
|
+
|
|
32
|
+
const TransferService = require('../../../../domain/bulkTransfer')
|
|
33
|
+
const Logger = require('@mojaloop/central-services-logger')
|
|
34
|
+
const ErrorHandler = require('@mojaloop/central-services-error-handling')
|
|
35
|
+
const Hash = require('@mojaloop/central-services-shared').Util.Hash
|
|
36
|
+
const Uuid = require('uuid4')
|
|
37
|
+
const HTTPENUM = require('@mojaloop/central-services-shared').Enum.Http
|
|
38
|
+
const BulkTransferModels = require('@mojaloop/object-store-lib').Models.BulkTransfer
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Operations on /bulkTransfers/{id}/error
|
|
42
|
+
*/
|
|
43
|
+
module.exports = {
|
|
44
|
+
/**
|
|
45
|
+
* summary: Handles bulk transfer error callback
|
|
46
|
+
* description: If the server is unable to find or create a bulk transfer, or another processing error occurs, the error callback PUT /bulkTransfers/<ID>/error is used. The <ID> in the URI should contain the bulkTransferId that was used for the creation request of the bulk transfer (POST /bulkTransfers), or the <ID> that was used in the GET /bulkTransfers/<ID>.
|
|
47
|
+
* parameters: id, body, Content-Length, Content-Type, Date, X-Forwarded-For, FSPIOP-Source, FSPIOP-Destination, FSPIOP-Encryption, FSPIOP-Signature, FSPIOP-URI, FSPIOP-HTTP-Method
|
|
48
|
+
* produces: application/json
|
|
49
|
+
* responses: default
|
|
50
|
+
*/
|
|
51
|
+
put: async function putBulkTransferErrorById (request, h) {
|
|
52
|
+
try {
|
|
53
|
+
Logger.debug('error::payload(%s)', JSON.stringify(request.payload))
|
|
54
|
+
|
|
55
|
+
const bulkTransferId = request.params.id
|
|
56
|
+
const { errorInformation, extensionList } = request.payload
|
|
57
|
+
const hash = Hash.generateSha256(JSON.stringify(request.payload))
|
|
58
|
+
const messageId = Uuid()
|
|
59
|
+
const message = { bulkTransferId, errorInformation, extensionList, hash }
|
|
60
|
+
|
|
61
|
+
const IndividualTransferFulfilModel = BulkTransferModels.getIndividualTransferFulfilModel()
|
|
62
|
+
await new IndividualTransferFulfilModel({ messageId, bulkTransferId, payload: request.payload }).save()
|
|
63
|
+
await TransferService.bulkTransferError(messageId, request.headers, message)
|
|
64
|
+
|
|
65
|
+
return h.response().code(HTTPENUM.ReturnCodes.OK.CODE)
|
|
66
|
+
} catch (err) {
|
|
67
|
+
Logger.error(err)
|
|
68
|
+
throw ErrorHandler.Factory.reformatFSPIOPError(err)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/*****
|
|
2
|
+
License
|
|
3
|
+
--------------
|
|
4
|
+
Copyright © 2020-2025 Mojaloop Foundation
|
|
5
|
+
The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
10
|
+
|
|
11
|
+
Contributors
|
|
12
|
+
--------------
|
|
13
|
+
This is the official list of the Mojaloop project contributors for this file.
|
|
14
|
+
Names of the original copyright holders (individuals or organizations)
|
|
15
|
+
should be listed with a '*' in the first column. People who have
|
|
16
|
+
contributed from an organization can be listed under the organization
|
|
17
|
+
that actually holds the copyright for their contributions (see the
|
|
18
|
+
Mojaloop Foundation organization for an example). Those individuals should have
|
|
19
|
+
their names indented and be marked with a '-'. Email address can be added
|
|
20
|
+
optionally within square brackets <email>.
|
|
21
|
+
|
|
22
|
+
* Mojaloop Foundation
|
|
23
|
+
- Name Surname <name.surname@mojaloop.io>
|
|
24
|
+
|
|
25
|
+
* ModusBox
|
|
26
|
+
- Georgi Georgiev <georgi.georgiev@modusbox.com>
|
|
27
|
+
- Valentin Genev <valentin.genev@modusbox.com>
|
|
28
|
+
- Steven Oderayi <steven.oderayi@modusbox.com>
|
|
29
|
+
--------------
|
|
30
|
+
******/
|
|
31
|
+
'use strict'
|
|
32
|
+
|
|
33
|
+
const Uuid = require('uuid4')
|
|
34
|
+
const Logger = require('@mojaloop/central-services-logger')
|
|
35
|
+
const ErrorHandler = require('@mojaloop/central-services-error-handling')
|
|
36
|
+
const BulkTransferModels = require('@mojaloop/object-store-lib').Models.BulkTransfer
|
|
37
|
+
const Hash = require('@mojaloop/central-services-shared').Util.Hash
|
|
38
|
+
const HTTPENUM = require('@mojaloop/central-services-shared').Enum.Http
|
|
39
|
+
const TransferService = require('../../../domain/bulkTransfer')
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Operations on /bulkTransfers/{id}
|
|
43
|
+
*/
|
|
44
|
+
module.exports = {
|
|
45
|
+
/**
|
|
46
|
+
* summary: Get a bulk transfer by Id
|
|
47
|
+
* description:
|
|
48
|
+
* parameters: accept, content-type, date, x-forwarded-for, fspiop-source, fspiop-destination, fspiop-encryption, fspiop-signature, fspiop-uri, fspiop-http-method, id
|
|
49
|
+
* produces:
|
|
50
|
+
* responses: default
|
|
51
|
+
*/
|
|
52
|
+
get: async function getBulkTransfersId (request, h) {
|
|
53
|
+
try {
|
|
54
|
+
Logger.isInfoEnabled && Logger.info(`getBulkTransfersId::id(${request.params.id})`)
|
|
55
|
+
const messageId = Uuid()
|
|
56
|
+
await TransferService.getBulkTransferById(messageId, request.headers, request.params)
|
|
57
|
+
return h.response().code(HTTPENUM.ReturnCodes.ACCEPTED.CODE)
|
|
58
|
+
} catch (err) {
|
|
59
|
+
Logger.error(err)
|
|
60
|
+
throw ErrorHandler.Factory.reformatFSPIOPError(err)
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
/**
|
|
64
|
+
* summary: Fulfil bulkTransfer
|
|
65
|
+
* description:
|
|
66
|
+
* parameters: content-type, date, x-forwarded-for, fspiop-source, fspiop-destination, fspiop-encryption, fspiop-signature, fspiop-uri, fspiop-http-method, id, body
|
|
67
|
+
* produces:
|
|
68
|
+
* responses: default
|
|
69
|
+
*/
|
|
70
|
+
put: async function BulkTransfersByIDPut (request, h) {
|
|
71
|
+
try {
|
|
72
|
+
Logger.debug('create::payload(%s)', JSON.stringify(request.payload))
|
|
73
|
+
const bulkTransferId = request.params.id
|
|
74
|
+
const { bulkTransferState, completedTimestamp, extensionList } = request.payload
|
|
75
|
+
const hash = Hash.generateSha256(JSON.stringify(request.payload))
|
|
76
|
+
const messageId = Uuid()
|
|
77
|
+
/**
|
|
78
|
+
* Disabled writing to ML Object Store (bulkTransferFulfils) as it is not used:
|
|
79
|
+
*/
|
|
80
|
+
// const BulkTransferFulfilModel = BulkTransferModels.getBulkTransferFulfilModel()
|
|
81
|
+
// const doc = Object.assign({}, { messageId, headers: request.headers, bulkTransferId }, request.payload)
|
|
82
|
+
// await new BulkTransferFulfilModel(doc).save()
|
|
83
|
+
|
|
84
|
+
const IndividualTransferFulfilModel = BulkTransferModels.getIndividualTransferFulfilModel()
|
|
85
|
+
await Promise.all(request.payload.individualTransferResults.map(payload => {
|
|
86
|
+
new IndividualTransferFulfilModel({ messageId, bulkTransferId, payload }).save()
|
|
87
|
+
return null
|
|
88
|
+
}))
|
|
89
|
+
const count = request.payload.individualTransferResults.length
|
|
90
|
+
const message = { bulkTransferId, bulkTransferState, completedTimestamp, extensionList, count, hash }
|
|
91
|
+
await TransferService.bulkFulfil(messageId, request.headers, message)
|
|
92
|
+
return h.response().code(HTTPENUM.ReturnCodes.OK.CODE)
|
|
93
|
+
} catch (err) {
|
|
94
|
+
Logger.error(err)
|
|
95
|
+
throw ErrorHandler.Factory.reformatFSPIOPError(err)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|