@hestia-earth/data-api 0.0.2-2 → 0.0.2-4
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/package.json +3 -2
- package/.dockerignore +0 -25
- package/.env.test +0 -7
- package/.eslintignore +0 -7
- package/.eslintrc.js +0 -11
- package/.gitlab-ci.yml +0 -125
- package/.mocharc.js +0 -8
- package/.nvrm +0 -1
- package/.nycrc +0 -15
- package/Dockerfile +0 -17
- package/cleanup-docker.sh +0 -4
- package/commitlint.config.js +0 -1
- package/database/index.ts +0 -76
- package/database/migrations/001.do.init.sql +0 -53
- package/database/migrations/002.do.add-aggregated-sites.sql +0 -16
- package/database/migrations/003.do.add-generated-period-cols.sql +0 -7
- package/database/migrations/index.ts +0 -36
- package/database/seed/common.ts +0 -7
- package/database/seed/index.ts +0 -55
- package/database/seed/local/index.ts +0 -28
- package/database/seed/production/index.ts +0 -3
- package/database/seed/staging/index.ts +0 -5
- package/database/seed/test/index.ts +0 -28
- package/dev.ts +0 -3
- package/dist/aggregated-nodes/model/index.js +0 -11
- package/docker-compose.yml +0 -42
- package/envs/.master.env +0 -7
- package/envs/.staging.env +0 -7
- package/index.js +0 -3
- package/run-docker.sh +0 -14
- package/run-test.sh +0 -5
- package/scripts/run-lambda.ts +0 -10
- package/scripts/run-migrations.ts +0 -18
- package/scripts/run-resetdb.ts +0 -18
- package/scripts/run-seed.ts +0 -18
- package/serverless.yml +0 -76
- package/src/aggregated-nodes/model/index.ts +0 -37
- package/src/aggregated-nodes/routes/pg-get-filters.ts +0 -44
- package/src/aggregated-nodes/routes/pg-get.ts +0 -50
- package/src/aggregated-nodes/routes.spec.ts +0 -242
- package/src/aggregated-nodes/routes.ts +0 -56
- package/src/aggregated-nodes/services/pg-get-filters.ts +0 -52
- package/src/aggregated-nodes/services/pg-get.ts +0 -77
- package/src/app.spec.ts +0 -34
- package/src/app.ts +0 -59
- package/src/config.ts +0 -21
- package/src/cors.spec.ts +0 -32
- package/src/cors.ts +0 -7
- package/src/errors.spec.ts +0 -114
- package/src/errors.ts +0 -121
- package/src/index.spec.ts +0 -94
- package/src/index.ts +0 -14
- package/src/lambdas/sentry.ts +0 -12
- package/src/lambdas/update-aggregated-nodes/handler.spec.ts +0 -86
- package/src/lambdas/update-aggregated-nodes/handler.ts +0 -141
- package/src/logger.spec.ts +0 -20
- package/src/logger.ts +0 -45
- package/src/maintenance.spec.ts +0 -76
- package/src/maintenance.ts +0 -19
- package/src/models.ts +0 -1
- package/src/routes.ts +0 -8
- package/src/settings/model/index.ts +0 -21
- package/src/settings/routes/get.spec.ts +0 -33
- package/src/settings/routes/get.ts +0 -3
- package/src/settings/routes/update.spec.ts +0 -33
- package/src/settings/routes/update.ts +0 -5
- package/src/settings/routes.spec.ts +0 -75
- package/src/settings/routes.ts +0 -21
- package/src/settings/services/get.spec.ts +0 -62
- package/src/settings/services/get.ts +0 -18
- package/src/settings/services/update.spec.ts +0 -118
- package/src/settings/services/update.ts +0 -47
- package/src/slack.spec.ts +0 -42
- package/src/slack.ts +0 -17
- package/src/swagger/routes.ts +0 -57
- package/src/types/async-express-errors/index.d.ts +0 -1
- package/src/types/express/index.d.ts +0 -10
- package/src/utils/endpoint-wrapper.spec.ts +0 -80
- package/src/utils/endpoint-wrapper.ts +0 -16
- package/src/utils/middleware.spec.ts +0 -154
- package/src/utils/middleware.ts +0 -33
- package/test/Dockerfile +0 -13
- package/test/docker-compose.yml +0 -40
- package/test/fixtures/aggregated-nodes/get.ts +0 -184
- package/test/fixtures/update-aggregated-nodes/abyssinianKaleSeedWhole-cycle_pivoted.csv +0 -5
- package/test/fixtures/update-aggregated-nodes/abyssinianKaleSeedWhole-cycle_pivoted.csv.cycle.json +0 -458
- package/test/fixtures/update-aggregated-nodes/abyssinianKaleSeedWhole-cycle_pivoted.csv.site.json +0 -182
- package/test/fixtures/update-aggregated-nodes/abyssinianKaleSeedWhole-impactassessment_pivoted.csv +0 -3
- package/test/fixtures/update-aggregated-nodes/abyssinianKaleSeedWhole-impactassessment_pivoted.csv.impactAssessment.json +0 -988
- package/test/fixtures/update-aggregated-nodes/abyssinianKaleStraw-impactassessment_pivoted.csv +0 -3
- package/test/fixtures/update-aggregated-nodes/cycle-missing-impactassessment_pivoted.csv +0 -3
- package/test/fixtures/update-aggregated-nodes/tomatoFruit-cycle_pivoted.csv +0 -5
- package/test/fixtures/update-aggregated-nodes/tomatoFruit-cycle_pivoted.csv.cycle.json +0 -584
- package/test/fixtures/update-aggregated-nodes/tomatoFruit-cycle_pivoted.csv.site.json +0 -212
- package/test/fixtures/update-aggregated-nodes/tomatoFruit-impactassessment_pivoted.csv +0 -3
- package/test/fixtures/update-aggregated-nodes/tomatoFruit-impactassessment_pivoted.csv.impactAssessment.json +0 -1002
- package/test/prepare.ts +0 -13
- package/test/utils.ts +0 -33
- package/tsconfig.build.json +0 -13
- package/tsconfig.dist.json +0 -14
- package/tsconfig.json +0 -42
- package/tsconfig.lambdas.json +0 -13
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai';
|
|
2
|
-
import * as sinon from 'sinon';
|
|
3
|
-
import 'mocha';
|
|
4
|
-
|
|
5
|
-
import * as slack from '../../slack';
|
|
6
|
-
import { postMessage } from './update';
|
|
7
|
-
import * as model from '../model';
|
|
8
|
-
import * as servicesGet from './get';
|
|
9
|
-
|
|
10
|
-
let stubs: sinon.SinonStub[] = [];
|
|
11
|
-
|
|
12
|
-
describe('settings > services', () => {
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
stubs = [];
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
afterEach(() => {
|
|
18
|
-
stubs.forEach((stub) => stub.restore());
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
describe('update', () => {
|
|
22
|
-
describe('postMessage', () => {
|
|
23
|
-
const slackThreadTs = 'thread';
|
|
24
|
-
let updateStub: sinon.SinonStub;
|
|
25
|
-
let sendMessageStub: sinon.SinonStub;
|
|
26
|
-
let replyMessageStub: sinon.SinonStub;
|
|
27
|
-
|
|
28
|
-
beforeEach(() => {
|
|
29
|
-
stubs.push((updateStub = sinon.stub(model, 'setSetting').resolves()));
|
|
30
|
-
stubs.push((sendMessageStub = sinon.stub(slack, 'sendMessage').resolves(slackThreadTs)));
|
|
31
|
-
stubs.push((replyMessageStub = sinon.stub(slack, 'replyMessage').resolves(slackThreadTs)));
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe('turning setting on', () => {
|
|
35
|
-
const key = model.SettingKey.maintenanceEnabled;
|
|
36
|
-
const value = true;
|
|
37
|
-
|
|
38
|
-
beforeEach(() => {
|
|
39
|
-
stubs.push(
|
|
40
|
-
sinon.stub(servicesGet, 'get').resolves({
|
|
41
|
-
rows: [{ setting: key, active: value, metadata: {} }]
|
|
42
|
-
} as any)
|
|
43
|
-
);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('should send the message', async () => {
|
|
47
|
-
await postMessage(key, value);
|
|
48
|
-
expect(sendMessageStub.called).to.equal(true);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should not reply to the message', async () => {
|
|
52
|
-
await postMessage(key, value);
|
|
53
|
-
expect(replyMessageStub.called).to.equal(false);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('should set the thread', async () => {
|
|
57
|
-
await postMessage(key, value);
|
|
58
|
-
expect(updateStub.calledWith({ key, value, metadata: { slackThreadTs } })).to.equal(true);
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
describe('turning setting off', () => {
|
|
63
|
-
const key = model.SettingKey.maintenanceEnabled;
|
|
64
|
-
const value = false;
|
|
65
|
-
|
|
66
|
-
describe('existing thread', () => {
|
|
67
|
-
beforeEach(() => {
|
|
68
|
-
stubs.push(
|
|
69
|
-
sinon.stub(servicesGet, 'get').resolves({
|
|
70
|
-
rows: [{ setting: key, active: value, metadata: { slackThreadTs } }]
|
|
71
|
-
} as any)
|
|
72
|
-
);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should not send the message', async () => {
|
|
76
|
-
await postMessage(key, value);
|
|
77
|
-
expect(sendMessageStub.called).to.equal(false);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('should reply the message', async () => {
|
|
81
|
-
await postMessage(key, value);
|
|
82
|
-
expect(replyMessageStub.called).to.equal(true);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('should remove the thread', async () => {
|
|
86
|
-
await postMessage(key, value);
|
|
87
|
-
expect(updateStub.calledWith({ key, value, metadata: { slackThreadTs: null } })).to.equal(true);
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
describe('non-existing thread', () => {
|
|
92
|
-
beforeEach(() => {
|
|
93
|
-
stubs.push(
|
|
94
|
-
sinon.stub(servicesGet, 'get').resolves({
|
|
95
|
-
rows: [{ setting: key, active: value, metadata: {} }]
|
|
96
|
-
} as any)
|
|
97
|
-
);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('should not send the message', async () => {
|
|
101
|
-
await postMessage(key, value);
|
|
102
|
-
expect(sendMessageStub.called).to.equal(false);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('should not reply to the message', async () => {
|
|
106
|
-
await postMessage(key, value);
|
|
107
|
-
expect(replyMessageStub.called).to.equal(false);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('should remove the thread', async () => {
|
|
111
|
-
await postMessage(key, value);
|
|
112
|
-
expect(updateStub.calledWith({ key, value, metadata: { slackThreadTs: null } })).to.equal(true);
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
});
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { apiUrl, webappUrl } from '../../config';
|
|
2
|
-
import { sendMessage, replyMessage } from '../../slack';
|
|
3
|
-
import { get } from './get';
|
|
4
|
-
import { SettingKey, setSetting } from '../model/';
|
|
5
|
-
|
|
6
|
-
type settingData = { [key in SettingKey]?: any };
|
|
7
|
-
|
|
8
|
-
const slackChannel = 'product-team';
|
|
9
|
-
|
|
10
|
-
const messageByKey: {
|
|
11
|
-
[key in SettingKey]?: (value: any) => string;
|
|
12
|
-
} = {
|
|
13
|
-
[SettingKey.maintenanceEnabled]: (value: boolean) =>
|
|
14
|
-
value ? 'Data API is under maintenance, please do not use the data explorer' : 'Maintenance ended'
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const postMessage = async (key: SettingKey, value: boolean) => {
|
|
18
|
-
const { rows } = await get(key);
|
|
19
|
-
const setting = rows?.[0];
|
|
20
|
-
const message = messageByKey[key](value);
|
|
21
|
-
const fullMessage = `
|
|
22
|
-
${messageByKey[key](value)}
|
|
23
|
-
|
|
24
|
-
*API*: ${apiUrl}
|
|
25
|
-
*Website*: ${webappUrl}
|
|
26
|
-
`.trim();
|
|
27
|
-
const existingThread = setting ? setting.metadata?.slackThreadTs : null;
|
|
28
|
-
const ts = value
|
|
29
|
-
? // setting is turned on, create new thread
|
|
30
|
-
await sendMessage({ channel: slackChannel, text: fullMessage })
|
|
31
|
-
: existingThread
|
|
32
|
-
? // setting is turned off and thread exists, reply to it
|
|
33
|
-
await replyMessage(existingThread, { channel: slackChannel, text: message })
|
|
34
|
-
: // setting is turned off and no thread exists, ignore
|
|
35
|
-
null;
|
|
36
|
-
|
|
37
|
-
return setSetting({ key, value, metadata: { slackThreadTs: value ? ts : null } });
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export const updateSingle = async (key: SettingKey, value: any) => {
|
|
41
|
-
await setSetting({ key, value });
|
|
42
|
-
Object.keys(messageByKey).includes(key) && (await postMessage(key, value));
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export const updateAll = (value: settingData) => {
|
|
46
|
-
return Promise.all(Object.entries(value).map(([key, v]) => updateSingle(key as SettingKey, v)));
|
|
47
|
-
};
|
package/src/slack.spec.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai';
|
|
2
|
-
import * as sinon from 'sinon';
|
|
3
|
-
import 'mocha';
|
|
4
|
-
import * as slackApi from '@slack/web-api/dist/WebClient';
|
|
5
|
-
import { sendMessage, replyMessage } from './slack';
|
|
6
|
-
|
|
7
|
-
let stubs: sinon.SinonStub[] = [];
|
|
8
|
-
const postMessageStub: sinon.SinonStub = sinon.stub().returns({ ts: 'ts' });
|
|
9
|
-
|
|
10
|
-
const SlackWebClientStub = sinon.createStubInstance(slackApi.WebClient);
|
|
11
|
-
(SlackWebClientStub as any).chat = { postMessage: postMessageStub };
|
|
12
|
-
|
|
13
|
-
describe('slack', () => {
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
stubs = [];
|
|
16
|
-
stubs.push(sinon.stub(slackApi, 'WebClient').returns(SlackWebClientStub));
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
stubs.forEach((stub) => stub.restore());
|
|
21
|
-
postMessageStub.resetHistory();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
describe('sendMessage', () => {
|
|
25
|
-
it('should send a message to a channel', async () => {
|
|
26
|
-
await sendMessage({});
|
|
27
|
-
expect(postMessageStub.args).to.deep.equal([[{ channel: process.env.SLACK_CHANNEL, link_names: true }]]);
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
describe('replyMessage', () => {
|
|
32
|
-
describe('with thread', () => {
|
|
33
|
-
it('should send a message to a thread', async () => {
|
|
34
|
-
await replyMessage('thread_ts', {});
|
|
35
|
-
|
|
36
|
-
expect(postMessageStub.args).to.deep.equal([
|
|
37
|
-
[{ channel: process.env.SLACK_CHANNEL, thread_ts: 'thread_ts', link_names: true }]
|
|
38
|
-
]);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
});
|
package/src/slack.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { WebClient, ChatPostMessageArguments } from '@slack/web-api';
|
|
2
|
-
|
|
3
|
-
import { slackConfig } from './config';
|
|
4
|
-
|
|
5
|
-
const channel = slackConfig.channel;
|
|
6
|
-
|
|
7
|
-
export const sendMessage = async (args: Partial<ChatPostMessageArguments>) =>
|
|
8
|
-
(
|
|
9
|
-
await new WebClient(slackConfig.token).chat.postMessage({
|
|
10
|
-
channel: args.channel || channel,
|
|
11
|
-
link_names: true,
|
|
12
|
-
...args
|
|
13
|
-
})
|
|
14
|
-
).ts;
|
|
15
|
-
|
|
16
|
-
export const replyMessage = async (thread_ts: string, args: Omit<ChatPostMessageArguments, 'thread_ts'>) =>
|
|
17
|
-
thread_ts ? await sendMessage({ thread_ts, ...args }) : null;
|
package/src/swagger/routes.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { Router } from 'express';
|
|
2
|
-
import { serve, setup } from 'swagger-ui-express';
|
|
3
|
-
import swaggerJSDoc from 'swagger-jsdoc';
|
|
4
|
-
|
|
5
|
-
import { apiUrl } from '../config';
|
|
6
|
-
import pkg from '../../package.json';
|
|
7
|
-
|
|
8
|
-
const options = {
|
|
9
|
-
swaggerDefinition: {
|
|
10
|
-
openapi: '3.0.1',
|
|
11
|
-
info: {
|
|
12
|
-
title: pkg.name,
|
|
13
|
-
version: pkg.version,
|
|
14
|
-
description: 'Hestia Data API documentation.',
|
|
15
|
-
license: {
|
|
16
|
-
name: 'GPL-3.0-or-later',
|
|
17
|
-
url: 'https://choosealicense.com/licenses/gpl-3.0/'
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
components: {
|
|
21
|
-
securitySchemes: {
|
|
22
|
-
AccessToken: {
|
|
23
|
-
type: 'apiKey',
|
|
24
|
-
in: 'header',
|
|
25
|
-
name: 'x-access-token'
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
servers: [
|
|
30
|
-
{
|
|
31
|
-
url: apiUrl
|
|
32
|
-
}
|
|
33
|
-
]
|
|
34
|
-
},
|
|
35
|
-
apis: ['src/*.ts', 'src/**/*.ts']
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const swaggerSpec = swaggerJSDoc(options);
|
|
39
|
-
const swaggerOptions = {
|
|
40
|
-
customCss: `
|
|
41
|
-
.swagger-ui .topbar {
|
|
42
|
-
display: none;
|
|
43
|
-
}
|
|
44
|
-
`,
|
|
45
|
-
customSiteTitle: 'Hestia Data API Explorer',
|
|
46
|
-
url: '/swagger.json'
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export default () => {
|
|
50
|
-
const router = Router();
|
|
51
|
-
|
|
52
|
-
router.use('/', serve);
|
|
53
|
-
router.get('/', setup(swaggerSpec, swaggerOptions));
|
|
54
|
-
router.get('/swagger.json', (req, res) => res.json(swaggerSpec));
|
|
55
|
-
|
|
56
|
-
return router;
|
|
57
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
declare module 'express-async-errors';
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai';
|
|
2
|
-
import * as sinon from 'sinon';
|
|
3
|
-
import 'mocha';
|
|
4
|
-
|
|
5
|
-
import { endpointWrapper } from './endpoint-wrapper';
|
|
6
|
-
|
|
7
|
-
class Response {
|
|
8
|
-
status(_status: number) {
|
|
9
|
-
return this;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
json() {}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
let stubs: sinon.SinonStub[] = [];
|
|
16
|
-
|
|
17
|
-
describe('utils > endpoint-wrapper', () => {
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
stubs = [];
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
stubs.forEach((stub) => stub.restore());
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
describe('endpointWrapper', () => {
|
|
27
|
-
const response = new Response();
|
|
28
|
-
let fakeFunction: Function;
|
|
29
|
-
let jsonStub: sinon.SinonStub;
|
|
30
|
-
let nextStub: sinon.SinonStub;
|
|
31
|
-
|
|
32
|
-
beforeEach(() => {
|
|
33
|
-
stubs.push((jsonStub = sinon.stub(response, 'json')));
|
|
34
|
-
nextStub = sinon.stub();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
describe('function fail', () => {
|
|
38
|
-
const err = new Error('error');
|
|
39
|
-
|
|
40
|
-
beforeEach(() => {
|
|
41
|
-
fakeFunction = () => {
|
|
42
|
-
throw err;
|
|
43
|
-
};
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('should reject', async () => {
|
|
47
|
-
const result = endpointWrapper(fakeFunction);
|
|
48
|
-
await result(null, response as any, nextStub);
|
|
49
|
-
expect(jsonStub.called).to.equal(false);
|
|
50
|
-
expect(nextStub.calledWith(err)).to.equal(true);
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
describe('function success', () => {
|
|
55
|
-
beforeEach(() => {
|
|
56
|
-
fakeFunction = () => 'success';
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('should resolve', async () => {
|
|
60
|
-
const result = endpointWrapper(fakeFunction);
|
|
61
|
-
await result(null, response as any, nextStub);
|
|
62
|
-
expect(jsonStub.calledWith('success')).to.equal(true);
|
|
63
|
-
expect(nextStub.called).to.equal(false);
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
describe('without a function', () => {
|
|
68
|
-
beforeEach(() => {
|
|
69
|
-
fakeFunction = undefined;
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should resolve', async () => {
|
|
73
|
-
const result = endpointWrapper(fakeFunction);
|
|
74
|
-
await result(null, response as any, nextStub);
|
|
75
|
-
expect(jsonStub.calledWith({})).to.equal(true);
|
|
76
|
-
expect(nextStub.called).to.equal(false);
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
});
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from 'express';
|
|
2
|
-
|
|
3
|
-
// Until express v5 promises must be coerced to void
|
|
4
|
-
// See https://github.com/davidbanham/express-async-errors/issues/36#issuecomment-944954003
|
|
5
|
-
export const endpointWrapper =
|
|
6
|
-
(originalFunction: Function = () => ({})) =>
|
|
7
|
-
(req: Request, res: Response, next: NextFunction) =>
|
|
8
|
-
void (async function () {
|
|
9
|
-
try {
|
|
10
|
-
const output = await Promise.resolve(originalFunction(req, res, next));
|
|
11
|
-
return res.status(200).json(output);
|
|
12
|
-
}
|
|
13
|
-
catch (err) {
|
|
14
|
-
return next(err);
|
|
15
|
-
}
|
|
16
|
-
})();
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai';
|
|
2
|
-
import * as sinon from 'sinon';
|
|
3
|
-
import 'mocha';
|
|
4
|
-
|
|
5
|
-
import { Errors } from '../errors';
|
|
6
|
-
import { requireQueryParams, parseArrayQueryParams, parseBooleanQueryParams } from './middleware';
|
|
7
|
-
import * as errors from '../errors';
|
|
8
|
-
|
|
9
|
-
let stubs: sinon.SinonStub[] = [];
|
|
10
|
-
|
|
11
|
-
describe('utils', () => {
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
stubs = [];
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
afterEach(() => {
|
|
17
|
-
stubs.forEach((stub) => stub.restore());
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
describe('middleware', () => {
|
|
21
|
-
let nextStub: sinon.SinonStub;
|
|
22
|
-
let throwErrorStub: sinon.SinonStub;
|
|
23
|
-
|
|
24
|
-
beforeEach(() => {
|
|
25
|
-
nextStub = sinon.stub();
|
|
26
|
-
stubs.push((throwErrorStub = sinon.stub(errors, 'throwError')));
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
describe('requireQueryParams', () => {
|
|
30
|
-
const req: any = { query: {} };
|
|
31
|
-
|
|
32
|
-
describe('with missing params', () => {
|
|
33
|
-
beforeEach(() => {
|
|
34
|
-
req.query.a = 1;
|
|
35
|
-
req.query.b = {};
|
|
36
|
-
req.query.c = '';
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('should throw an error', () => {
|
|
40
|
-
requireQueryParams('a', 'b', 'c')(req, null, nextStub);
|
|
41
|
-
expect(throwErrorStub.calledWith(Errors.MissingQueryParam, { params: ['b', 'c'] })).to.equal(true);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('should NOT call next', () => {
|
|
45
|
-
requireQueryParams('a', 'b', 'c')(req, null, nextStub);
|
|
46
|
-
expect(nextStub.called).to.equal(false);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
describe('with all params', () => {
|
|
51
|
-
beforeEach(() => {
|
|
52
|
-
req.query.a = 1;
|
|
53
|
-
req.query.b = { value: true };
|
|
54
|
-
req.query.c = 'test';
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('should NOT throw an error', () => {
|
|
58
|
-
requireQueryParams('a', 'b', 'c')(req, null, nextStub);
|
|
59
|
-
expect(throwErrorStub.called).to.equal(false);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('should call next', () => {
|
|
63
|
-
requireQueryParams('a', 'b', 'c')(req, null, nextStub);
|
|
64
|
-
expect(nextStub.called).to.equal(true);
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
describe('parseArrayQueryParams', () => {
|
|
70
|
-
const req: any = { query: {} };
|
|
71
|
-
|
|
72
|
-
describe('specified param is a string', () => {
|
|
73
|
-
beforeEach(() => {
|
|
74
|
-
req.query.stringParam = 'param1,param2';
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('should split the search params', () => {
|
|
78
|
-
parseArrayQueryParams('stringParam')(req, null, nextStub);
|
|
79
|
-
expect(req.query.stringParam).to.deep.equal(['param1', 'param2']);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('should call next', () => {
|
|
83
|
-
parseArrayQueryParams()(req, null, nextStub);
|
|
84
|
-
expect(nextStub.called).to.equal(true);
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
describe('specified param is an array', () => {
|
|
89
|
-
beforeEach(() => {
|
|
90
|
-
req.query.arrayParam = ['param1', 'param2'];
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('should keep the search params', () => {
|
|
94
|
-
parseArrayQueryParams('arrayParam')(req, null, nextStub);
|
|
95
|
-
expect(req.query.arrayParam).to.deep.equal(['param1', 'param2']);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('should call next', () => {
|
|
99
|
-
parseArrayQueryParams()(req, null, nextStub);
|
|
100
|
-
expect(nextStub.called).to.equal(true);
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
describe('parseBooleanQueryParams', () => {
|
|
106
|
-
let req: any;
|
|
107
|
-
|
|
108
|
-
describe('param as "true"', () => {
|
|
109
|
-
beforeEach(() => {
|
|
110
|
-
req = {
|
|
111
|
-
query: {
|
|
112
|
-
booleanParam: 'true'
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('should return true', () => {
|
|
118
|
-
parseBooleanQueryParams('booleanParam')(req, null, nextStub);
|
|
119
|
-
expect(req.query.booleanParam).to.equal(true);
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
describe('param as "false"', () => {
|
|
124
|
-
beforeEach(() => {
|
|
125
|
-
req = {
|
|
126
|
-
query: {
|
|
127
|
-
booleanParam: 'false'
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('should return false', () => {
|
|
133
|
-
parseBooleanQueryParams('booleanParam')(req, null, nextStub);
|
|
134
|
-
expect(req.query.booleanParam).to.equal(false);
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
describe('with empty query string', () => {
|
|
139
|
-
beforeEach(() => {
|
|
140
|
-
req = {
|
|
141
|
-
query: {
|
|
142
|
-
booleanParam: ''
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('should return false', () => {
|
|
148
|
-
parseBooleanQueryParams('booleanParam')(req, null, nextStub);
|
|
149
|
-
expect(req.query.booleanParam).to.equal(false);
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
});
|
package/src/utils/middleware.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from 'express';
|
|
2
|
-
|
|
3
|
-
import { Errors, throwError } from '../errors';
|
|
4
|
-
|
|
5
|
-
const undefinedObject = <T>(value: T) => typeof value === 'object' && !Object.keys(value).length;
|
|
6
|
-
|
|
7
|
-
const undefinedString = <T>(value: T) => typeof value === 'string' && !value;
|
|
8
|
-
|
|
9
|
-
const isUndefined = <T>(value: T) =>
|
|
10
|
-
value === null || typeof value === 'undefined' || undefinedObject(value) || undefinedString(value);
|
|
11
|
-
|
|
12
|
-
export const requireQueryParams =
|
|
13
|
-
(...params: string[]) =>
|
|
14
|
-
({ query }: Request, _r: Response, next: NextFunction) => {
|
|
15
|
-
const missingParams = params.filter((param) => isUndefined(query[param]));
|
|
16
|
-
return missingParams.length === 0 ? next() : throwError(Errors.MissingQueryParam, { params: missingParams });
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export const parseArrayQueryParams =
|
|
20
|
-
(...fields: string[]) =>
|
|
21
|
-
({ query }: Request, _r: Response, next: NextFunction) => {
|
|
22
|
-
fields.forEach((field) => {
|
|
23
|
-
query[field] = typeof query[field] === 'string' ? (query[field] as string).split(',') : query[field];
|
|
24
|
-
});
|
|
25
|
-
next();
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export const parseBooleanQueryParams =
|
|
29
|
-
(...fields: string[]) =>
|
|
30
|
-
(req: Request, _res: Response, next: NextFunction) => {
|
|
31
|
-
fields.forEach((field) => ((req.query as any)[field] = req.query[field] === 'true'));
|
|
32
|
-
next();
|
|
33
|
-
};
|
package/test/Dockerfile
DELETED
package/test/docker-compose.yml
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
version: '3'
|
|
2
|
-
|
|
3
|
-
services:
|
|
4
|
-
test:
|
|
5
|
-
env_file: '../.env.test'
|
|
6
|
-
image: hestia-data-api:test
|
|
7
|
-
build:
|
|
8
|
-
context: ../
|
|
9
|
-
dockerfile: test/Dockerfile
|
|
10
|
-
environment:
|
|
11
|
-
- PGHOST=hestia-data-api-test-db
|
|
12
|
-
- PGUSER=postgres
|
|
13
|
-
- PGDATABASE=postgres
|
|
14
|
-
- PGPASSWORD=password
|
|
15
|
-
- PGPORT=5432
|
|
16
|
-
volumes:
|
|
17
|
-
- ../src:/app/src
|
|
18
|
-
- ../coverage:/app/coverage
|
|
19
|
-
- ./:/app/test
|
|
20
|
-
depends_on:
|
|
21
|
-
db:
|
|
22
|
-
condition: service_healthy
|
|
23
|
-
|
|
24
|
-
db:
|
|
25
|
-
image: postgres:14
|
|
26
|
-
container_name: hestia-data-api-test-db
|
|
27
|
-
ports:
|
|
28
|
-
- 5432:5432
|
|
29
|
-
volumes:
|
|
30
|
-
- postgres:/data/postgres
|
|
31
|
-
environment:
|
|
32
|
-
- POSTGRES_PASSWORD=password
|
|
33
|
-
healthcheck:
|
|
34
|
-
test: ['CMD', 'pg_isready', '-q', '-d', 'postgres', '-U', 'postgres']
|
|
35
|
-
interval: 1s
|
|
36
|
-
timeout: 5s
|
|
37
|
-
retries: 5
|
|
38
|
-
|
|
39
|
-
volumes:
|
|
40
|
-
postgres:
|