lesgo 0.6.3 → 0.7.3
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/bin/lesgo-scripts.sh +31 -26
- package/package.json +2 -1
- package/src/middlewares/__tests__/errorHttpResponseMiddleware.spec.js +33 -16
- package/src/middlewares/__tests__/normalizeHttpRequestMiddleware.spec.js +24 -0
- package/src/middlewares/__tests__/successHttpResponseMiddleware.spec.js +28 -12
- package/src/middlewares/errorHttpResponseMiddleware.js +10 -3
- package/src/middlewares/gzipHttpResponse.js +1 -1
- package/src/middlewares/successHttpResponseMiddleware.js +9 -2
- package/src/services/AuroraDbRDSProxyService.js +183 -0
- package/src/services/ElasticsearchService.js +44 -65
- package/src/services/__tests__/AuroraDbRDSProxyService.spec.js +278 -0
- package/src/services/__tests__/LengthAwarePaginator.spec.js +44 -1
- package/src/services/pagination/LengthAwarePaginator.js +3 -2
- package/src/services/pagination/Paginator.js +21 -6
- package/src/utils/__tests__/db.spec.js +40 -1
- package/src/utils/db.js +13 -2
- package/src/utils/logger.js +1 -1
package/bin/lesgo-scripts.sh
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
|
+
set -Eeuo pipefail
|
|
4
|
+
|
|
3
5
|
###############################################################################
|
|
4
6
|
# #
|
|
5
7
|
# INITIALIZE EVERYTHING #
|
|
6
8
|
# #
|
|
7
9
|
###############################################################################
|
|
8
10
|
|
|
9
|
-
usage="$(basename "$0") [-f] [-s] [-t] [-h] [-d] [-y] -- script to deploy serverless functions
|
|
11
|
+
usage="$(basename "$0") [-f] [-s] [-t] [-h] [-d] [-c] [-y] -- script to deploy serverless functions
|
|
10
12
|
|
|
11
13
|
where:
|
|
12
14
|
-t define the type of action to be taken (build, deploy, invoke, logs, destroy)
|
|
@@ -15,22 +17,24 @@ where:
|
|
|
15
17
|
-h show this help text
|
|
16
18
|
-l to invoke a local function
|
|
17
19
|
-d set data parameters for invoke function
|
|
20
|
+
-c set config as per --config path-to-serverless-yml
|
|
18
21
|
-y no question/prompt for ci/cd"
|
|
19
22
|
|
|
20
23
|
# arg options
|
|
21
|
-
BUILD=0;
|
|
22
|
-
DEPLOY=0;
|
|
23
|
-
INVOKE=0;
|
|
24
|
-
LOGS=0;
|
|
25
|
-
DESTROY=0;
|
|
26
|
-
FUNCTION='';
|
|
27
|
-
STAGE='';
|
|
28
|
-
INVOKE_LOCAL=0;
|
|
29
|
-
DATA=''
|
|
30
|
-
NO_QUESTION=0;
|
|
24
|
+
BUILD=0; # serverless build without deploy
|
|
25
|
+
DEPLOY=0; # serverless deploy
|
|
26
|
+
INVOKE=0; # serverless invoke of specific function
|
|
27
|
+
LOGS=0; # serverless stream log of specific function
|
|
28
|
+
DESTROY=0; # serverless remove entire service
|
|
29
|
+
FUNCTION=''; # specify function to involve
|
|
30
|
+
STAGE=''; # deploy specific stage/environment
|
|
31
|
+
INVOKE_LOCAL=0; # default to non local execution
|
|
32
|
+
DATA='' # set the data parameters for invoke function
|
|
33
|
+
NO_QUESTION=0; # default to prompt
|
|
34
|
+
CONFIG='./serverless.yml'; # specify serverless config file to deploy, default to root
|
|
31
35
|
|
|
32
36
|
# parse the options
|
|
33
|
-
while getopts "lhs:f:t:d:y" OPT ; do
|
|
37
|
+
while getopts "lhs:f:t:d:c:y" OPT ; do
|
|
34
38
|
case ${OPT} in
|
|
35
39
|
f) FUNCTION=${OPTARG} ;;
|
|
36
40
|
s) STAGE=${OPTARG} ;;
|
|
@@ -51,6 +55,7 @@ while getopts "lhs:f:t:d:y" OPT ; do
|
|
|
51
55
|
fi;;
|
|
52
56
|
l) INVOKE_LOCAL=1 ;;
|
|
53
57
|
d) DATA=${OPTARG} ;;
|
|
58
|
+
c) CONFIG=${OPTARG} ;;
|
|
54
59
|
y) NO_QUESTION=1 ;;
|
|
55
60
|
h)
|
|
56
61
|
echo "${usage}"
|
|
@@ -100,8 +105,8 @@ function deploy_func_check ()
|
|
|
100
105
|
|
|
101
106
|
function deploy_func ()
|
|
102
107
|
{
|
|
103
|
-
echo -e "${YELLOW}Deploying ${FUNCTION} to ${STAGE}${NC}"
|
|
104
|
-
sls deploy -f ${FUNCTION} --stage ${STAGE} --env ${ENVFILE}
|
|
108
|
+
echo -e "${YELLOW}Deploying ${FUNCTION} to ${STAGE}${NC} using ${CONFIG}"
|
|
109
|
+
sls deploy -f ${FUNCTION} --stage ${STAGE} --env ${ENVFILE} --config ${CONFIG}
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
function prompt_confirmation_deploy_all ()
|
|
@@ -110,7 +115,7 @@ function prompt_confirmation_deploy_all ()
|
|
|
110
115
|
deploy_full;
|
|
111
116
|
else
|
|
112
117
|
while true; do
|
|
113
|
-
read -p "Confirm deploy service to ${STAGE} with .env.${ENVFILE}? [Y|N] " yn
|
|
118
|
+
read -p "Confirm deploy service to ${STAGE} with .env.${ENVFILE} using ${CONFIG}? [Y|N] " yn
|
|
114
119
|
case ${yn} in
|
|
115
120
|
[Yy] | yes | Yes | YES ) deploy_full; break;;
|
|
116
121
|
[Nn] | no | No | NO ) echo -e "${YELLOW}Cancelled deploying service to [${STAGE}]${NC}"; exit;;
|
|
@@ -123,7 +128,7 @@ function prompt_confirmation_deploy_all ()
|
|
|
123
128
|
function prompt_confirmation_deploy_function ()
|
|
124
129
|
{
|
|
125
130
|
while true; do
|
|
126
|
-
read -p "Confirm deploy function ${FUNCTION} to ${STAGE} with .env.${ENVFILE}? [Y|N] " yn
|
|
131
|
+
read -p "Confirm deploy function ${FUNCTION} to ${STAGE} with .env.${ENVFILE} using ${CONFIG}? [Y|N] " yn
|
|
127
132
|
case ${yn} in
|
|
128
133
|
[Yy] | yes | Yes | YES ) deploy_func; break;;
|
|
129
134
|
[Nn] | no | No | NO ) echo -e "${YELLOW}Cancelled deploying function ${FUNCTION} to [${STAGE}]${NC}"; exit;;
|
|
@@ -135,35 +140,35 @@ function prompt_confirmation_deploy_function ()
|
|
|
135
140
|
function deploy_full ()
|
|
136
141
|
{
|
|
137
142
|
echo -e "${YELLOW}Deploying service to ${STAGE}${NC}"
|
|
138
|
-
sls deploy --stage ${STAGE} --env ${ENVFILE}
|
|
143
|
+
sls deploy --stage ${STAGE} --env ${ENVFILE} --config ${CONFIG}
|
|
139
144
|
}
|
|
140
145
|
|
|
141
146
|
function invoke_func ()
|
|
142
147
|
{
|
|
143
|
-
echo -e "${YELLOW}Invoking function ${FUNCTION} on ${STAGE}${NC}"
|
|
148
|
+
echo -e "${YELLOW}Invoking function ${FUNCTION} on ${STAGE}${NC} using ${CONFIG}"
|
|
144
149
|
if [ ${INVOKE_LOCAL} == 1 ]; then
|
|
145
|
-
sls invoke local -f ${FUNCTION} --stage ${STAGE} --env ${ENVFILE} -d ${DATA} -l
|
|
150
|
+
sls invoke local -f ${FUNCTION} --stage ${STAGE} --env ${ENVFILE} -d ${DATA} -l --config ${CONFIG}
|
|
146
151
|
else
|
|
147
|
-
sls invoke -f ${FUNCTION} --stage ${STAGE} --env ${ENVFILE} -d ${DATA} -l
|
|
152
|
+
sls invoke -f ${FUNCTION} --stage ${STAGE} --env ${ENVFILE} -d ${DATA} -l --config ${CONFIG}
|
|
148
153
|
fi
|
|
149
154
|
}
|
|
150
155
|
|
|
151
156
|
function log_stream_func ()
|
|
152
157
|
{
|
|
153
|
-
echo -e "${YELLOW}Log Streaming function ${FUNCTION} on ${STAGE}${NC}"
|
|
154
|
-
sls logs -f ${FUNCTION} --stage ${STAGE} --env ${ENVFILE} -t
|
|
158
|
+
echo -e "${YELLOW}Log Streaming function ${FUNCTION} on ${STAGE}${NC} using ${CONFIG}"
|
|
159
|
+
sls logs -f ${FUNCTION} --stage ${STAGE} --env ${ENVFILE} -t --config ${CONFIG}
|
|
155
160
|
}
|
|
156
161
|
|
|
157
162
|
function build ()
|
|
158
163
|
{
|
|
159
|
-
echo -e "${YELLOW}Building bundle without deployment${NC}"
|
|
160
|
-
sls webpack --stage ${STAGE} --env ${ENVFILE}
|
|
164
|
+
echo -e "${YELLOW}Building bundle without deployment${NC} using ${CONFIG}"
|
|
165
|
+
sls webpack --stage ${STAGE} --env ${ENVFILE} --config ${CONFIG}
|
|
161
166
|
}
|
|
162
167
|
|
|
163
168
|
function prompt_confirmation_destroy_service ()
|
|
164
169
|
{
|
|
165
170
|
while true; do
|
|
166
|
-
read -p "Confirm destroy service to ${STAGE} with .env.${ENVFILE}? Note: This action will remove entire service from AWS. Ensure no other applications are using this service. [Y|N] " yn
|
|
171
|
+
read -p "Confirm destroy service to ${STAGE} with .env.${ENVFILE} using ${CONFIG}? Note: This action will remove entire service from AWS. Ensure no other applications are using this service. [Y|N] " yn
|
|
167
172
|
case ${yn} in
|
|
168
173
|
[Yy] | yes | Yes | YES ) destroy_service; break;;
|
|
169
174
|
[Nn] | no | No | NO ) echo -e "${YELLOW}Cancelled destroying service to [${STAGE}]${NC}"; exit;;
|
|
@@ -175,7 +180,7 @@ function prompt_confirmation_destroy_service ()
|
|
|
175
180
|
function destroy_service ()
|
|
176
181
|
{
|
|
177
182
|
echo -e "${YELLOW}Removing service to ${STAGE}${NC}"
|
|
178
|
-
sls remove --stage ${STAGE} --env ${ENVFILE}
|
|
183
|
+
sls remove --stage ${STAGE} --env ${ENVFILE} --config ${CONFIG}
|
|
179
184
|
}
|
|
180
185
|
|
|
181
186
|
###############################################################################
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lesgo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"description": "Core framework for lesgo node.js serverless framework.",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"author": "Sufiyan Rahmat",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"firebase-admin": "^9.3.0",
|
|
58
58
|
"jsonwebtoken": "^8.5.1",
|
|
59
59
|
"memcached-elasticache": "^1.1.1",
|
|
60
|
+
"mysql2": "^2.2.5",
|
|
60
61
|
"nanoid": "^3.1.16"
|
|
61
62
|
},
|
|
62
63
|
"husky": {
|
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
import ValidationErrorException from '../__mocks__/ValidationErrorException';
|
|
6
6
|
|
|
7
7
|
describe('MiddlewareGroup: test errorHandler middleware', () => {
|
|
8
|
-
it('test with thrown Error', () => {
|
|
9
|
-
const data = errorHttpResponseHandler({
|
|
8
|
+
it('test with thrown Error', async () => {
|
|
9
|
+
const data = await errorHttpResponseHandler({
|
|
10
10
|
error: new Error('Test validation error'),
|
|
11
11
|
});
|
|
12
12
|
|
|
@@ -29,8 +29,8 @@ describe('MiddlewareGroup: test errorHandler middleware', () => {
|
|
|
29
29
|
expect(dataBody).toHaveProperty('error.details', '');
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
it('test with thrown custom Error with default parameters', () => {
|
|
33
|
-
const data = errorHttpResponseHandler({
|
|
32
|
+
it('test with thrown custom Error with default parameters', async () => {
|
|
33
|
+
const data = await errorHttpResponseHandler({
|
|
34
34
|
error: new ValidationErrorException('Test validation error'),
|
|
35
35
|
});
|
|
36
36
|
const dataBody = JSON.parse(data.body);
|
|
@@ -45,8 +45,8 @@ describe('MiddlewareGroup: test errorHandler middleware', () => {
|
|
|
45
45
|
expect(dataBody).toHaveProperty('error.details', '');
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
it('test with thrown custom Error with given parameters', () => {
|
|
49
|
-
const data = errorHttpResponseHandler({
|
|
48
|
+
it('test with thrown custom Error with given parameters', async () => {
|
|
49
|
+
const data = await errorHttpResponseHandler({
|
|
50
50
|
error: new ValidationErrorException(
|
|
51
51
|
'Test validation error',
|
|
52
52
|
'VALIDATION_ERROR_SAMPLE',
|
|
@@ -70,8 +70,10 @@ describe('MiddlewareGroup: test errorHandler middleware', () => {
|
|
|
70
70
|
});
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
-
it('test with error message', () => {
|
|
74
|
-
const data = errorHttpResponseHandler({
|
|
73
|
+
it('test with error message', async () => {
|
|
74
|
+
const data = await errorHttpResponseHandler({
|
|
75
|
+
error: 'Test error message',
|
|
76
|
+
});
|
|
75
77
|
const dataBody = JSON.parse(data.body);
|
|
76
78
|
|
|
77
79
|
expect(data.statusCode).toBe(500);
|
|
@@ -81,8 +83,8 @@ describe('MiddlewareGroup: test errorHandler middleware', () => {
|
|
|
81
83
|
expect(dataBody).toHaveProperty('error.details', '');
|
|
82
84
|
});
|
|
83
85
|
|
|
84
|
-
it('test with error message with event', () => {
|
|
85
|
-
const data = errorHttpResponseHandler({
|
|
86
|
+
it('test with error message with event', async () => {
|
|
87
|
+
const data = await errorHttpResponseHandler({
|
|
86
88
|
error: 'Test error message',
|
|
87
89
|
event: {
|
|
88
90
|
someEventKey: 'someEventValue',
|
|
@@ -97,8 +99,8 @@ describe('MiddlewareGroup: test errorHandler middleware', () => {
|
|
|
97
99
|
});
|
|
98
100
|
});
|
|
99
101
|
|
|
100
|
-
it('test with error message with event in non-debug mode', () => {
|
|
101
|
-
const data = errorHttpResponseHandler({
|
|
102
|
+
it('test with error message with event in non-debug mode', async () => {
|
|
103
|
+
const data = await errorHttpResponseHandler({
|
|
102
104
|
error: 'Test error message',
|
|
103
105
|
event: {
|
|
104
106
|
someEventKey: 'someEventValue',
|
|
@@ -110,8 +112,8 @@ describe('MiddlewareGroup: test errorHandler middleware', () => {
|
|
|
110
112
|
expect(dataBody).toHaveProperty('_meta', {});
|
|
111
113
|
});
|
|
112
114
|
|
|
113
|
-
it('test with undefined opts', () => {
|
|
114
|
-
const data = errorHttpResponseHandler();
|
|
115
|
+
it('test with undefined opts', async () => {
|
|
116
|
+
const data = await errorHttpResponseHandler();
|
|
115
117
|
|
|
116
118
|
expect(data.headers['Access-Control-Allow-Origin']).toBe('*');
|
|
117
119
|
expect(data.headers['Cache-Control']).toBe('no-cache');
|
|
@@ -128,16 +130,31 @@ describe('MiddlewareGroup: test errorHandler middleware', () => {
|
|
|
128
130
|
expect(dataBody).toHaveProperty('error.message', '');
|
|
129
131
|
expect(dataBody).toHaveProperty('error.details', '');
|
|
130
132
|
});
|
|
133
|
+
|
|
134
|
+
it('should call db.end() whenever a db options is set', async () => {
|
|
135
|
+
const end = jest.fn().mockResolvedValue();
|
|
136
|
+
await errorHttpResponseHandler({
|
|
137
|
+
error: 'Test error message',
|
|
138
|
+
event: {
|
|
139
|
+
someEventKey: 'someEventValue',
|
|
140
|
+
},
|
|
141
|
+
db: {
|
|
142
|
+
end,
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
expect(end).toHaveBeenCalledTimes(1);
|
|
147
|
+
});
|
|
131
148
|
});
|
|
132
149
|
|
|
133
150
|
describe('MiddlewareGroup: test errorHttpResponseAfterHandler', () => {
|
|
134
|
-
it('test with default parameters', () => {
|
|
151
|
+
it('test with default parameters', async () => {
|
|
135
152
|
const handler = {
|
|
136
153
|
error: {},
|
|
137
154
|
event: {},
|
|
138
155
|
};
|
|
139
156
|
|
|
140
|
-
errorHttpResponseAfterHandler(handler, () => {});
|
|
157
|
+
await errorHttpResponseAfterHandler(handler, () => {});
|
|
141
158
|
expect(handler.response).toHaveProperty('statusCode', 500);
|
|
142
159
|
});
|
|
143
160
|
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import app from 'Config/app'; // eslint-disable-line import/no-unresolved
|
|
1
2
|
import {
|
|
2
3
|
normalizeRequest,
|
|
3
4
|
normalizeHttpRequestBeforeHandler,
|
|
4
5
|
} from '../normalizeHttpRequestMiddleware';
|
|
6
|
+
import logger from '../../utils/logger';
|
|
5
7
|
|
|
6
8
|
describe('MiddlewareGroup: test normalizeRequest', () => {
|
|
7
9
|
it('test with default parameters', () => {
|
|
@@ -116,4 +118,26 @@ describe('MiddlewareGroup: test normalizeHttpRequestBeforeHandler', () => {
|
|
|
116
118
|
normalizeHttpRequestBeforeHandler(handler, () => {});
|
|
117
119
|
expect(handler.event.auth.sub).toBe('f2b5349d-f5e3-44f5-9c08-ae6b01e95434');
|
|
118
120
|
});
|
|
121
|
+
|
|
122
|
+
it('should not set meta on debug', () => {
|
|
123
|
+
app.debug = true;
|
|
124
|
+
const handler = {
|
|
125
|
+
event: {
|
|
126
|
+
headers: {},
|
|
127
|
+
auth: '1',
|
|
128
|
+
queryStringParameters: {
|
|
129
|
+
foo: 'bar',
|
|
130
|
+
},
|
|
131
|
+
body: {
|
|
132
|
+
test: 'body',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
normalizeHttpRequestBeforeHandler(handler, () => {});
|
|
137
|
+
expect(logger.meta.auth).toBe(handler.event.auth);
|
|
138
|
+
expect(logger.meta.queryStringParameters).toStrictEqual(
|
|
139
|
+
handler.event.queryStringParameters
|
|
140
|
+
);
|
|
141
|
+
expect(logger.meta.body).toStrictEqual(handler.event.body);
|
|
142
|
+
});
|
|
119
143
|
});
|
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
} from '../successHttpResponseMiddleware';
|
|
5
5
|
|
|
6
6
|
describe('MiddlewareGroup: test successHttpResponseHandler middleware', () => {
|
|
7
|
-
it('test default without parameters', () => {
|
|
8
|
-
const data = successHttpResponseHandler();
|
|
7
|
+
it('test default without parameters', async () => {
|
|
8
|
+
const data = await successHttpResponseHandler();
|
|
9
9
|
|
|
10
10
|
expect(data.headers['Access-Control-Allow-Origin']).toBe('*');
|
|
11
11
|
expect(data.headers['Cache-Control']).toBe('no-cache');
|
|
@@ -20,8 +20,8 @@ describe('MiddlewareGroup: test successHttpResponseHandler middleware', () => {
|
|
|
20
20
|
expect(dataBody).toHaveProperty('_meta', {});
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
it('test default', () => {
|
|
24
|
-
const data = successHttpResponseHandler({ response: 'Some message' });
|
|
23
|
+
it('test default', async () => {
|
|
24
|
+
const data = await successHttpResponseHandler({ response: 'Some message' });
|
|
25
25
|
|
|
26
26
|
expect(data.headers['Access-Control-Allow-Origin']).toBe('*');
|
|
27
27
|
expect(data.headers['Cache-Control']).toBe('no-cache');
|
|
@@ -36,8 +36,8 @@ describe('MiddlewareGroup: test successHttpResponseHandler middleware', () => {
|
|
|
36
36
|
expect(dataBody).toHaveProperty('_meta', {});
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
it('test with status code and event', () => {
|
|
40
|
-
const data = successHttpResponseHandler({
|
|
39
|
+
it('test with status code and event', async () => {
|
|
40
|
+
const data = await successHttpResponseHandler({
|
|
41
41
|
response: 'Some message',
|
|
42
42
|
statusCode: 201,
|
|
43
43
|
event: {
|
|
@@ -53,8 +53,8 @@ describe('MiddlewareGroup: test successHttpResponseHandler middleware', () => {
|
|
|
53
53
|
expect(dataBody).toHaveProperty('_meta', {});
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
-
it('test with status code and event in debug mode', () => {
|
|
57
|
-
const data = successHttpResponseHandler({
|
|
56
|
+
it('test with status code and event in debug mode', async () => {
|
|
57
|
+
const data = await successHttpResponseHandler({
|
|
58
58
|
response: 'Some message',
|
|
59
59
|
statusCode: 201,
|
|
60
60
|
event: {
|
|
@@ -73,8 +73,8 @@ describe('MiddlewareGroup: test successHttpResponseHandler middleware', () => {
|
|
|
73
73
|
});
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
it('test with configurable header', () => {
|
|
77
|
-
const data = successHttpResponseHandler({
|
|
76
|
+
it('test with configurable header', async () => {
|
|
77
|
+
const data = await successHttpResponseHandler({
|
|
78
78
|
response: 'Some message',
|
|
79
79
|
headers: {
|
|
80
80
|
'Access-Control-Allow-Credentials': false,
|
|
@@ -96,16 +96,32 @@ describe('MiddlewareGroup: test successHttpResponseHandler middleware', () => {
|
|
|
96
96
|
expect(dataBody).toHaveProperty('data', 'Some message');
|
|
97
97
|
expect(dataBody).toHaveProperty('_meta', {});
|
|
98
98
|
});
|
|
99
|
+
|
|
100
|
+
it('should call db.end() whenever a db options is set', async () => {
|
|
101
|
+
const end = jest.fn().mockResolvedValue();
|
|
102
|
+
await successHttpResponseHandler({
|
|
103
|
+
response: 'Some message',
|
|
104
|
+
headers: {
|
|
105
|
+
'Access-Control-Allow-Credentials': false,
|
|
106
|
+
'X-Token-Id': 'token',
|
|
107
|
+
},
|
|
108
|
+
db: {
|
|
109
|
+
end,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(end).toHaveBeenCalledTimes(1);
|
|
114
|
+
});
|
|
99
115
|
});
|
|
100
116
|
|
|
101
117
|
describe('MiddlewareGroup: test successHttpResponseAfterHandler', () => {
|
|
102
|
-
it('test with default parameters', () => {
|
|
118
|
+
it('test with default parameters', async () => {
|
|
103
119
|
const handler = {
|
|
104
120
|
response: {},
|
|
105
121
|
event: {},
|
|
106
122
|
};
|
|
107
123
|
|
|
108
|
-
successHttpResponseAfterHandler(handler, () => {});
|
|
124
|
+
await successHttpResponseAfterHandler(handler, () => {});
|
|
109
125
|
expect(handler.response).toHaveProperty('statusCode', 200);
|
|
110
126
|
});
|
|
111
127
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import logger from '../utils/logger';
|
|
2
|
+
import isEmpty from '../utils/isEmpty';
|
|
2
3
|
|
|
3
|
-
export const errorHttpResponseHandler = opts => {
|
|
4
|
+
export const errorHttpResponseHandler = async opts => {
|
|
4
5
|
const defaults = {
|
|
5
6
|
response: '',
|
|
6
7
|
statusCode: 500,
|
|
@@ -47,6 +48,12 @@ export const errorHttpResponseHandler = opts => {
|
|
|
47
48
|
logger.warn(options.error);
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
try {
|
|
52
|
+
if (!isEmpty(opts.db)) await opts.db.end();
|
|
53
|
+
} catch (err) {
|
|
54
|
+
// do nothing
|
|
55
|
+
}
|
|
56
|
+
|
|
50
57
|
return {
|
|
51
58
|
headers: options.headers,
|
|
52
59
|
statusCode,
|
|
@@ -54,7 +61,7 @@ export const errorHttpResponseHandler = opts => {
|
|
|
54
61
|
};
|
|
55
62
|
};
|
|
56
63
|
|
|
57
|
-
export const errorHttpResponseAfterHandler = (handler, next, opts) => {
|
|
64
|
+
export const errorHttpResponseAfterHandler = async (handler, next, opts) => {
|
|
58
65
|
const defaults = {
|
|
59
66
|
error: handler.error,
|
|
60
67
|
event: handler.event,
|
|
@@ -64,7 +71,7 @@ export const errorHttpResponseAfterHandler = (handler, next, opts) => {
|
|
|
64
71
|
const options = { ...defaults, ...opts };
|
|
65
72
|
|
|
66
73
|
// eslint-disable-next-line no-param-reassign
|
|
67
|
-
handler.response = errorHttpResponseHandler(options);
|
|
74
|
+
handler.response = await errorHttpResponseHandler(options);
|
|
68
75
|
/* istanbul ignore next */
|
|
69
76
|
next();
|
|
70
77
|
};
|
|
@@ -31,7 +31,7 @@ export const gzip = async response => {
|
|
|
31
31
|
* Determine request origin
|
|
32
32
|
*/
|
|
33
33
|
export const determineRequestOrigin = handler => {
|
|
34
|
-
const { requestContext } = handler.event;
|
|
34
|
+
const { requestContext = {} } = handler.event;
|
|
35
35
|
let requestFrom = 'APIGATEWAY';
|
|
36
36
|
|
|
37
37
|
if (requestContext.elb) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import gzipHttpResponse from './gzipHttpResponse';
|
|
2
|
+
import isEmpty from '../utils/isEmpty';
|
|
2
3
|
|
|
3
|
-
export const successHttpResponseHandler = opts => {
|
|
4
|
+
export const successHttpResponseHandler = async opts => {
|
|
4
5
|
const defaults = {
|
|
5
6
|
response: '',
|
|
6
7
|
statusCode: 200,
|
|
@@ -23,6 +24,12 @@ export const successHttpResponseHandler = opts => {
|
|
|
23
24
|
|
|
24
25
|
const options = { ...defaults, ...optionsHeadersMerged };
|
|
25
26
|
|
|
27
|
+
try {
|
|
28
|
+
if (!isEmpty(opts.db)) await opts.db.end();
|
|
29
|
+
} catch (err) {
|
|
30
|
+
// do nothing
|
|
31
|
+
}
|
|
32
|
+
|
|
26
33
|
return {
|
|
27
34
|
headers: options.headers,
|
|
28
35
|
statusCode: options.statusCode,
|
|
@@ -43,7 +50,7 @@ export const successHttpResponseAfterHandler = async (handler, next, opts) => {
|
|
|
43
50
|
const options = { ...defaults, ...opts };
|
|
44
51
|
|
|
45
52
|
// eslint-disable-next-line no-param-reassign
|
|
46
|
-
handler.response = successHttpResponseHandler(options);
|
|
53
|
+
handler.response = await successHttpResponseHandler(options);
|
|
47
54
|
|
|
48
55
|
// eslint-disable-next-line no-param-reassign
|
|
49
56
|
handler.response = await gzipHttpResponse(handler, opts);
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import mysql from 'mysql2/promise';
|
|
2
|
+
import logger from '../utils/logger';
|
|
3
|
+
import isEmpty from '../utils/isEmpty';
|
|
4
|
+
import LesgoException from '../exceptions/LesgoException';
|
|
5
|
+
import LengthAwarePaginator from './pagination/LengthAwarePaginator';
|
|
6
|
+
import Paginator from './pagination/Paginator';
|
|
7
|
+
|
|
8
|
+
const FILE = 'Lesgo/services/AuroraDbRDSProxyService';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Service to connect to AWS Aurora Provisioned MySQL Database via RDS Proxy.
|
|
12
|
+
* Use Services/AuroraDbServerlessService for the Serverless type via Data API.
|
|
13
|
+
*/
|
|
14
|
+
export default class AuroraDbRDSProxyService {
|
|
15
|
+
constructor(opts = {}) {
|
|
16
|
+
const { host, user, password, database, persists } = opts;
|
|
17
|
+
|
|
18
|
+
this.clientOpts = {
|
|
19
|
+
host,
|
|
20
|
+
user,
|
|
21
|
+
password,
|
|
22
|
+
database,
|
|
23
|
+
persists,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
this.conn = {};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Persists the database connection for later use
|
|
31
|
+
*
|
|
32
|
+
* @param {*} connectionOpts
|
|
33
|
+
* @returns
|
|
34
|
+
*/
|
|
35
|
+
async pConnect(connectionOpts = {}) {
|
|
36
|
+
await this.connect({ ...connectionOpts, persists: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async connect(connectionOpts = {}) {
|
|
40
|
+
const clientOpts = {
|
|
41
|
+
host: connectionOpts.host ? connectionOpts.host : this.clientOpts.host,
|
|
42
|
+
user: connectionOpts.user ? connectionOpts.user : this.clientOpts.user,
|
|
43
|
+
password: connectionOpts.password
|
|
44
|
+
? connectionOpts.password
|
|
45
|
+
: this.clientOpts.password,
|
|
46
|
+
database: connectionOpts.database
|
|
47
|
+
? connectionOpts.database
|
|
48
|
+
: this.clientOpts.database,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const persistentConn = connectionOpts.persists || this.clientOpts.persists;
|
|
52
|
+
|
|
53
|
+
logger.debug(`${FILE}::PREPARING DB CONNECTION`, {
|
|
54
|
+
clientOpts,
|
|
55
|
+
persistentConn,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const conn = await mysql.createConnection(clientOpts);
|
|
59
|
+
conn.config.namedPlaceholders = true;
|
|
60
|
+
logger.debug(`${FILE}::DB CONNECTED`);
|
|
61
|
+
|
|
62
|
+
if (persistentConn) {
|
|
63
|
+
this.conn = conn;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return conn;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* eslint-disable-next-line class-methods-use-this */
|
|
70
|
+
async end(conn = {}) {
|
|
71
|
+
logger.debug(`${FILE}::ENDING DB CONNECTION`);
|
|
72
|
+
if (!isEmpty(conn)) {
|
|
73
|
+
await conn.end();
|
|
74
|
+
logger.debug(`${FILE}::DB DISCONNECTED`);
|
|
75
|
+
} else {
|
|
76
|
+
await this.conn.end();
|
|
77
|
+
logger.debug(`${FILE}::PERSISTED DB DISCONNECTED`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async query(sql, sqlParams, connectionOpts = {}) {
|
|
82
|
+
let conn = {};
|
|
83
|
+
if (!isEmpty(connectionOpts)) {
|
|
84
|
+
conn = await this.connect(connectionOpts);
|
|
85
|
+
} else if (isEmpty(this.conn)) {
|
|
86
|
+
conn = await this.connect();
|
|
87
|
+
} else {
|
|
88
|
+
conn = this.conn;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
logger.debug(`${FILE}::QUERYING_DB`, { sql, sqlParams });
|
|
93
|
+
const [results, fields] = await conn.execute(sql, sqlParams);
|
|
94
|
+
logger.debug(`${FILE}::DB_RESPONSE`, { results, fields });
|
|
95
|
+
|
|
96
|
+
return { results, fields };
|
|
97
|
+
} catch (err) {
|
|
98
|
+
throw new LesgoException(
|
|
99
|
+
'Exception caught executing SQL Statement',
|
|
100
|
+
`${FILE}::QUERY_EXECUTION_EXCEPTION`,
|
|
101
|
+
500,
|
|
102
|
+
{ err }
|
|
103
|
+
);
|
|
104
|
+
} finally {
|
|
105
|
+
if (isEmpty(this.conn) || !isEmpty(connectionOpts)) await this.end(conn);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async select(sql, sqlParams, connectionOpts = {}) {
|
|
110
|
+
const resp = await this.query(sql, sqlParams, connectionOpts);
|
|
111
|
+
return resp.results;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async selectFirst(sql, sqlParams, connectionOpts = {}) {
|
|
115
|
+
const resp = await this.query(sql, sqlParams, connectionOpts);
|
|
116
|
+
return resp.results[0];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async selectPaginate(
|
|
120
|
+
sql,
|
|
121
|
+
sqlParams,
|
|
122
|
+
perPage = 10,
|
|
123
|
+
currentPage = 1,
|
|
124
|
+
total = null,
|
|
125
|
+
connectionOpts = {}
|
|
126
|
+
) {
|
|
127
|
+
let paginator;
|
|
128
|
+
if (typeof total === 'number') {
|
|
129
|
+
paginator = new LengthAwarePaginator(
|
|
130
|
+
this,
|
|
131
|
+
sql,
|
|
132
|
+
sqlParams,
|
|
133
|
+
{
|
|
134
|
+
perPage,
|
|
135
|
+
currentPage,
|
|
136
|
+
total,
|
|
137
|
+
},
|
|
138
|
+
connectionOpts
|
|
139
|
+
);
|
|
140
|
+
} else {
|
|
141
|
+
paginator = new Paginator(
|
|
142
|
+
this,
|
|
143
|
+
sql,
|
|
144
|
+
sqlParams,
|
|
145
|
+
{
|
|
146
|
+
perPage,
|
|
147
|
+
currentPage,
|
|
148
|
+
},
|
|
149
|
+
connectionOpts
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return (await paginator).toObject();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async insert(sql, sqlParams, connectionOpts = {}) {
|
|
157
|
+
const resp = await this.query(sql, sqlParams, connectionOpts);
|
|
158
|
+
|
|
159
|
+
if (resp.results.affectedRows <= 0) {
|
|
160
|
+
throw new LesgoException(
|
|
161
|
+
'No records inserted from INSERT query',
|
|
162
|
+
`${FILE}::NO_RECORDS_INSERTED`,
|
|
163
|
+
400,
|
|
164
|
+
{ resp, sql, sqlParams }
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return resp.results.insertId;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async update(sql, sqlParams, connectionOpts = {}) {
|
|
172
|
+
const resp = await this.query(sql, sqlParams, connectionOpts);
|
|
173
|
+
|
|
174
|
+
if (resp.results.changedRows <= 0) {
|
|
175
|
+
logger.warn(`${FILE}::No records updated from UPDATE query`, {
|
|
176
|
+
sql,
|
|
177
|
+
sqlParams,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return Promise.resolve();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -39,22 +39,17 @@ class ElasticsearchService {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
search(body) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
this.result = response;
|
|
55
|
-
|
|
56
|
-
resolve(response);
|
|
57
|
-
});
|
|
42
|
+
const param = {
|
|
43
|
+
index: this.index,
|
|
44
|
+
type: this.type,
|
|
45
|
+
body,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return this.client.search(param, (err, response) => {
|
|
49
|
+
if (err) return Promise.reject(err);
|
|
50
|
+
|
|
51
|
+
this.result = response;
|
|
52
|
+
return Promise.resolve(response);
|
|
58
53
|
});
|
|
59
54
|
}
|
|
60
55
|
|
|
@@ -75,11 +70,9 @@ class ElasticsearchService {
|
|
|
75
70
|
body: settings,
|
|
76
71
|
};
|
|
77
72
|
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
err ? /* istanbul ignore next */ reject(err) : resolve(response);
|
|
82
|
-
});
|
|
73
|
+
return this.client.indices.create(params, (err, response) => {
|
|
74
|
+
if (err) return Promise.reject(err);
|
|
75
|
+
return Promise.resolve(response);
|
|
83
76
|
});
|
|
84
77
|
}
|
|
85
78
|
|
|
@@ -89,31 +82,27 @@ class ElasticsearchService {
|
|
|
89
82
|
...options,
|
|
90
83
|
};
|
|
91
84
|
|
|
92
|
-
return
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
err ? /* istanbul ignore next */ reject(err) : resolve(response);
|
|
96
|
-
});
|
|
85
|
+
return this.client.indices.delete(params, (err, response) => {
|
|
86
|
+
if (err) return Promise.reject(err);
|
|
87
|
+
return Promise.resolve(response);
|
|
97
88
|
});
|
|
98
89
|
}
|
|
99
90
|
|
|
100
91
|
existIndices(index, options = {}) {
|
|
101
92
|
const params = { index, ...options };
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
});
|
|
93
|
+
|
|
94
|
+
return this.client.indices.exists(params, (err, response) => {
|
|
95
|
+
if (err) return Promise.reject(err);
|
|
96
|
+
return Promise.resolve(response.body);
|
|
107
97
|
});
|
|
108
98
|
}
|
|
109
99
|
|
|
110
100
|
putMapping(index, type, body) {
|
|
111
101
|
const params = { index, type, body: { properties: body } };
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
});
|
|
102
|
+
|
|
103
|
+
return this.client.indices.putMapping(params, (err, response) => {
|
|
104
|
+
if (err) return Promise.reject(err);
|
|
105
|
+
return Promise.resolve(response);
|
|
117
106
|
});
|
|
118
107
|
}
|
|
119
108
|
|
|
@@ -124,11 +113,9 @@ class ElasticsearchService {
|
|
|
124
113
|
id,
|
|
125
114
|
};
|
|
126
115
|
|
|
127
|
-
return
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
err ? /* istanbul ignore next */ reject(err) : resolve(response);
|
|
131
|
-
});
|
|
116
|
+
return this.client.get(params, (err, response) => {
|
|
117
|
+
if (err) return Promise.reject(err);
|
|
118
|
+
return Promise.resolve(response);
|
|
132
119
|
});
|
|
133
120
|
}
|
|
134
121
|
|
|
@@ -141,24 +128,20 @@ class ElasticsearchService {
|
|
|
141
128
|
refresh,
|
|
142
129
|
};
|
|
143
130
|
|
|
144
|
-
return
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
err ? /* istanbul ignore next */ reject(err) : resolve(response);
|
|
148
|
-
});
|
|
131
|
+
return this.client.index(params, (err, response) => {
|
|
132
|
+
if (err) return Promise.reject(err);
|
|
133
|
+
return Promise.resolve(response);
|
|
149
134
|
});
|
|
150
135
|
}
|
|
151
136
|
|
|
152
137
|
bulkIndex(bodies) {
|
|
153
|
-
return
|
|
154
|
-
this.
|
|
155
|
-
|
|
156
|
-
(err
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
);
|
|
161
|
-
});
|
|
138
|
+
return this.client.bulk(
|
|
139
|
+
{ body: this.constructBulkIndex(bodies) },
|
|
140
|
+
(err, response) => {
|
|
141
|
+
if (err) return Promise.reject(err);
|
|
142
|
+
return Promise.resolve(response);
|
|
143
|
+
}
|
|
144
|
+
);
|
|
162
145
|
}
|
|
163
146
|
|
|
164
147
|
create(id, body) {
|
|
@@ -169,11 +152,9 @@ class ElasticsearchService {
|
|
|
169
152
|
body,
|
|
170
153
|
};
|
|
171
154
|
|
|
172
|
-
return
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
err ? /* istanbul ignore next */ reject(err) : resolve(response);
|
|
176
|
-
});
|
|
155
|
+
return this.client.index(params, (err, response) => {
|
|
156
|
+
if (err) return Promise.reject(err);
|
|
157
|
+
return Promise.resolve(response);
|
|
177
158
|
});
|
|
178
159
|
}
|
|
179
160
|
|
|
@@ -184,11 +165,9 @@ class ElasticsearchService {
|
|
|
184
165
|
id,
|
|
185
166
|
};
|
|
186
167
|
|
|
187
|
-
return
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
err ? /* istanbul ignore next */ reject(err) : resolve(response);
|
|
191
|
-
});
|
|
168
|
+
return this.client.get(params, (err, response) => {
|
|
169
|
+
if (err) return Promise.reject(err);
|
|
170
|
+
return Promise.resolve(response);
|
|
192
171
|
});
|
|
193
172
|
}
|
|
194
173
|
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import mysql from 'mysql2/promise';
|
|
2
|
+
import AuroraDbRDSProxyService from '../AuroraDbRDSProxyService';
|
|
3
|
+
import LesgoException from '../../exceptions/LesgoException';
|
|
4
|
+
|
|
5
|
+
const auroraConfig = {
|
|
6
|
+
host: 'some-fake-host',
|
|
7
|
+
user: 'fakeUsername',
|
|
8
|
+
password: 'fakePassword',
|
|
9
|
+
database: 'fakeDbName',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const customConfig = {
|
|
13
|
+
host: 'some-fake-host-2',
|
|
14
|
+
user: 'fakeUsername2',
|
|
15
|
+
password: 'fakePassword2',
|
|
16
|
+
database: 'fakeDbName2',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
describe('test AuroraDbRDSProxyService instantiate', () => {
|
|
20
|
+
it('should not throw exception when instantiating', () => {
|
|
21
|
+
const db = new AuroraDbRDSProxyService(auroraConfig);
|
|
22
|
+
expect(db.clientOpts).toMatchObject(auroraConfig);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should allow for empty config', () => {
|
|
26
|
+
const db = new AuroraDbRDSProxyService();
|
|
27
|
+
expect(db.clientOpts).toMatchObject({});
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('test AuroraDbRDSProxyService connect', () => {
|
|
32
|
+
it('should be able to connect without custom config', async () => {
|
|
33
|
+
const db = new AuroraDbRDSProxyService(auroraConfig);
|
|
34
|
+
const conn = await db.connect();
|
|
35
|
+
|
|
36
|
+
expect(conn.mocked).toMatchObject(auroraConfig);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should be able to connect with custom config', async () => {
|
|
40
|
+
const db = new AuroraDbRDSProxyService(auroraConfig);
|
|
41
|
+
const conn = await db.connect(customConfig);
|
|
42
|
+
|
|
43
|
+
expect(conn.mocked).toMatchObject(customConfig);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('test AuroraDbRDSProxyService query', () => {
|
|
48
|
+
it('should return results object when calling query function', async () => {
|
|
49
|
+
const db = new AuroraDbRDSProxyService(auroraConfig);
|
|
50
|
+
const resp = await db.query('SELECT_QUERY', {});
|
|
51
|
+
|
|
52
|
+
expect(resp).toMatchObject({
|
|
53
|
+
results: [
|
|
54
|
+
{
|
|
55
|
+
id: 1,
|
|
56
|
+
uid: 'some-uid-1',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 2,
|
|
60
|
+
uid: 'some-uid-2',
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(mysql.createConnection).toHaveBeenCalled();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should return results object when calling querying with connection options passed', async () => {
|
|
69
|
+
const db = new AuroraDbRDSProxyService(auroraConfig);
|
|
70
|
+
const resp = await db.query('SELECT_QUERY', {}, auroraConfig);
|
|
71
|
+
|
|
72
|
+
expect(resp).toMatchObject({
|
|
73
|
+
results: [
|
|
74
|
+
{
|
|
75
|
+
id: 1,
|
|
76
|
+
uid: 'some-uid-1',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 2,
|
|
80
|
+
uid: 'some-uid-2',
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(mysql.createConnection).toHaveBeenCalled();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should return results object when calling querying with no connection options passed and persistent conn', async () => {
|
|
89
|
+
const db = new AuroraDbRDSProxyService(auroraConfig);
|
|
90
|
+
await db.pConnect();
|
|
91
|
+
const resp = await db.query('SELECT_QUERY', {});
|
|
92
|
+
|
|
93
|
+
expect(resp).toMatchObject({
|
|
94
|
+
results: [
|
|
95
|
+
{
|
|
96
|
+
id: 1,
|
|
97
|
+
uid: 'some-uid-1',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: 2,
|
|
101
|
+
uid: 'some-uid-2',
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(mysql.createConnection).toHaveBeenCalled();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('test AuroraDbRDSProxyService select', () => {
|
|
111
|
+
it('should return array results when calling select function', async () => {
|
|
112
|
+
const db = new AuroraDbRDSProxyService(auroraConfig);
|
|
113
|
+
return expect(db.select('SELECT_QUERY', {})).resolves.toMatchObject([
|
|
114
|
+
{
|
|
115
|
+
id: 1,
|
|
116
|
+
uid: 'some-uid-1',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: 2,
|
|
120
|
+
uid: 'some-uid-2',
|
|
121
|
+
},
|
|
122
|
+
]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should throw an exception when writing with invalid query', async () => {
|
|
126
|
+
const error = new LesgoException(
|
|
127
|
+
'Exception caught executing SQL Statement',
|
|
128
|
+
'AURORADBSERVICE_QUERY_EXCEPTION',
|
|
129
|
+
500,
|
|
130
|
+
{
|
|
131
|
+
err: {
|
|
132
|
+
code: 'BadRequestException',
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const db = new AuroraDbRDSProxyService(auroraConfig);
|
|
138
|
+
return expect(db.select('INVALID_QUERY', {})).rejects.toMatchObject(error);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should throw an exception when calling select with invalid query parameters', async () => {
|
|
142
|
+
const error = new LesgoException(
|
|
143
|
+
'Exception caught executing SQL Statement',
|
|
144
|
+
'AURORADBSERVICE_QUERY_EXCEPTION',
|
|
145
|
+
500,
|
|
146
|
+
{
|
|
147
|
+
err: {
|
|
148
|
+
code: 'BadRequestException',
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const db = new AuroraDbRDSProxyService(auroraConfig);
|
|
154
|
+
return expect(
|
|
155
|
+
db.select('RANDOM_QUERY', 'INVALID_QUERY_PARAMETERS')
|
|
156
|
+
).rejects.toMatchObject(error);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('test AuroraDbRDSProxyService selectPaginate', () => {
|
|
161
|
+
it('should return paginated results when calling selectPaginate function', async () => {
|
|
162
|
+
const db = new AuroraDbRDSProxyService(auroraConfig);
|
|
163
|
+
return expect(db.selectPaginate('SELECT_QUERY', {})).resolves.toMatchObject(
|
|
164
|
+
{
|
|
165
|
+
count: 2,
|
|
166
|
+
previous_page: false,
|
|
167
|
+
current_page: 1,
|
|
168
|
+
next_page: false,
|
|
169
|
+
per_page: 10,
|
|
170
|
+
items: [
|
|
171
|
+
{
|
|
172
|
+
id: 1,
|
|
173
|
+
uid: 'some-uid-1',
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: 2,
|
|
177
|
+
uid: 'some-uid-2',
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should return paginated results when calling selectPaginate with defined total', async () => {
|
|
185
|
+
const db = new AuroraDbRDSProxyService(auroraConfig);
|
|
186
|
+
return expect(
|
|
187
|
+
db.selectPaginate('SELECT_QUERY', {}, 1, 2, 1)
|
|
188
|
+
).resolves.toMatchObject({
|
|
189
|
+
count: 1,
|
|
190
|
+
previous_page: 1,
|
|
191
|
+
current_page: 2,
|
|
192
|
+
next_page: 3,
|
|
193
|
+
per_page: 1,
|
|
194
|
+
items: [
|
|
195
|
+
{
|
|
196
|
+
id: 1,
|
|
197
|
+
uid: 'some-uid-1',
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe('test AuroraDbRDSProxyService selectFirst', () => {
|
|
205
|
+
it('should only return the first record when calling selectFirst', async () => {
|
|
206
|
+
const db = new AuroraDbRDSProxyService(auroraConfig);
|
|
207
|
+
return expect(db.selectFirst('SELECT_QUERY', {})).resolves.toMatchObject({
|
|
208
|
+
id: 1,
|
|
209
|
+
uid: 'some-uid-1',
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe('test AuroraDbRDSProxyService insert', () => {
|
|
215
|
+
it('should return recordId when inserting record', async () => {
|
|
216
|
+
const db = new AuroraDbRDSProxyService(auroraConfig);
|
|
217
|
+
return expect(db.insert('INSERT_QUERY', {})).resolves.toEqual(20);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should throw exception when calling insert with invalid query', async () => {
|
|
221
|
+
const error = new LesgoException(
|
|
222
|
+
'No records inserted from INSERT query',
|
|
223
|
+
'AURORADBSERVICE_NO_RECORDS_INSERTED',
|
|
224
|
+
400
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const db = new AuroraDbRDSProxyService(auroraConfig);
|
|
228
|
+
return expect(db.insert('INVALID_INSERT_QUERY', {})).rejects.toMatchObject(
|
|
229
|
+
error
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe('test AuroraDbRDSProxyService update', () => {
|
|
235
|
+
it('should return success when making update query', async () => {
|
|
236
|
+
const db = new AuroraDbRDSProxyService(auroraConfig);
|
|
237
|
+
return expect(db.update('UPDATE_QUERY', {})).resolves.not.toThrow();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should throw not exception when caliing update with invalid query', async () => {
|
|
241
|
+
const db = new AuroraDbRDSProxyService(auroraConfig);
|
|
242
|
+
return expect(db.update('INVALID_UPDATE_QUERY', {})).resolves.not.toThrow();
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe('test AuroraDbRDSProxyService pConnect', () => {
|
|
247
|
+
it('should be able to connect without custom config', async () => {
|
|
248
|
+
const db = new AuroraDbRDSProxyService(auroraConfig);
|
|
249
|
+
await db.pConnect();
|
|
250
|
+
|
|
251
|
+
expect(db.conn.mocked).toMatchObject(auroraConfig);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('should be able to connect with custom config', async () => {
|
|
255
|
+
const db = new AuroraDbRDSProxyService(auroraConfig);
|
|
256
|
+
await db.pConnect(customConfig);
|
|
257
|
+
|
|
258
|
+
expect(db.conn.mocked).toMatchObject(customConfig);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
describe('test AuroraDbRDSProxyService end', () => {
|
|
263
|
+
it('should end the connection when passed', async () => {
|
|
264
|
+
const db = new AuroraDbRDSProxyService(auroraConfig);
|
|
265
|
+
const conn = await db.connect();
|
|
266
|
+
await db.end(conn);
|
|
267
|
+
|
|
268
|
+
expect(conn.end).toHaveBeenCalledTimes(1);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should end the persisted connection when none is passed', async () => {
|
|
272
|
+
const db = new AuroraDbRDSProxyService(auroraConfig);
|
|
273
|
+
await db.pConnect();
|
|
274
|
+
await db.end();
|
|
275
|
+
|
|
276
|
+
expect(db.conn.end).toHaveBeenCalledTimes(1);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
@@ -32,6 +32,8 @@ describe('test LengthAwarePaginator instantiate', () => {
|
|
|
32
32
|
expect(await paginator.lastItem()).toMatchObject(mockDataLastItem);
|
|
33
33
|
expect(paginator.perPage()).toEqual(5);
|
|
34
34
|
expect(await paginator.total()).toEqual(30);
|
|
35
|
+
|
|
36
|
+
expect(db.select).toHaveBeenCalled();
|
|
35
37
|
});
|
|
36
38
|
it('should not throw exception when instantiating with current page', async () => {
|
|
37
39
|
const paginator = new LengthAwarePaginator(
|
|
@@ -51,6 +53,8 @@ describe('test LengthAwarePaginator instantiate', () => {
|
|
|
51
53
|
expect(await paginator.lastItem()).toMatchObject(mockDataLastItem);
|
|
52
54
|
expect(paginator.perPage()).toEqual(5);
|
|
53
55
|
expect(await paginator.total()).toEqual(30);
|
|
56
|
+
|
|
57
|
+
expect(db.select).toHaveBeenCalled();
|
|
54
58
|
});
|
|
55
59
|
it('should default perPage to 10 when instantiating without perPage', async () => {
|
|
56
60
|
const paginator = new LengthAwarePaginator(
|
|
@@ -66,6 +70,8 @@ describe('test LengthAwarePaginator instantiate', () => {
|
|
|
66
70
|
expect(paginator.currentPage()).toEqual(1);
|
|
67
71
|
expect(paginator.perPage()).toEqual(10);
|
|
68
72
|
expect(await paginator.total()).toEqual(30);
|
|
73
|
+
|
|
74
|
+
expect(db.select).toHaveBeenCalled();
|
|
69
75
|
});
|
|
70
76
|
it('should throw exception if total is not a number', async () => {
|
|
71
77
|
try {
|
|
@@ -116,6 +122,43 @@ describe('test LengthAwarePaginator instantiate', () => {
|
|
|
116
122
|
{ ...mockDataLastItem },
|
|
117
123
|
],
|
|
118
124
|
});
|
|
125
|
+
|
|
126
|
+
expect(db.select).toHaveBeenCalled();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should simply return an empty paginator object if total is explicitly zero', async () => {
|
|
130
|
+
const paginator = new LengthAwarePaginator(
|
|
131
|
+
db,
|
|
132
|
+
'SELECT * FROM tests',
|
|
133
|
+
{},
|
|
134
|
+
{
|
|
135
|
+
perPage: 5,
|
|
136
|
+
currentPage: 1,
|
|
137
|
+
total: 0,
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
expect(await paginator.count()).toEqual(0);
|
|
142
|
+
expect(await paginator.previousPage()).toEqual(false);
|
|
143
|
+
expect(paginator.currentPage()).toEqual(1);
|
|
144
|
+
expect(await paginator.nextPage()).toEqual(false);
|
|
145
|
+
expect(await paginator.firstItem()).toBe(undefined);
|
|
146
|
+
expect(await paginator.lastItem()).toBe(undefined);
|
|
147
|
+
expect(paginator.perPage()).toEqual(5);
|
|
148
|
+
expect(await paginator.total()).toEqual(0);
|
|
149
|
+
|
|
150
|
+
expect(await paginator.toObject()).toMatchObject({
|
|
151
|
+
count: 0,
|
|
152
|
+
previous_page: false,
|
|
153
|
+
current_page: 1,
|
|
154
|
+
next_page: false,
|
|
155
|
+
per_page: 5,
|
|
156
|
+
last_page: 0,
|
|
157
|
+
total: 0,
|
|
158
|
+
items: [],
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
expect(db.select).not.toHaveBeenCalled();
|
|
119
162
|
});
|
|
120
163
|
});
|
|
121
164
|
|
|
@@ -138,7 +181,7 @@ describe('test total() usage', () => {
|
|
|
138
181
|
});
|
|
139
182
|
|
|
140
183
|
describe('test lastPage() usage', () => {
|
|
141
|
-
it('should get the last page using supplied
|
|
184
|
+
it('should get the last page using supplied parameter as total data', async () => {
|
|
142
185
|
const paginator1 = new LengthAwarePaginator(
|
|
143
186
|
db,
|
|
144
187
|
'SELECT * FROM total_tests',
|
|
@@ -12,8 +12,9 @@ export default class LengthAwarePaginator extends Paginator {
|
|
|
12
12
|
* @param sql
|
|
13
13
|
* @param sqlParams
|
|
14
14
|
* @param options
|
|
15
|
+
* @param connection
|
|
15
16
|
*/
|
|
16
|
-
constructor(db, sql, sqlParams, options) {
|
|
17
|
+
constructor(db, sql, sqlParams, options, connection = {}) {
|
|
17
18
|
const validFields = [{ key: 'total', type: 'number', required: true }];
|
|
18
19
|
|
|
19
20
|
let validated = {};
|
|
@@ -33,7 +34,7 @@ export default class LengthAwarePaginator extends Paginator {
|
|
|
33
34
|
|
|
34
35
|
const { total } = validated;
|
|
35
36
|
|
|
36
|
-
super(db, sql, sqlParams, options);
|
|
37
|
+
super(db, sql, sqlParams, options, connection);
|
|
37
38
|
this.totalProp = total;
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -12,13 +12,14 @@ export default class Paginator {
|
|
|
12
12
|
* @param sqlParams
|
|
13
13
|
* @param options
|
|
14
14
|
*/
|
|
15
|
-
constructor(db, sql, sqlParams, options = {}) {
|
|
15
|
+
constructor(db, sql, sqlParams, options = {}, connection = {}) {
|
|
16
16
|
const validFields = [
|
|
17
17
|
{ key: 'db', type: 'object', required: true },
|
|
18
18
|
{ key: 'sql', type: 'string', required: true },
|
|
19
19
|
{ key: 'sqlParams', type: 'object', required: true },
|
|
20
20
|
{ key: 'perPage', type: 'number', required: false },
|
|
21
21
|
{ key: 'currentPage', type: 'number', required: false },
|
|
22
|
+
{ key: 'connection', type: 'object', required: false },
|
|
22
23
|
];
|
|
23
24
|
|
|
24
25
|
let validated = {};
|
|
@@ -29,6 +30,7 @@ export default class Paginator {
|
|
|
29
30
|
sql,
|
|
30
31
|
sqlParams,
|
|
31
32
|
...options,
|
|
33
|
+
connection,
|
|
32
34
|
},
|
|
33
35
|
validFields
|
|
34
36
|
);
|
|
@@ -56,6 +58,8 @@ export default class Paginator {
|
|
|
56
58
|
|
|
57
59
|
this.response = [];
|
|
58
60
|
this.totalProp = false;
|
|
61
|
+
|
|
62
|
+
this.connection = connection;
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
/**
|
|
@@ -213,10 +217,17 @@ export default class Paginator {
|
|
|
213
217
|
}
|
|
214
218
|
|
|
215
219
|
async executeQuery() {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
+
const total = this.totalProp;
|
|
221
|
+
if (
|
|
222
|
+
(typeof total === 'number' && total > 0) ||
|
|
223
|
+
(typeof total !== 'number' && !total)
|
|
224
|
+
) {
|
|
225
|
+
this.response = await this.dbProp.select(
|
|
226
|
+
this.generatePaginationSqlSnippet(),
|
|
227
|
+
this.sqlParamsProp,
|
|
228
|
+
this.connection
|
|
229
|
+
);
|
|
230
|
+
}
|
|
220
231
|
|
|
221
232
|
this.hasNext = this.response.length > this.perPage();
|
|
222
233
|
if (this.hasNext) {
|
|
@@ -232,7 +243,11 @@ export default class Paginator {
|
|
|
232
243
|
* @returns {Promise<number>}
|
|
233
244
|
*/
|
|
234
245
|
async countTotalItems() {
|
|
235
|
-
const resp = await this.dbProp.select(
|
|
246
|
+
const resp = await this.dbProp.select(
|
|
247
|
+
this.sqlProp,
|
|
248
|
+
this.sqlParamsProp,
|
|
249
|
+
this.connection
|
|
250
|
+
);
|
|
236
251
|
this.totalProp = Object.keys(resp).length;
|
|
237
252
|
|
|
238
253
|
return this.totalProp;
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import config from 'Config/db'; // eslint-disable-line import/no-unresolved
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
let db;
|
|
4
|
+
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
jest.isolateModules(() => {
|
|
7
|
+
db = require('../db').default; // eslint-disable-line global-require
|
|
8
|
+
});
|
|
9
|
+
});
|
|
3
10
|
|
|
4
11
|
describe('test db utils instantiate', () => {
|
|
5
12
|
it('should not throw error on instantiating AuroraDbService', () => {
|
|
@@ -29,4 +36,36 @@ describe('test db utils instantiate', () => {
|
|
|
29
36
|
},
|
|
30
37
|
});
|
|
31
38
|
});
|
|
39
|
+
|
|
40
|
+
it('should update AuroraDb credentials on connect based on dataApi config', () => {
|
|
41
|
+
config.default = 'dataApi';
|
|
42
|
+
let thisDb;
|
|
43
|
+
jest.isolateModules(() => {
|
|
44
|
+
thisDb = require('../db').default; // eslint-disable-line global-require
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return expect(thisDb.client).toMatchObject({
|
|
48
|
+
mocked: {
|
|
49
|
+
database: config.connections.dataApi.database,
|
|
50
|
+
resourceArn: config.connections.dataApi.resourceArn,
|
|
51
|
+
secretArn: config.connections.dataApi.secretArn,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should update AuroraDb credentials on connect based on rdsProxy config', () => {
|
|
57
|
+
config.default = 'rdsProxy';
|
|
58
|
+
let thisDb;
|
|
59
|
+
jest.isolateModules(() => {
|
|
60
|
+
thisDb = require('../db').default; // eslint-disable-line global-require
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return expect(thisDb.clientOpts).toMatchObject({
|
|
64
|
+
database: config.connections.rdsProxy.database,
|
|
65
|
+
host: config.connections.rdsProxy.host,
|
|
66
|
+
user: config.connections.rdsProxy.user,
|
|
67
|
+
password: config.connections.rdsProxy.password,
|
|
68
|
+
persists: config.connections.rdsProxy.persists,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
32
71
|
});
|
package/src/utils/db.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import dbConfig from 'Config/db'; // eslint-disable-line import/no-unresolved
|
|
2
|
+
import AuroraDbRDSProxyService from '../services/AuroraDbRDSProxyService';
|
|
2
3
|
import AuroraDbService from '../services/AuroraDbService';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
/* eslint-disable-next-line import/no-mutable-exports */
|
|
6
|
+
let db;
|
|
7
|
+
|
|
8
|
+
if (dbConfig.default === 'rdsProxy' || dbConfig.default === 'rdsProxyRead') {
|
|
9
|
+
db = new AuroraDbRDSProxyService(dbConfig.connections[dbConfig.default]);
|
|
10
|
+
} else if (dbConfig.default === 'dataApi') {
|
|
11
|
+
db = new AuroraDbService(dbConfig.connections[dbConfig.default]);
|
|
12
|
+
} else {
|
|
13
|
+
// @deprecated
|
|
14
|
+
db = new AuroraDbService(dbConfig);
|
|
15
|
+
}
|
|
5
16
|
|
|
6
17
|
export default db;
|
package/src/utils/logger.js
CHANGED