@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,77 +0,0 @@
|
|
|
1
|
-
import { partition as _partition } from 'lodash';
|
|
2
|
-
import { query as doQuery } from '../../../database';
|
|
3
|
-
import { AggregatedNodeRow, IFilter } from '../model';
|
|
4
|
-
|
|
5
|
-
type FilterClauseFunc = (args: { idx: number; exclude: boolean }) => string | Record<string, string>;
|
|
6
|
-
|
|
7
|
-
const filterClauseMapping: Record<keyof IFilter, FilterClauseFunc> = {
|
|
8
|
-
periods: ({ idx, exclude = false }) => (exclude ? 'TRUE' : `c.generated_period = ANY($${idx}::text[])`),
|
|
9
|
-
// IA aggregatedQualityScore is always equal to that of cycle
|
|
10
|
-
minAggregatedQualityScore: ({ idx, exclude = false }) =>
|
|
11
|
-
exclude ? 'TRUE' : `(c.jsonld_pivoted->>'aggregatedQualityScore')::integer >= $${idx}`,
|
|
12
|
-
practices: ({ idx, exclude = false }) => (exclude ? 'TRUE' : `c.jsonld_pivoted['practices'] ?| $${idx}::text[]`),
|
|
13
|
-
products: ({ idx, exclude = false }) => ({
|
|
14
|
-
iaFilter: exclude ? 'TRUE' : `ia.jsonld_pivoted['product']['term']->>'@id' = ANY($${idx}::text[])`,
|
|
15
|
-
cycleFilter: exclude ? 'TRUE' : `c.jsonld_pivoted['products'] ?| $${idx}::text[]`
|
|
16
|
-
}),
|
|
17
|
-
// country field also holds values like 'Northern Europe'
|
|
18
|
-
regions: ({ idx, exclude = false }) =>
|
|
19
|
-
exclude ? 'TRUE' : `s.jsonld_pivoted['country']->>'@id' = ANY($${idx}::text[])`,
|
|
20
|
-
defaultMethodClassifications: ({ idx, exclude = false }) =>
|
|
21
|
-
exclude ? 'TRUE' : `c.jsonld_pivoted->>'defaultMethodClassification' = ANY($${idx}::text[])`
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export const getFilterClauses = (filter: IFilter) => {
|
|
25
|
-
const filterClauses: Partial<Record<keyof IFilter, Record<string, string>>> = {};
|
|
26
|
-
const params = [];
|
|
27
|
-
|
|
28
|
-
const [included, excluded] = _partition(Object.keys(filterClauseMapping), (key) => filter[key]);
|
|
29
|
-
|
|
30
|
-
included.forEach((key, idx) => {
|
|
31
|
-
filterClauses[key] = filterClauseMapping[key]({ idx: idx + 1 });
|
|
32
|
-
params.push(filter[key]);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
excluded.forEach((key) => {
|
|
36
|
-
filterClauses[key] = filterClauseMapping[key]({ exclude: true });
|
|
37
|
-
});
|
|
38
|
-
return { filterClauses, params };
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const getNodesQuery = (filter: IFilter): [string, any[]] => {
|
|
42
|
-
const { filterClauses, params } = getFilterClauses(filter);
|
|
43
|
-
return [
|
|
44
|
-
`
|
|
45
|
-
SELECT
|
|
46
|
-
c.jsonld_pivoted AS cycle,
|
|
47
|
-
s.jsonld_pivoted['country'] AS region,
|
|
48
|
-
COALESCE(
|
|
49
|
-
jsonb_agg(ia.jsonld_pivoted ORDER BY ia.jsonld_pivoted->>'@id') FILTER (WHERE ia.jsonld_pivoted IS NOT NULL),
|
|
50
|
-
'[]'
|
|
51
|
-
) AS "impactAssessments"
|
|
52
|
-
FROM aggregated_cycles AS c
|
|
53
|
-
LEFT JOIN aggregated_impact_assessments AS ia
|
|
54
|
-
ON c.hestia_id = ia.cycle_hestia_id
|
|
55
|
-
AND ${filterClauses.products.iaFilter}
|
|
56
|
-
JOIN aggregated_sites AS s
|
|
57
|
-
ON c.site_hestia_id = s.hestia_id
|
|
58
|
-
AND ${filterClauses.regions}
|
|
59
|
-
WHERE ${filterClauses.practices}
|
|
60
|
-
AND ${filterClauses.defaultMethodClassifications}
|
|
61
|
-
AND ${filterClauses.products.cycleFilter}
|
|
62
|
-
AND ${filterClauses.minAggregatedQualityScore}
|
|
63
|
-
AND ${filterClauses.periods}
|
|
64
|
-
GROUP BY cycle, region
|
|
65
|
-
ORDER BY c.jsonld_pivoted->>'@id'
|
|
66
|
-
LIMIT 500
|
|
67
|
-
`,
|
|
68
|
-
params
|
|
69
|
-
];
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
export const applyFilter = async ({ filter }: { page?: number; filter: IFilter; query?: string }) => {
|
|
73
|
-
// TODO: handle page and query later
|
|
74
|
-
const [pgQuery, params] = getNodesQuery(filter);
|
|
75
|
-
const rows = await doQuery<AggregatedNodeRow>(pgQuery, params);
|
|
76
|
-
return rows;
|
|
77
|
-
};
|
package/src/app.spec.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai';
|
|
2
|
-
import * as sinon from 'sinon';
|
|
3
|
-
import 'mocha';
|
|
4
|
-
|
|
5
|
-
import { logger } from './logger';
|
|
6
|
-
import { app, healthcheck } from './app';
|
|
7
|
-
|
|
8
|
-
let stubs: sinon.SinonStub[] = [];
|
|
9
|
-
|
|
10
|
-
describe('app', () => {
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
stubs = [];
|
|
13
|
-
stubs.push(sinon.stub(logger, 'error'));
|
|
14
|
-
stubs.push(sinon.stub(logger, 'debug'));
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
afterEach(() => {
|
|
18
|
-
stubs.forEach((stub) => stub.restore());
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('should be defined', () => {
|
|
22
|
-
expect(app !== undefined).to.equal(true);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
describe('healthcheck', () => {
|
|
26
|
-
it('should return status 200', () => {
|
|
27
|
-
const res = {
|
|
28
|
-
status: sinon.stub().returns({ send() {} })
|
|
29
|
-
};
|
|
30
|
-
healthcheck(null, res);
|
|
31
|
-
expect(res.status.calledWith(200)).to.equal(true);
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
});
|
package/src/app.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import * as express from 'express';
|
|
2
|
-
import 'express-async-errors';
|
|
3
|
-
const http2Express = require('http2-express-bridge');
|
|
4
|
-
import { RequestHandler } from 'express';
|
|
5
|
-
|
|
6
|
-
const app: express.Express = http2Express(express);
|
|
7
|
-
|
|
8
|
-
import * as Sentry from '@sentry/node';
|
|
9
|
-
import * as Tracing from '@sentry/tracing';
|
|
10
|
-
import { sentryConfig } from './config';
|
|
11
|
-
|
|
12
|
-
Sentry.init({
|
|
13
|
-
...sentryConfig,
|
|
14
|
-
integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })]
|
|
15
|
-
});
|
|
16
|
-
app.use(Sentry.Handlers.requestHandler());
|
|
17
|
-
app.use(Sentry.Handlers.tracingHandler());
|
|
18
|
-
|
|
19
|
-
import cors from 'cors';
|
|
20
|
-
import { disableCors } from './config';
|
|
21
|
-
import { whitelistOrigin } from './cors';
|
|
22
|
-
app.use(cors(disableCors ? undefined : { origin: whitelistOrigin }));
|
|
23
|
-
|
|
24
|
-
import compression from 'compression';
|
|
25
|
-
app.use(compression());
|
|
26
|
-
|
|
27
|
-
import * as bodyParser from 'body-parser';
|
|
28
|
-
// based on AWS Lambda body size limit
|
|
29
|
-
const limit = '6mb';
|
|
30
|
-
app.use(bodyParser.json({ limit }));
|
|
31
|
-
app.use(bodyParser.urlencoded({ limit, extended: false }));
|
|
32
|
-
|
|
33
|
-
export const healthcheck = (_req, res) => res.status(200).send();
|
|
34
|
-
app.get('/health', healthcheck);
|
|
35
|
-
|
|
36
|
-
import swagger from './swagger/routes';
|
|
37
|
-
app.use('/swagger', swagger());
|
|
38
|
-
|
|
39
|
-
// make sure settings is not blocked by maintenance mode
|
|
40
|
-
import settings from './settings/routes';
|
|
41
|
-
app.use('/settings', settings());
|
|
42
|
-
|
|
43
|
-
import { maintenance } from './maintenance';
|
|
44
|
-
// Until express v5 promises must be coerced to void
|
|
45
|
-
// See https://github.com/davidbanham/express-async-errors/issues/36#issuecomment-944954003
|
|
46
|
-
app.use(maintenance as RequestHandler);
|
|
47
|
-
|
|
48
|
-
// log routes after healthcheck and maintenance
|
|
49
|
-
import morgan from 'morgan';
|
|
50
|
-
import { stream } from './logger';
|
|
51
|
-
app.use(morgan('combined', { stream }));
|
|
52
|
-
|
|
53
|
-
import routes from './routes';
|
|
54
|
-
routes(app);
|
|
55
|
-
|
|
56
|
-
import errorHandler from './errors';
|
|
57
|
-
errorHandler(app);
|
|
58
|
-
|
|
59
|
-
export { app };
|
package/src/config.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
const { version } = require('../package.json');
|
|
2
|
-
|
|
3
|
-
export const PORT = process.env.PORT || '3000';
|
|
4
|
-
export const apiUrl = (process.env.API_URL || 'http://localhost:{PORT}').replace('{PORT}', PORT);
|
|
5
|
-
export const webappUrl = process.env.WEBAPP_URL || 'http://localhost';
|
|
6
|
-
|
|
7
|
-
export const disableCors = (process.env.CORS_DISABLED || 'false') === 'true';
|
|
8
|
-
export const whitelist = (process.env.CORS_WHITELIST || '').split(',');
|
|
9
|
-
whitelist.push(apiUrl);
|
|
10
|
-
whitelist.push(webappUrl);
|
|
11
|
-
|
|
12
|
-
export const sentryConfig = {
|
|
13
|
-
dsn: process.env.SENTRY_DSN,
|
|
14
|
-
environment: process.env.SENTRY_ENV,
|
|
15
|
-
release: version
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export const slackConfig = {
|
|
19
|
-
token: process.env.SLACK_TOKEN,
|
|
20
|
-
channel: process.env.SLACK_CHANNEL
|
|
21
|
-
};
|
package/src/cors.spec.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai';
|
|
2
|
-
import * as sinon from 'sinon';
|
|
3
|
-
import 'mocha';
|
|
4
|
-
|
|
5
|
-
import { whitelistOrigin } from './cors';
|
|
6
|
-
|
|
7
|
-
describe('cors', () => {
|
|
8
|
-
describe('whitelistOrigin', () => {
|
|
9
|
-
const origin = 'http://localhost';
|
|
10
|
-
let callback: sinon.SinonStub;
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
callback = sinon.stub();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('should allow localhost', () => {
|
|
17
|
-
whitelistOrigin(origin, callback);
|
|
18
|
-
expect(callback.calledWith(null, true)).to.equal(true);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('should allow no origin', () => {
|
|
22
|
-
whitelistOrigin(undefined, callback);
|
|
23
|
-
expect(callback.calledWith(null, true)).to.equal(true);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('should not allow other origin', () => {
|
|
27
|
-
const other = 'other origin';
|
|
28
|
-
whitelistOrigin(other, callback);
|
|
29
|
-
expect(callback.calledWith(null, true)).to.equal(false);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
});
|
package/src/cors.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { whitelist } from './config';
|
|
2
|
-
import { HestiaError } from './errors';
|
|
3
|
-
|
|
4
|
-
export const whitelistOrigin = (origin: string, callback: Function) =>
|
|
5
|
-
!origin || whitelist.includes(origin)
|
|
6
|
-
? callback(null, true)
|
|
7
|
-
: callback(new HestiaError(`${origin} is not allowed by CORS`));
|
package/src/errors.spec.ts
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai';
|
|
2
|
-
import * as sinon from 'sinon';
|
|
3
|
-
import 'mocha';
|
|
4
|
-
|
|
5
|
-
import { logger } from './logger';
|
|
6
|
-
import { Errors, errorHandler, HestiaError } from './errors';
|
|
7
|
-
|
|
8
|
-
class Response {
|
|
9
|
-
statusCode = 200;
|
|
10
|
-
json() {}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
let stubs: sinon.SinonStub[] = [];
|
|
14
|
-
|
|
15
|
-
describe('errors', () => {
|
|
16
|
-
let errorStub: sinon.SinonStub;
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
stubs = [];
|
|
20
|
-
stubs.push((errorStub = sinon.stub(logger, 'error')));
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
afterEach(() => {
|
|
24
|
-
stubs.forEach((stub) => stub.restore());
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe('errorHandler', () => {
|
|
28
|
-
let error: any = {
|
|
29
|
-
message: 'error'
|
|
30
|
-
};
|
|
31
|
-
const response = new Response();
|
|
32
|
-
|
|
33
|
-
it('should set status code to 500', () => {
|
|
34
|
-
errorHandler(error, null, response as any);
|
|
35
|
-
expect(response.statusCode).to.equal(500);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should render error as json', () => {
|
|
39
|
-
let stub: sinon.SinonStub;
|
|
40
|
-
stubs.push((stub = sinon.stub(response, 'json')));
|
|
41
|
-
errorHandler(error, null, response as any);
|
|
42
|
-
|
|
43
|
-
expect(
|
|
44
|
-
stub.calledWith({
|
|
45
|
-
message: error.message
|
|
46
|
-
})
|
|
47
|
-
).to.equal(true);
|
|
48
|
-
stub.restore();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should log Error stack', () => {
|
|
52
|
-
const err = new Error();
|
|
53
|
-
errorHandler(err, null, response as any);
|
|
54
|
-
expect(errorStub.calledWith(err.stack)).to.equal(true);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
describe('Hestia error', () => {
|
|
58
|
-
beforeEach(() => {
|
|
59
|
-
error = new HestiaError('error');
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('should not log the error', () => {
|
|
63
|
-
errorHandler(error, null, response as any);
|
|
64
|
-
expect(errorStub.called).to.equal(false);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('should set status code to 400', () => {
|
|
68
|
-
errorHandler(error, null, response as any);
|
|
69
|
-
expect(response.statusCode).to.equal(400);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe(Errors.Unauthorized, () => {
|
|
74
|
-
beforeEach(() => {
|
|
75
|
-
error = { message: Errors.Unauthorized };
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('should set status code to 401', () => {
|
|
79
|
-
errorHandler(error, null, response as any);
|
|
80
|
-
expect(response.statusCode).to.equal(401);
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
describe(Errors.NotFound, () => {
|
|
85
|
-
beforeEach(() => {
|
|
86
|
-
error = { message: Errors.NotFound };
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('should set status code to 404', () => {
|
|
90
|
-
errorHandler(error, null, response as any);
|
|
91
|
-
expect(response.statusCode).to.equal(404);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe('no message', () => {
|
|
96
|
-
beforeEach(() => {
|
|
97
|
-
error = {};
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('should render default error', () => {
|
|
101
|
-
let stub: sinon.SinonStub;
|
|
102
|
-
stubs.push((stub = sinon.stub(response, 'json')));
|
|
103
|
-
errorHandler(error, null, response as any);
|
|
104
|
-
|
|
105
|
-
expect(
|
|
106
|
-
stub.calledWith({
|
|
107
|
-
message: 'There was an error processing your request.'
|
|
108
|
-
})
|
|
109
|
-
).to.equal(true);
|
|
110
|
-
stub.restore();
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
});
|
package/src/errors.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { Express, Request, Response, NextFunction } from 'express';
|
|
2
|
-
import * as Sentry from '@sentry/node';
|
|
3
|
-
|
|
4
|
-
import { logger } from './logger';
|
|
5
|
-
|
|
6
|
-
export enum Errors {
|
|
7
|
-
UnderMaintenance = 'under-maintenance',
|
|
8
|
-
ReadOnly = 'read-only',
|
|
9
|
-
InvalidQueryParam = 'invalid-param-query',
|
|
10
|
-
MissingQueryParam = 'missing-param-query',
|
|
11
|
-
InvalidBodyParam = 'invalid-param-body',
|
|
12
|
-
MissingBodyParam = 'missing-param-body',
|
|
13
|
-
SchemaInvalid = 'invalid-schema',
|
|
14
|
-
NotFound = 'not-found',
|
|
15
|
-
Unauthorized = 'Unauthorized'
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export enum StatusCodes {
|
|
19
|
-
MethodNotAllowed = 405,
|
|
20
|
-
RateLimit = 429,
|
|
21
|
-
ServiceUnavailable = 503,
|
|
22
|
-
InternalServerError = 500
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export class HestiaError extends Error {
|
|
26
|
-
constructor(message: string, public details = {}, public statusCode?: StatusCodes) {
|
|
27
|
-
super(message);
|
|
28
|
-
this.name = 'HestiaError';
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export const throwError = (message: string, details?: any, statusCode?: StatusCodes) => {
|
|
33
|
-
throw new HestiaError(message, details, statusCode);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const isHestiaError = (error: Error) => error.name === 'HestiaError';
|
|
37
|
-
|
|
38
|
-
export const shouldHandleError = (error: Error) => !isHestiaError(error);
|
|
39
|
-
|
|
40
|
-
const statusCode = (error: Error) =>
|
|
41
|
-
(error as any).statusCode ||
|
|
42
|
-
(errorMessage(error).includes(Errors.NotFound)
|
|
43
|
-
? 404
|
|
44
|
-
: error.message === Errors.Unauthorized
|
|
45
|
-
? 401
|
|
46
|
-
: isHestiaError(error)
|
|
47
|
-
? 400
|
|
48
|
-
: 500);
|
|
49
|
-
|
|
50
|
-
export const errorMessage = (error?: Error) => error?.message || 'There was an error processing your request.';
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* @swagger
|
|
54
|
-
*
|
|
55
|
-
* components:
|
|
56
|
-
* responses:
|
|
57
|
-
* DefaultObject:
|
|
58
|
-
* content:
|
|
59
|
-
* application/json:
|
|
60
|
-
* schema:
|
|
61
|
-
* type: object
|
|
62
|
-
* BadRequestError:
|
|
63
|
-
* description: Bad Request
|
|
64
|
-
* content:
|
|
65
|
-
* application/json:
|
|
66
|
-
* schema:
|
|
67
|
-
* $ref: '#/components/schemas/Error'
|
|
68
|
-
* UnauthorizedError:
|
|
69
|
-
* description: Unauthorized
|
|
70
|
-
* content:
|
|
71
|
-
* application/json:
|
|
72
|
-
* schema:
|
|
73
|
-
* $ref: '#/components/schemas/Error'
|
|
74
|
-
* example:
|
|
75
|
-
* - message: Unauthorized
|
|
76
|
-
* NotFoundError:
|
|
77
|
-
* description: The requested ressource was not found
|
|
78
|
-
* content:
|
|
79
|
-
* application/json:
|
|
80
|
-
* schema:
|
|
81
|
-
* $ref: '#/components/schemas/Error'
|
|
82
|
-
* UnexpectedError:
|
|
83
|
-
* description: Unexpected error
|
|
84
|
-
* content:
|
|
85
|
-
* application/json:
|
|
86
|
-
* schema:
|
|
87
|
-
* $ref: '#/components/schemas/Error'
|
|
88
|
-
* ServiceUnavailable:
|
|
89
|
-
* description: Service Unavailable due to Maintenance
|
|
90
|
-
* content:
|
|
91
|
-
* application/json:
|
|
92
|
-
* schema:
|
|
93
|
-
* $ref: '#/components/schemas/Error'
|
|
94
|
-
* NoContent:
|
|
95
|
-
* description: Return nothing
|
|
96
|
-
*
|
|
97
|
-
* schemas:
|
|
98
|
-
* Error:
|
|
99
|
-
* type: object
|
|
100
|
-
* required:
|
|
101
|
-
* - message
|
|
102
|
-
* properties:
|
|
103
|
-
* message:
|
|
104
|
-
* type: string
|
|
105
|
-
*/
|
|
106
|
-
export const errorHandler = (err: any, _r: Request, res: Response, _n?: NextFunction) => {
|
|
107
|
-
// error was not thrown intentionally
|
|
108
|
-
const unexpectedError = shouldHandleError(err);
|
|
109
|
-
unexpectedError && logger.error(err.stack ? err.stack : err);
|
|
110
|
-
const message = errorMessage(err);
|
|
111
|
-
res.statusCode = statusCode(err);
|
|
112
|
-
return res.json({
|
|
113
|
-
message,
|
|
114
|
-
...('details' in err ? { details: err.details } : {})
|
|
115
|
-
});
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
export default (app: Express) => {
|
|
119
|
-
app.use(Sentry.Handlers.errorHandler({ shouldHandleError }));
|
|
120
|
-
app.use(errorHandler);
|
|
121
|
-
};
|
package/src/index.spec.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai';
|
|
2
|
-
import * as sinon from 'sinon';
|
|
3
|
-
import 'mocha';
|
|
4
|
-
|
|
5
|
-
import { run } from './index';
|
|
6
|
-
import { app } from './app';
|
|
7
|
-
import { logger } from './logger';
|
|
8
|
-
import { PORT } from './config';
|
|
9
|
-
|
|
10
|
-
let stubs: sinon.SinonStub[] = [];
|
|
11
|
-
|
|
12
|
-
describe('run', () => {
|
|
13
|
-
const server: any = {};
|
|
14
|
-
let loggerStub: sinon.SinonStub;
|
|
15
|
-
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
stubs = [];
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
afterEach(() => {
|
|
21
|
-
stubs.forEach((stub) => stub.restore());
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
describe('start error', () => {
|
|
25
|
-
beforeEach(() => {
|
|
26
|
-
loggerStub = sinon.stub(logger, 'error');
|
|
27
|
-
stubs.push(loggerStub);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe('error as string', () => {
|
|
31
|
-
const error = 'error';
|
|
32
|
-
|
|
33
|
-
beforeEach(() => {
|
|
34
|
-
stubs.push(
|
|
35
|
-
sinon.stub(app, 'listen').returns({
|
|
36
|
-
on: (_p, callback: any) => {
|
|
37
|
-
callback(error);
|
|
38
|
-
return server;
|
|
39
|
-
}
|
|
40
|
-
} as any)
|
|
41
|
-
);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('should log the error', () => {
|
|
45
|
-
run();
|
|
46
|
-
expect(loggerStub.calledWith(error)).to.equal(true);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
describe('error as Error', () => {
|
|
51
|
-
let error: Error;
|
|
52
|
-
|
|
53
|
-
beforeEach(() => {
|
|
54
|
-
error = new Error('error');
|
|
55
|
-
|
|
56
|
-
stubs.push(
|
|
57
|
-
sinon.stub(app, 'listen').returns({
|
|
58
|
-
on: (_p, callback: any) => {
|
|
59
|
-
callback(error);
|
|
60
|
-
return server;
|
|
61
|
-
}
|
|
62
|
-
} as any)
|
|
63
|
-
);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('should log the error', () => {
|
|
67
|
-
run();
|
|
68
|
-
expect(loggerStub.calledWith(error.stack)).to.equal(true);
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe('start success', () => {
|
|
74
|
-
beforeEach(() => {
|
|
75
|
-
stubs.push(
|
|
76
|
-
sinon.stub(app, 'listen').callsFake((_p, callback) => {
|
|
77
|
-
callback();
|
|
78
|
-
return {
|
|
79
|
-
on: (_p1, _cb: any) => {
|
|
80
|
-
return server;
|
|
81
|
-
}
|
|
82
|
-
} as any;
|
|
83
|
-
})
|
|
84
|
-
);
|
|
85
|
-
loggerStub = sinon.stub(logger, 'info');
|
|
86
|
-
stubs.push(loggerStub);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('should log the port', () => {
|
|
90
|
-
run();
|
|
91
|
-
expect(loggerStub.calledWith(`App running on port ${PORT}`)).to.equal(true);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import '../database';
|
|
2
|
-
import { logger } from './logger';
|
|
3
|
-
import { PORT } from './config';
|
|
4
|
-
import { app } from './app';
|
|
5
|
-
|
|
6
|
-
export const run = () => {
|
|
7
|
-
const server = app
|
|
8
|
-
.listen(PORT, () => logger.info(`App running on port ${PORT}`))
|
|
9
|
-
.on('error', (err) => logger.error(err.stack || err));
|
|
10
|
-
|
|
11
|
-
// ALB idle timeout set to 300 seconds
|
|
12
|
-
server.keepAliveTimeout = 301 * 1000;
|
|
13
|
-
server.headersTimeout = 305 * 1000;
|
|
14
|
-
};
|
package/src/lambdas/sentry.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import * as Sentry from '@sentry/serverless';
|
|
2
|
-
|
|
3
|
-
import pkg from '../../package.json';
|
|
4
|
-
|
|
5
|
-
Sentry.AWSLambda.init({
|
|
6
|
-
dsn: process.env.SENTRY_DSN,
|
|
7
|
-
environment: process.env.STAGE,
|
|
8
|
-
release: pkg.version,
|
|
9
|
-
enabled: process.env.IS_OFFLINE !== 'true'
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
export const wrapHandler = handler => Sentry.AWSLambda.wrapHandler(handler);
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import 'mocha';
|
|
2
|
-
import sinon from 'sinon';
|
|
3
|
-
import { of } from 'rxjs';
|
|
4
|
-
import { expect } from 'chai';
|
|
5
|
-
import * as s3 from '@hestia-earth/pipeline-utils/dist/s3';
|
|
6
|
-
import { sortBy as _sortBy } from 'lodash';
|
|
7
|
-
|
|
8
|
-
import { loadFixture, initDb } from '../../../test/utils';
|
|
9
|
-
import { run } from './handler';
|
|
10
|
-
import { query } from '../../../database';
|
|
11
|
-
import * as testSeeds from '../../../database/seed/test';
|
|
12
|
-
|
|
13
|
-
const stubs: sinon.SinonStub[] = [];
|
|
14
|
-
|
|
15
|
-
const cycles = ['abyssinianKaleSeedWhole-cycle_pivoted.csv', 'tomatoFruit-cycle_pivoted.csv'];
|
|
16
|
-
|
|
17
|
-
const impactAssessments = [
|
|
18
|
-
'abyssinianKaleSeedWhole-impactassessment_pivoted.csv',
|
|
19
|
-
'tomatoFruit-impactassessment_pivoted.csv'
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
const keys = [
|
|
23
|
-
...cycles,
|
|
24
|
-
...impactAssessments,
|
|
25
|
-
// abyssinianKaleStraw should be ignored as no ref to a cycle
|
|
26
|
-
'abyssinianKaleStraw-impactassessment_pivoted.csv',
|
|
27
|
-
// hould be ignored as cycle.id refers to a non-existent cycle
|
|
28
|
-
'cycle-missing-impactassessment_pivoted.csv'
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
const fixture = (fileName: string) => loadFixture(`update-aggregated-nodes/${fileName}`);
|
|
32
|
-
|
|
33
|
-
const getJsonResults = (rows) =>
|
|
34
|
-
rows
|
|
35
|
-
.map((r) => r.jsonld_pivoted)
|
|
36
|
-
.filter(
|
|
37
|
-
(json) =>
|
|
38
|
-
![
|
|
39
|
-
testSeeds.aggregatedCycles[0].jsonld_pivoted['@id'],
|
|
40
|
-
testSeeds.aggregatedSites[0].jsonld_pivoted['@id'],
|
|
41
|
-
testSeeds.aggregatedImpactAssessments[0].jsonld_pivoted['@id']
|
|
42
|
-
].includes(json['@id'])
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
describe('update-aggregated-nodes > run', () => {
|
|
46
|
-
beforeEach(async () => {
|
|
47
|
-
await initDb();
|
|
48
|
-
stubs.push(sinon.stub(s3, 'listFolder').resolves(keys.map((Key) => ({ Key }))));
|
|
49
|
-
stubs.push(sinon.stub(s3, 'getObject').callsFake(({ Key }) => of(fixture(Key)) as any));
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
afterEach(() => {
|
|
53
|
-
stubs.forEach((stub) => stub.restore());
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('should save the pivoted csv files in the db', async () => {
|
|
57
|
-
await run();
|
|
58
|
-
|
|
59
|
-
const { rows: siteRows } = await query('SELECT * FROM aggregated_sites');
|
|
60
|
-
const siteResult = _sortBy(getJsonResults(siteRows), '@id');
|
|
61
|
-
const jsonPivotedSites = _sortBy(
|
|
62
|
-
cycles.flatMap((key) => JSON.parse(fixture(`${key}.site.json`))),
|
|
63
|
-
'@id'
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
expect(siteResult.sort()).to.deep.equal(jsonPivotedSites.sort());
|
|
67
|
-
|
|
68
|
-
const { rows: cycleRows } = await query('SELECT * FROM aggregated_cycles');
|
|
69
|
-
const cycleResult = _sortBy(getJsonResults(cycleRows), '@id');
|
|
70
|
-
const jsonPivotedCycles = _sortBy(
|
|
71
|
-
cycles.flatMap((key) => JSON.parse(fixture(`${key}.cycle.json`))),
|
|
72
|
-
'@id'
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
expect(cycleResult).to.deep.equal(jsonPivotedCycles);
|
|
76
|
-
|
|
77
|
-
const { rows: iaRows } = await query('SELECT * FROM aggregated_impact_assessments');
|
|
78
|
-
const iaResult = _sortBy(getJsonResults(iaRows), '@id');
|
|
79
|
-
const jsonPivotedIas = _sortBy(
|
|
80
|
-
impactAssessments.flatMap((key) => JSON.parse(fixture(`${key}.impactAssessment.json`))),
|
|
81
|
-
'@id'
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
expect(iaResult.sort()).to.deep.equal(jsonPivotedIas.sort());
|
|
85
|
-
});
|
|
86
|
-
});
|