@hestia-earth/data-api 0.0.2-3 → 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 +1 -1
- package/.dockerignore +0 -25
- package/.env.test +0 -7
- package/.eslintignore +0 -7
- package/.eslintrc.js +0 -11
- package/.gitlab-ci.yml +0 -128
- package/.mocharc.js +0 -8
- package/.nvmrc +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 -60
- 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/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/package.serverless.json +0 -21
- 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 -101
- package/src/aggregated-nodes/model/index.ts +0 -37
- package/src/aggregated-nodes/routes/pg-get-filters.ts +0 -54
- package/src/aggregated-nodes/routes/pg-get.ts +0 -61
- package/src/aggregated-nodes/routes.spec.ts +0 -274
- package/src/aggregated-nodes/routes.ts +0 -56
- package/src/aggregated-nodes/services/pg-get-filters.ts +0 -62
- 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 -77
- package/src/lambdas/update-aggregated-nodes/handler.ts +0 -129
- 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 -196
- 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 -32
- package/tsconfig.build.json +0 -13
- package/tsconfig.dist.json +0 -14
- package/tsconfig.json +0 -42
- package/tsconfig.lambdas.json +0 -13
package/scripts/run-lambda.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
import * as dotenv from 'dotenv';
|
|
3
|
-
dotenv.config();
|
|
4
|
-
|
|
5
|
-
import { updateAggregatedNodes } from '../src/lambdas/update-aggregated-nodes/handler';
|
|
6
|
-
|
|
7
|
-
updateAggregatedNodes().then(() => process.exit(0)).catch(err => {
|
|
8
|
-
console.error(err);
|
|
9
|
-
process.exit(1);
|
|
10
|
-
});
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
import * as dotenv from 'dotenv';
|
|
3
|
-
dotenv.config();
|
|
4
|
-
|
|
5
|
-
import { runMigrations } from '../database/migrations';
|
|
6
|
-
|
|
7
|
-
void (async function () {
|
|
8
|
-
const { gracefulExit } = await import('exit-hook');
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
await runMigrations();
|
|
12
|
-
gracefulExit(0);
|
|
13
|
-
}
|
|
14
|
-
catch (error) {
|
|
15
|
-
console.error(error);
|
|
16
|
-
gracefulExit(1);
|
|
17
|
-
}
|
|
18
|
-
})();
|
package/scripts/run-resetdb.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
import * as dotenv from 'dotenv';
|
|
3
|
-
dotenv.config();
|
|
4
|
-
|
|
5
|
-
import { resetDb } from '../test/utils';
|
|
6
|
-
|
|
7
|
-
void (async function () {
|
|
8
|
-
const { gracefulExit } = await import('exit-hook');
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
await resetDb();
|
|
12
|
-
gracefulExit(0);
|
|
13
|
-
}
|
|
14
|
-
catch (error) {
|
|
15
|
-
console.error(error);
|
|
16
|
-
gracefulExit(1);
|
|
17
|
-
}
|
|
18
|
-
})();
|
package/scripts/run-seed.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
import * as dotenv from 'dotenv';
|
|
3
|
-
dotenv.config();
|
|
4
|
-
|
|
5
|
-
import { runSeed } from '../database/seed';
|
|
6
|
-
|
|
7
|
-
void (async function () {
|
|
8
|
-
const { gracefulExit } = await import('exit-hook');
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
await runSeed();
|
|
12
|
-
gracefulExit(0);
|
|
13
|
-
}
|
|
14
|
-
catch (error) {
|
|
15
|
-
console.error(error);
|
|
16
|
-
gracefulExit(1);
|
|
17
|
-
}
|
|
18
|
-
})();
|
package/serverless.yml
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
frameworkVersion: '3'
|
|
2
|
-
|
|
3
|
-
service: hestia-data-api
|
|
4
|
-
|
|
5
|
-
plugins:
|
|
6
|
-
- serverless-deployment-bucket
|
|
7
|
-
- serverless-offline
|
|
8
|
-
|
|
9
|
-
package:
|
|
10
|
-
exclude:
|
|
11
|
-
- node_modules/@types/**
|
|
12
|
-
# cannot be removed as used in migrations
|
|
13
|
-
# - node_modules/typescript/**
|
|
14
|
-
- node_modules/**/*.map
|
|
15
|
-
- node_modules/**/*.d.ts
|
|
16
|
-
- node_modules/**/coverage/**
|
|
17
|
-
- node_modules/**/tests/**
|
|
18
|
-
- node_modules/**/test/**
|
|
19
|
-
- .nyc_output/**
|
|
20
|
-
- coverage/**
|
|
21
|
-
- data/**
|
|
22
|
-
- docs/**
|
|
23
|
-
- envs/**
|
|
24
|
-
- samples/**
|
|
25
|
-
- scripts/**
|
|
26
|
-
- test/**
|
|
27
|
-
- tmp/**
|
|
28
|
-
- tsconfig*
|
|
29
|
-
- "*.yml"
|
|
30
|
-
- "*.md"
|
|
31
|
-
- "*.sh"
|
|
32
|
-
- ".dockerignore"
|
|
33
|
-
- "Dockerfile"
|
|
34
|
-
- "gulpfile.js"
|
|
35
|
-
|
|
36
|
-
custom:
|
|
37
|
-
serverless-offline:
|
|
38
|
-
httpPort: 3002
|
|
39
|
-
stage: ${env:STAGE, self:provider.stage}
|
|
40
|
-
securityGroup:
|
|
41
|
-
staging: sg-0800627978a40527b
|
|
42
|
-
prod: sg-049204c3b58a8bd08
|
|
43
|
-
subnetId1:
|
|
44
|
-
# private 1a
|
|
45
|
-
staging: subnet-04ad6e355249c757d
|
|
46
|
-
prod: subnet-00c4a891bce6f48e0
|
|
47
|
-
subnetId2:
|
|
48
|
-
# private 1b
|
|
49
|
-
staging: subnet-01260544aaa3c6810
|
|
50
|
-
prod: subnet-02365f46c569f5e1a
|
|
51
|
-
debugging:
|
|
52
|
-
staging: true
|
|
53
|
-
prod: false
|
|
54
|
-
logLevel:
|
|
55
|
-
staging: DEBUG
|
|
56
|
-
prod: INFO
|
|
57
|
-
bucket: hestia-data-${self:custom.stage}
|
|
58
|
-
|
|
59
|
-
provider:
|
|
60
|
-
name: aws
|
|
61
|
-
runtime: nodejs18.x
|
|
62
|
-
region: us-east-1
|
|
63
|
-
stage: staging
|
|
64
|
-
deploymentBucket:
|
|
65
|
-
name: hestia-serverless-${self:custom.stage}
|
|
66
|
-
memorySize: 512
|
|
67
|
-
timeout: 600
|
|
68
|
-
environment:
|
|
69
|
-
SERVICE_NAME: ${self:service}
|
|
70
|
-
STAGE: ${self:custom.stage}
|
|
71
|
-
DEBUG: ${self:custom.debugging.${self:custom.stage}}
|
|
72
|
-
LOG_LEVEL: ${self:custom.logLevel.${self:custom.stage}}
|
|
73
|
-
SENTRY_DSN: https://00ed710e2b664894a3ff3124bfff24cf@o441427.ingest.sentry.io/4505186792767488
|
|
74
|
-
BUCKET: ${self:custom.bucket}
|
|
75
|
-
PGHOST: ${env:PGHOST}
|
|
76
|
-
PGDATABASE: ${env:PGDATABASE}
|
|
77
|
-
PGPORT: ${env:PGPORT}
|
|
78
|
-
PGUSER: ${env:PGUSER}
|
|
79
|
-
PGPASSWORD: ${env:PGPASSWORD}
|
|
80
|
-
iamRoleStatements:
|
|
81
|
-
- Effect: Allow
|
|
82
|
-
Action:
|
|
83
|
-
- s3:GetObject
|
|
84
|
-
- s3:GetObjectAcl
|
|
85
|
-
- s3:ListObjects
|
|
86
|
-
- s3:ListBucket
|
|
87
|
-
Resource:
|
|
88
|
-
- "arn:aws:s3:::${self:custom.bucket}"
|
|
89
|
-
- "arn:aws:s3:::${self:custom.bucket}/*"
|
|
90
|
-
vpc:
|
|
91
|
-
securityGroupIds:
|
|
92
|
-
- ${self:custom.securityGroup.${self:custom.stage}}
|
|
93
|
-
subnetIds:
|
|
94
|
-
- ${self:custom.subnetId1.${self:custom.stage}}
|
|
95
|
-
- ${self:custom.subnetId2.${self:custom.stage}}
|
|
96
|
-
|
|
97
|
-
functions:
|
|
98
|
-
updateAggregatedNodes:
|
|
99
|
-
handler: build/src/lambdas/update-aggregated-nodes/handler.updateAggregatedNodes
|
|
100
|
-
events:
|
|
101
|
-
- schedule: cron(0 2 * * ? *) # 2:00am every day
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { ISiteJSONLD } from '@hestia-earth/schema';
|
|
2
|
-
|
|
3
|
-
export interface IFilter {
|
|
4
|
-
regions?: string[];
|
|
5
|
-
periods?: string[];
|
|
6
|
-
products?: string[];
|
|
7
|
-
defaultMethodClassifications?: string[];
|
|
8
|
-
practices?: string[];
|
|
9
|
-
minAggregatedQualityScore?: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const validFilterFields = [
|
|
13
|
-
'regions',
|
|
14
|
-
'periods',
|
|
15
|
-
'products',
|
|
16
|
-
'defaultMethodClassifications',
|
|
17
|
-
'practices',
|
|
18
|
-
'minAggregatedQualityScore'
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
export interface IFilters {
|
|
22
|
-
cycleCount: number;
|
|
23
|
-
impactAssessmentCount: number;
|
|
24
|
-
products: string[];
|
|
25
|
-
periods: string[];
|
|
26
|
-
practices: string[];
|
|
27
|
-
minAggregatedQualityScore: number[];
|
|
28
|
-
regions: string[];
|
|
29
|
-
defaultMethodClassifications: string[];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// TODO: update with pivoted types when available
|
|
33
|
-
export interface AggregatedNodeRow {
|
|
34
|
-
cycle: any; // pivoted cycle
|
|
35
|
-
region: ISiteJSONLD['country'];
|
|
36
|
-
impactAssessments: [any]; // pivoted impact assessments
|
|
37
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { Request } from 'express';
|
|
2
|
-
|
|
3
|
-
import { applyFilters } from '../services/pg-get-filters';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @swagger
|
|
7
|
-
*
|
|
8
|
-
* paths:
|
|
9
|
-
* /aggregated-nodes/pg/filters:
|
|
10
|
-
* get:
|
|
11
|
-
* summary: "Get the additional filters available for a filter selection and node counts (uses PG only)"
|
|
12
|
-
* parameters:
|
|
13
|
-
* - in: query
|
|
14
|
-
* name: filter
|
|
15
|
-
* schema:
|
|
16
|
-
* type: string
|
|
17
|
-
* examples:
|
|
18
|
-
* example1:
|
|
19
|
-
* summary: Barley in Australia and India between 2010 and 2019.
|
|
20
|
-
* value: "regions=GADM-AUS|GADM-IND;\
|
|
21
|
-
* products=barleyGrainWhole|barleyStraw;\
|
|
22
|
-
* periods=2010-2019"
|
|
23
|
-
* example2:
|
|
24
|
-
* summary: Wheat and Maize in France, United Kingdom and Worldwide between 2000 and 2019.
|
|
25
|
-
* value: "regions=region-world|GADM-GBR|GADM-FRA;\
|
|
26
|
-
* products=wheatGrain|maizeGrain;\
|
|
27
|
-
* periods=2000-2009|2010-2019"
|
|
28
|
-
* description: Set of filters
|
|
29
|
-
* - in: query
|
|
30
|
-
* name: query
|
|
31
|
-
* schema:
|
|
32
|
-
* type: string
|
|
33
|
-
* example: Wheat
|
|
34
|
-
* description: Free search text
|
|
35
|
-
* responses:
|
|
36
|
-
* 200:
|
|
37
|
-
* content:
|
|
38
|
-
* application/json:
|
|
39
|
-
* schema:
|
|
40
|
-
* type: object
|
|
41
|
-
* 400:
|
|
42
|
-
* $ref: '#/components/responses/BadRequestError'
|
|
43
|
-
* 404:
|
|
44
|
-
* $ref: '#/components/responses/NotFoundError'
|
|
45
|
-
* 500:
|
|
46
|
-
* $ref: '#/components/responses/UnexpectedError'
|
|
47
|
-
* 503:
|
|
48
|
-
* $ref: '#/components/responses/ServiceUnavailable'
|
|
49
|
-
*/
|
|
50
|
-
|
|
51
|
-
export const getFilters = ({ query: qs, filter }: Request) => {
|
|
52
|
-
const query = qs.query as string;
|
|
53
|
-
return applyFilters({ filter, query });
|
|
54
|
-
};
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/* eslint-disable max-len */
|
|
2
|
-
import { Request } from 'express';
|
|
3
|
-
|
|
4
|
-
import { applyFilter } from '../services/pg-get';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @swagger
|
|
8
|
-
*
|
|
9
|
-
* paths:
|
|
10
|
-
* /aggregated-nodes/pg:
|
|
11
|
-
* get:
|
|
12
|
-
* summary: "Get the aggregated nodes corresponding to a set of filters and search query (uses PG only)"
|
|
13
|
-
* parameters:
|
|
14
|
-
* - in: query
|
|
15
|
-
* name: filter
|
|
16
|
-
* schema:
|
|
17
|
-
* type: string
|
|
18
|
-
* examples:
|
|
19
|
-
* example1:
|
|
20
|
-
* summary: Barley in Australia and India between 2010 and 2019.
|
|
21
|
-
* value: "regions=GADM-AUS|GADM-IND;\
|
|
22
|
-
* products=barleyGrainWhole|barleyStraw;\
|
|
23
|
-
* periods=2010-2019"
|
|
24
|
-
* example2:
|
|
25
|
-
* summary: Wheat and Maize in France, United Kingdom and Worldwide between 2000 and 2019.
|
|
26
|
-
* value: "regions=region-world|GADM-GBR|GADM-FRA;\
|
|
27
|
-
* products=wheatGrain|maizeGrain;\
|
|
28
|
-
* periods=2000-2009|2010-2019"
|
|
29
|
-
* description: Set of filters
|
|
30
|
-
* - in: query
|
|
31
|
-
* name: page
|
|
32
|
-
* schema:
|
|
33
|
-
* type: integer
|
|
34
|
-
* example: 1
|
|
35
|
-
* - in: query
|
|
36
|
-
* name: query
|
|
37
|
-
* schema:
|
|
38
|
-
* type: string
|
|
39
|
-
* example: Wheat
|
|
40
|
-
* description: Free search text
|
|
41
|
-
* responses:
|
|
42
|
-
* 200:
|
|
43
|
-
* content:
|
|
44
|
-
* application/json:
|
|
45
|
-
* schema:
|
|
46
|
-
* type: object
|
|
47
|
-
* 400:
|
|
48
|
-
* $ref: '#/components/responses/BadRequestError'
|
|
49
|
-
* 404:
|
|
50
|
-
* $ref: '#/components/responses/NotFoundError'
|
|
51
|
-
* 500:
|
|
52
|
-
* $ref: '#/components/responses/UnexpectedError'
|
|
53
|
-
* 503:
|
|
54
|
-
* $ref: '#/components/responses/ServiceUnavailable'
|
|
55
|
-
*/
|
|
56
|
-
|
|
57
|
-
export const get = ({ query: qs, filter }: Request) => {
|
|
58
|
-
const page = Number(qs.page) || 1;
|
|
59
|
-
const query = qs.query as string;
|
|
60
|
-
return applyFilter({ page, filter, query });
|
|
61
|
-
};
|
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai';
|
|
2
|
-
import * as sinon from 'sinon';
|
|
3
|
-
import 'mocha';
|
|
4
|
-
import express from 'express';
|
|
5
|
-
import supertest from 'supertest';
|
|
6
|
-
import bodyParser from 'body-parser';
|
|
7
|
-
|
|
8
|
-
import router from './routes';
|
|
9
|
-
import { Errors, errorHandler } from '../errors';
|
|
10
|
-
import { initDb } from '../../test/utils';
|
|
11
|
-
import { seed } from '../../database/seed';
|
|
12
|
-
|
|
13
|
-
import * as fixtures from '../../test/fixtures/aggregated-nodes/get';
|
|
14
|
-
|
|
15
|
-
let stubs: sinon.SinonStub[] = [];
|
|
16
|
-
|
|
17
|
-
const app = express();
|
|
18
|
-
|
|
19
|
-
const getFixtureNode = (type: 'site' | 'cycle' | 'impactAssessment', id: string) =>
|
|
20
|
-
fixtures[
|
|
21
|
-
{ cycle: 'aggregatedCycles', site: 'aggregatedSites', impactAssessment: 'aggregatedImpactAssessments' }[type]
|
|
22
|
-
].find((node) => node.jsonld_pivoted['@id'] === id).jsonld_pivoted;
|
|
23
|
-
|
|
24
|
-
app.use(bodyParser.json());
|
|
25
|
-
app.use(router());
|
|
26
|
-
app.use(errorHandler);
|
|
27
|
-
|
|
28
|
-
describe('routes', () => {
|
|
29
|
-
beforeEach(() => {
|
|
30
|
-
stubs = [];
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
afterEach(() => {
|
|
34
|
-
stubs.forEach((stub) => stub.restore());
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
describe('aggregated-nodes', () => {
|
|
38
|
-
beforeEach(async () => {
|
|
39
|
-
await initDb();
|
|
40
|
-
await seed(fixtures);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe('GET /pg', () => {
|
|
44
|
-
it('when invalid filter passed returns invalid param error', async () => {
|
|
45
|
-
const result = await supertest(app)
|
|
46
|
-
.get('/pg')
|
|
47
|
-
.query({ filter: 'invalidfilter' })
|
|
48
|
-
.set('Accept', 'application/json');
|
|
49
|
-
|
|
50
|
-
expect(result.body.message).to.equal(Errors.InvalidQueryParam);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('when neither filter nor query passed returns missing param error', async () => {
|
|
54
|
-
const result = await supertest(app).get('/pg').query({}).set('Accept', 'application/json');
|
|
55
|
-
|
|
56
|
-
expect(result.body.message).to.equal(Errors.MissingQueryParam);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('can filter by regions', async () => {
|
|
60
|
-
const expected = [
|
|
61
|
-
{
|
|
62
|
-
cycle: getFixtureNode('cycle', '1'),
|
|
63
|
-
region: getFixtureNode('site', '1').country,
|
|
64
|
-
impactAssessments: [getFixtureNode('impactAssessment', '1'), getFixtureNode('impactAssessment', '2')]
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
cycle: getFixtureNode('cycle', '2'),
|
|
68
|
-
region: getFixtureNode('site', '1').country,
|
|
69
|
-
impactAssessments: [getFixtureNode('impactAssessment', '3')]
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
cycle: getFixtureNode('cycle', '3'),
|
|
73
|
-
region: getFixtureNode('site', '2').country,
|
|
74
|
-
impactAssessments: [getFixtureNode('impactAssessment', '4')]
|
|
75
|
-
}
|
|
76
|
-
];
|
|
77
|
-
|
|
78
|
-
const filter = 'regions=GADM-POL|GADM-DEU';
|
|
79
|
-
const result = await supertest(app).get('/pg').query({ filter }).set('Accept', 'application/json');
|
|
80
|
-
expect(result.body.results).to.deep.equal(expected);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('can filter by practices', async () => {
|
|
84
|
-
const expected = [
|
|
85
|
-
{
|
|
86
|
-
cycle: getFixtureNode('cycle', '1'),
|
|
87
|
-
region: getFixtureNode('site', '1').country,
|
|
88
|
-
impactAssessments: [getFixtureNode('impactAssessment', '1'), getFixtureNode('impactAssessment', '2')]
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
cycle: getFixtureNode('cycle', '2'),
|
|
92
|
-
region: getFixtureNode('site', '1').country,
|
|
93
|
-
impactAssessments: [getFixtureNode('impactAssessment', '3')]
|
|
94
|
-
}
|
|
95
|
-
];
|
|
96
|
-
const filter = 'practices=earthingUpByHand|deepRipping';
|
|
97
|
-
const result = await supertest(app).get('/pg').query({ filter }).set('Accept', 'application/json');
|
|
98
|
-
expect(result.body.results).to.deep.equal(expected);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('can filter by minimum aggregatedQualityScore', async () => {
|
|
102
|
-
const expected = [
|
|
103
|
-
{
|
|
104
|
-
cycle: getFixtureNode('cycle', '1'),
|
|
105
|
-
region: getFixtureNode('site', '1').country,
|
|
106
|
-
impactAssessments: [getFixtureNode('impactAssessment', '1'), getFixtureNode('impactAssessment', '2')]
|
|
107
|
-
}
|
|
108
|
-
];
|
|
109
|
-
const filter = 'minAggregatedQualityScore=3';
|
|
110
|
-
const result = await supertest(app).get('/pg').query({ filter }).set('Accept', 'application/json');
|
|
111
|
-
expect(result.body.results).to.deep.equal(expected);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('can filter by defaultMethodClassifications', async () => {
|
|
115
|
-
const expected = [
|
|
116
|
-
{
|
|
117
|
-
cycle: getFixtureNode('cycle', '2'),
|
|
118
|
-
region: getFixtureNode('site', '1').country,
|
|
119
|
-
impactAssessments: [getFixtureNode('impactAssessment', '3')]
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
cycle: getFixtureNode('cycle', '3'),
|
|
123
|
-
region: getFixtureNode('site', '2').country,
|
|
124
|
-
impactAssessments: [getFixtureNode('impactAssessment', '4')]
|
|
125
|
-
}
|
|
126
|
-
];
|
|
127
|
-
const filter = 'defaultMethodClassifications=physical measurement|expert opinion';
|
|
128
|
-
const result = await supertest(app).get('/pg').query({ filter }).set('Accept', 'application/json');
|
|
129
|
-
expect(result.body.results).to.deep.equal(expected);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('can filter by periods', async () => {
|
|
133
|
-
const expected = [
|
|
134
|
-
{
|
|
135
|
-
cycle: getFixtureNode('cycle', '3'),
|
|
136
|
-
region: getFixtureNode('site', '2').country,
|
|
137
|
-
impactAssessments: [getFixtureNode('impactAssessment', '4')]
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
cycle: getFixtureNode('cycle', '4'),
|
|
141
|
-
region: getFixtureNode('site', '3').country,
|
|
142
|
-
impactAssessments: []
|
|
143
|
-
}
|
|
144
|
-
];
|
|
145
|
-
const filter = 'periods=2010-2019|2000-2009';
|
|
146
|
-
const result = await supertest(app).get('/pg').query({ filter }).set('Accept', 'application/json');
|
|
147
|
-
expect(result.body.results).to.deep.equal(expected);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('can filter by products', async () => {
|
|
151
|
-
const expected = [
|
|
152
|
-
{
|
|
153
|
-
cycle: getFixtureNode('cycle', '1'),
|
|
154
|
-
region: getFixtureNode('site', '1').country,
|
|
155
|
-
// impactAssessments have also been filtered
|
|
156
|
-
impactAssessments: [getFixtureNode('impactAssessment', '1')]
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
cycle: getFixtureNode('cycle', '2'),
|
|
160
|
-
region: getFixtureNode('site', '1').country,
|
|
161
|
-
impactAssessments: [getFixtureNode('impactAssessment', '3')]
|
|
162
|
-
}
|
|
163
|
-
];
|
|
164
|
-
const filter = 'products=wheatGrain|fishFingerlings';
|
|
165
|
-
const result = await supertest(app).get('/pg').query({ filter }).set('Accept', 'application/json');
|
|
166
|
-
expect(result.body.results).to.deep.equal(expected);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('can combine multiple filters', async () => {
|
|
170
|
-
const expected = [
|
|
171
|
-
{
|
|
172
|
-
cycle: getFixtureNode('cycle', '5'),
|
|
173
|
-
region: getFixtureNode('site', '3').country,
|
|
174
|
-
// impactAssessments have also been filtered
|
|
175
|
-
impactAssessments: []
|
|
176
|
-
}
|
|
177
|
-
];
|
|
178
|
-
const filter = [
|
|
179
|
-
'regions=GADM-POL|GADM-DEU|GADM-BRA',
|
|
180
|
-
'products=wheatGrain|fishFingerlings|cheese',
|
|
181
|
-
'periods=2010-2019|2020-2029',
|
|
182
|
-
'defaultMethodClassifications=modelled|expert opinion',
|
|
183
|
-
'minAggregatedQualityScore=2',
|
|
184
|
-
'practices=earthingUpByHand|deepRipping|baggingFruit'
|
|
185
|
-
].join(';');
|
|
186
|
-
const result = await supertest(app).get('/pg').query({ filter }).set('Accept', 'application/json');
|
|
187
|
-
expect(result.body.results).to.deep.equal(expected);
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
describe('GET /pg/filters', () => {
|
|
192
|
-
it('when invalid filter passed returns invalid param error', async () => {
|
|
193
|
-
const result = await supertest(app)
|
|
194
|
-
.get('/pg/filters')
|
|
195
|
-
.query({ filter: 'invalidfilter' })
|
|
196
|
-
.set('Accept', 'application/json');
|
|
197
|
-
|
|
198
|
-
expect(result.body.message).to.equal(Errors.InvalidQueryParam);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
describe('returns the filters and node counts available for the current selection', () => {
|
|
202
|
-
it('returns subset of filters corresponding to selected nodes', async () => {
|
|
203
|
-
const expected = {
|
|
204
|
-
cycleCount: 1,
|
|
205
|
-
impactAssessmentCount: 0,
|
|
206
|
-
products: ['apples', 'cheese'],
|
|
207
|
-
periods: ['2020-2029'],
|
|
208
|
-
practices: ['baggingFruit'],
|
|
209
|
-
minAggregatedQualityScore: [2],
|
|
210
|
-
regions: ['GADM-BRA'],
|
|
211
|
-
defaultMethodClassifications: ['modelled']
|
|
212
|
-
};
|
|
213
|
-
const filter = [
|
|
214
|
-
'regions=GADM-POL|GADM-DEU|GADM-BRA',
|
|
215
|
-
'products=wheatGrain|fishFingerlings|cheese',
|
|
216
|
-
'periods=2010-2019|2020-2029',
|
|
217
|
-
'defaultMethodClassifications=modelled|expert opinion',
|
|
218
|
-
'minAggregatedQualityScore=2',
|
|
219
|
-
'practices=earthingUpByHand|deepRipping|baggingFruit'
|
|
220
|
-
].join(';');
|
|
221
|
-
const result = await supertest(app).get('/pg/filters').query({ filter }).set('Accept', 'application/json');
|
|
222
|
-
expect(result.body).to.deep.equal(expected);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
it('returns all filters when there is nothing currently selected', async () => {
|
|
226
|
-
const expected = {
|
|
227
|
-
cycleCount: 9,
|
|
228
|
-
impactAssessmentCount: 4,
|
|
229
|
-
products: ['apples', 'barley', 'cheese', 'fishFingerlings', 'wheatGrain'],
|
|
230
|
-
periods: ['1980-1989', '1990-1999', '2000-2009', '2010-2019', '2020-2029'],
|
|
231
|
-
practices: ['baggingFruit', 'deepRipping', 'earthingUpByHand'],
|
|
232
|
-
minAggregatedQualityScore: [1, 2, 3],
|
|
233
|
-
regions: ['GADM-BRA', 'GADM-DEU', 'GADM-POL'],
|
|
234
|
-
defaultMethodClassifications: ['expert opinion', 'modelled', 'physical measurement', 'unsourced assumption']
|
|
235
|
-
};
|
|
236
|
-
const result = await supertest(app).get('/pg/filters').query({}).set('Accept', 'application/json');
|
|
237
|
-
expect(result.body).to.deep.equal(expected);
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
it('does not exclude nodes with missing practices key', async () => {
|
|
241
|
-
const expected = {
|
|
242
|
-
cycleCount: 1,
|
|
243
|
-
impactAssessmentCount: 0,
|
|
244
|
-
products: ['apples', 'cheese'],
|
|
245
|
-
periods: ['1980-1989'],
|
|
246
|
-
practices: [],
|
|
247
|
-
minAggregatedQualityScore: [2],
|
|
248
|
-
regions: ['GADM-BRA'],
|
|
249
|
-
defaultMethodClassifications: ['modelled']
|
|
250
|
-
};
|
|
251
|
-
const filter = ['regions=GADM-BRA', 'periods=1980-1989'].join(';');
|
|
252
|
-
const result = await supertest(app).get('/pg/filters').query({ filter }).set('Accept', 'application/json');
|
|
253
|
-
expect(result.body).to.deep.equal(expected);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it('returns empty arrays when no filters available', async () => {
|
|
257
|
-
const expected = {
|
|
258
|
-
cycleCount: 0,
|
|
259
|
-
impactAssessmentCount: 0,
|
|
260
|
-
products: [],
|
|
261
|
-
periods: [],
|
|
262
|
-
practices: [],
|
|
263
|
-
minAggregatedQualityScore: [],
|
|
264
|
-
regions: [],
|
|
265
|
-
defaultMethodClassifications: []
|
|
266
|
-
};
|
|
267
|
-
const filter = ['products=grapes'].join(';');
|
|
268
|
-
const result = await supertest(app).get('/pg/filters').query({ filter }).set('Accept', 'application/json');
|
|
269
|
-
expect(result.body).to.deep.equal(expected);
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
});
|
|
273
|
-
});
|
|
274
|
-
});
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { Request, Response, NextFunction, Router } from 'express';
|
|
2
|
-
import { endpointWrapper } from '../utils/endpoint-wrapper';
|
|
3
|
-
import { get as pgGet } from './routes/pg-get';
|
|
4
|
-
import { HestiaError, Errors } from '../errors';
|
|
5
|
-
import { validFilterFields } from './model';
|
|
6
|
-
import { getFilters as pgGetFilters } from './routes/pg-get-filters';
|
|
7
|
-
import { IFilter } from './model';
|
|
8
|
-
|
|
9
|
-
const isValidFilterString = (filter: string) =>
|
|
10
|
-
filter.split(';').every((field) => {
|
|
11
|
-
const [key] = field.split('=');
|
|
12
|
-
return validFilterFields.includes(key);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
const validateGet = (req: Request, _res: Response, next: NextFunction) => {
|
|
16
|
-
const missingParam = !req.query.filter && !req.query.query;
|
|
17
|
-
const invalidParam = req.query.filter && !isValidFilterString(req.query.filter as string);
|
|
18
|
-
return missingParam
|
|
19
|
-
? next(new HestiaError(Errors.MissingQueryParam))
|
|
20
|
-
: invalidParam
|
|
21
|
-
? next(new HestiaError(Errors.InvalidQueryParam))
|
|
22
|
-
: next();
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const validateGetFilters = (req: Request, _res: Response, next: NextFunction) => {
|
|
26
|
-
const invalidParam = req.query.filter && !isValidFilterString(req.query.filter as string);
|
|
27
|
-
return invalidParam ? next(new HestiaError(Errors.InvalidQueryParam)) : next();
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const parseArrayField = (val: string) => val.split('|');
|
|
31
|
-
|
|
32
|
-
const filterFieldParsers: Record<keyof IFilter, (val: string) => any> = {
|
|
33
|
-
regions: parseArrayField,
|
|
34
|
-
periods: parseArrayField,
|
|
35
|
-
products: parseArrayField,
|
|
36
|
-
defaultMethodClassifications: parseArrayField,
|
|
37
|
-
minAggregatedQualityScore: (val: string) => Number(val),
|
|
38
|
-
practices: parseArrayField
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const parseFilterString = (req: Request, _res: Response, next: NextFunction) => {
|
|
42
|
-
req.filter = ((req.query.filter || '') as string).split(';').reduce((acc, entry) => {
|
|
43
|
-
const [key, val] = entry.split('=');
|
|
44
|
-
return filterFieldParsers[key] ? { ...acc, [key]: filterFieldParsers[key](val) } : acc;
|
|
45
|
-
}, {});
|
|
46
|
-
next();
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export default () => {
|
|
50
|
-
const router = Router();
|
|
51
|
-
|
|
52
|
-
router.get('/pg/filters', validateGetFilters, parseFilterString, endpointWrapper(pgGetFilters));
|
|
53
|
-
router.get('/pg', validateGet, parseFilterString, endpointWrapper(pgGet));
|
|
54
|
-
|
|
55
|
-
return router;
|
|
56
|
-
};
|