@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.
Files changed (102) hide show
  1. package/package.json +3 -2
  2. package/.dockerignore +0 -25
  3. package/.env.test +0 -7
  4. package/.eslintignore +0 -7
  5. package/.eslintrc.js +0 -11
  6. package/.gitlab-ci.yml +0 -125
  7. package/.mocharc.js +0 -8
  8. package/.nvrm +0 -1
  9. package/.nycrc +0 -15
  10. package/Dockerfile +0 -17
  11. package/cleanup-docker.sh +0 -4
  12. package/commitlint.config.js +0 -1
  13. package/database/index.ts +0 -76
  14. package/database/migrations/001.do.init.sql +0 -53
  15. package/database/migrations/002.do.add-aggregated-sites.sql +0 -16
  16. package/database/migrations/003.do.add-generated-period-cols.sql +0 -7
  17. package/database/migrations/index.ts +0 -36
  18. package/database/seed/common.ts +0 -7
  19. package/database/seed/index.ts +0 -55
  20. package/database/seed/local/index.ts +0 -28
  21. package/database/seed/production/index.ts +0 -3
  22. package/database/seed/staging/index.ts +0 -5
  23. package/database/seed/test/index.ts +0 -28
  24. package/dev.ts +0 -3
  25. package/dist/aggregated-nodes/model/index.js +0 -11
  26. package/docker-compose.yml +0 -42
  27. package/envs/.master.env +0 -7
  28. package/envs/.staging.env +0 -7
  29. package/index.js +0 -3
  30. package/run-docker.sh +0 -14
  31. package/run-test.sh +0 -5
  32. package/scripts/run-lambda.ts +0 -10
  33. package/scripts/run-migrations.ts +0 -18
  34. package/scripts/run-resetdb.ts +0 -18
  35. package/scripts/run-seed.ts +0 -18
  36. package/serverless.yml +0 -76
  37. package/src/aggregated-nodes/model/index.ts +0 -37
  38. package/src/aggregated-nodes/routes/pg-get-filters.ts +0 -44
  39. package/src/aggregated-nodes/routes/pg-get.ts +0 -50
  40. package/src/aggregated-nodes/routes.spec.ts +0 -242
  41. package/src/aggregated-nodes/routes.ts +0 -56
  42. package/src/aggregated-nodes/services/pg-get-filters.ts +0 -52
  43. package/src/aggregated-nodes/services/pg-get.ts +0 -77
  44. package/src/app.spec.ts +0 -34
  45. package/src/app.ts +0 -59
  46. package/src/config.ts +0 -21
  47. package/src/cors.spec.ts +0 -32
  48. package/src/cors.ts +0 -7
  49. package/src/errors.spec.ts +0 -114
  50. package/src/errors.ts +0 -121
  51. package/src/index.spec.ts +0 -94
  52. package/src/index.ts +0 -14
  53. package/src/lambdas/sentry.ts +0 -12
  54. package/src/lambdas/update-aggregated-nodes/handler.spec.ts +0 -86
  55. package/src/lambdas/update-aggregated-nodes/handler.ts +0 -141
  56. package/src/logger.spec.ts +0 -20
  57. package/src/logger.ts +0 -45
  58. package/src/maintenance.spec.ts +0 -76
  59. package/src/maintenance.ts +0 -19
  60. package/src/models.ts +0 -1
  61. package/src/routes.ts +0 -8
  62. package/src/settings/model/index.ts +0 -21
  63. package/src/settings/routes/get.spec.ts +0 -33
  64. package/src/settings/routes/get.ts +0 -3
  65. package/src/settings/routes/update.spec.ts +0 -33
  66. package/src/settings/routes/update.ts +0 -5
  67. package/src/settings/routes.spec.ts +0 -75
  68. package/src/settings/routes.ts +0 -21
  69. package/src/settings/services/get.spec.ts +0 -62
  70. package/src/settings/services/get.ts +0 -18
  71. package/src/settings/services/update.spec.ts +0 -118
  72. package/src/settings/services/update.ts +0 -47
  73. package/src/slack.spec.ts +0 -42
  74. package/src/slack.ts +0 -17
  75. package/src/swagger/routes.ts +0 -57
  76. package/src/types/async-express-errors/index.d.ts +0 -1
  77. package/src/types/express/index.d.ts +0 -10
  78. package/src/utils/endpoint-wrapper.spec.ts +0 -80
  79. package/src/utils/endpoint-wrapper.ts +0 -16
  80. package/src/utils/middleware.spec.ts +0 -154
  81. package/src/utils/middleware.ts +0 -33
  82. package/test/Dockerfile +0 -13
  83. package/test/docker-compose.yml +0 -40
  84. package/test/fixtures/aggregated-nodes/get.ts +0 -184
  85. package/test/fixtures/update-aggregated-nodes/abyssinianKaleSeedWhole-cycle_pivoted.csv +0 -5
  86. package/test/fixtures/update-aggregated-nodes/abyssinianKaleSeedWhole-cycle_pivoted.csv.cycle.json +0 -458
  87. package/test/fixtures/update-aggregated-nodes/abyssinianKaleSeedWhole-cycle_pivoted.csv.site.json +0 -182
  88. package/test/fixtures/update-aggregated-nodes/abyssinianKaleSeedWhole-impactassessment_pivoted.csv +0 -3
  89. package/test/fixtures/update-aggregated-nodes/abyssinianKaleSeedWhole-impactassessment_pivoted.csv.impactAssessment.json +0 -988
  90. package/test/fixtures/update-aggregated-nodes/abyssinianKaleStraw-impactassessment_pivoted.csv +0 -3
  91. package/test/fixtures/update-aggregated-nodes/cycle-missing-impactassessment_pivoted.csv +0 -3
  92. package/test/fixtures/update-aggregated-nodes/tomatoFruit-cycle_pivoted.csv +0 -5
  93. package/test/fixtures/update-aggregated-nodes/tomatoFruit-cycle_pivoted.csv.cycle.json +0 -584
  94. package/test/fixtures/update-aggregated-nodes/tomatoFruit-cycle_pivoted.csv.site.json +0 -212
  95. package/test/fixtures/update-aggregated-nodes/tomatoFruit-impactassessment_pivoted.csv +0 -3
  96. package/test/fixtures/update-aggregated-nodes/tomatoFruit-impactassessment_pivoted.csv.impactAssessment.json +0 -1002
  97. package/test/prepare.ts +0 -13
  98. package/test/utils.ts +0 -33
  99. package/tsconfig.build.json +0 -13
  100. package/tsconfig.dist.json +0 -14
  101. package/tsconfig.json +0 -42
  102. package/tsconfig.lambdas.json +0 -13
package/run-docker.sh DELETED
@@ -1,14 +0,0 @@
1
- #!/bin/sh
2
-
3
- docker build --progress=plain \
4
- -t hestia-data-api:latest \
5
- .
6
-
7
- docker run --rm \
8
- --name hestia-data-api \
9
- --env-file .env \
10
- --env PORT=80 \
11
- -v ${PWD}/scripts:/app/scripts \
12
- -v ${PWD}/src:/app/src \
13
- -p 3001:80 \
14
- hestia-data-api:latest npm run dev
package/run-test.sh DELETED
@@ -1,5 +0,0 @@
1
- #!/bin/sh
2
-
3
- docker-compose -f test/docker-compose.yml build
4
- docker-compose -f test/docker-compose.yml run test
5
- docker-compose -f test/docker-compose.yml down
@@ -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
- })();
@@ -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
- })();
@@ -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,76 +0,0 @@
1
- frameworkVersion: '3'
2
- service: hestia-data-api
3
-
4
- plugins:
5
- - serverless-deployment-bucket
6
- - serverless-offline
7
-
8
- custom:
9
- serverless-offline:
10
- httpPort: 3002
11
- stage: ${env:STAGE, self:provider.stage}
12
- securityGroup:
13
- staging: sg-0800627978a40527b
14
- prod: sg-049204c3b58a8bd08
15
- subnetId1:
16
- # private 1a
17
- staging: subnet-04ad6e355249c757d
18
- prod: subnet-00c4a891bce6f48e0
19
- subnetId2:
20
- # private 1b
21
- staging: subnet-01260544aaa3c6810
22
- prod: subnet-02365f46c569f5e1a
23
- debugging:
24
- staging: true
25
- prod: false
26
- logLevel:
27
- staging: DEBUG
28
- prod: INFO
29
- bucketData: hestia-data-${self:custom.stage}
30
- domain:
31
- staging: https://www-staging.hestia.earth
32
- prod: https://www.hestia.earth
33
-
34
- provider:
35
- name: aws
36
- runtime: nodejs18.x
37
- region: us-east-1
38
- stage: staging
39
- deploymentBucket:
40
- name: hestia-serverless-${self:custom.stage}
41
- memorySize: 512
42
- timeout: 600
43
- environment:
44
- SERVICE_NAME: ${self:service}
45
- STAGE: ${self:custom.stage}
46
- DEBUG: ${self:custom.debugging.${self:custom.stage}}
47
- LOG_LEVEL: ${self:custom.logLevel.${self:custom.stage}}
48
- SENTRY_DSN: https://00ed710e2b664894a3ff3124bfff24cf@o441427.ingest.0.1.0.io/4505186792767488
49
- BUCKET_DATA: ${self:custom.bucketData}
50
- PGHOST: ${env:PGHOST}
51
- PGDATABASE: ${env:PGDATABASE}
52
- PGPORT: ${env:PGPORT}
53
- PGUSER: ${env:PGUSER}
54
- PGPASSWORD: ${env:PGPASSWORD}
55
- iamRoleStatements:
56
- - Effect: Allow
57
- Action:
58
- - s3:GetObject
59
- - s3:GetObjectAcl
60
- - s3:ListObjects
61
- - s3:ListBucket
62
- Resource:
63
- - "arn:aws:s3:::${self:custom.bucketData}"
64
- - "arn:aws:s3:::${self:custom.bucketData}/*"
65
- vpc:
66
- securityGroupIds:
67
- - ${self:custom.securityGroup.${self:custom.stage}}
68
- subnetIds:
69
- - ${self:custom.subnetId1.${self:custom.stage}}
70
- - ${self:custom.subnetId2.${self:custom.stage}}
71
-
72
- functions:
73
- updateAggregatedNodes:
74
- handler: build/src/lambdas/update-aggregated-nodes/handler.updateAggregatedNodes
75
- events:
76
- - 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,44 +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
- * example: "regions=Africa|Albania;products=abyssinianKaleStraw|abyssinianKaleSeedWhole;periods=1970-1973"
18
- * description: Set of filters
19
- * - in: query
20
- * name: query
21
- * schema:
22
- * type: string
23
- * example: Wheat
24
- * description: Free search text
25
- * responses:
26
- * 200:
27
- * content:
28
- * application/json:
29
- * schema:
30
- * type: object
31
- * 400:
32
- * $ref: '#/components/responses/BadRequestError'
33
- * 404:
34
- * $ref: '#/components/responses/NotFoundError'
35
- * 500:
36
- * $ref: '#/components/responses/UnexpectedError'
37
- * 503:
38
- * $ref: '#/components/responses/ServiceUnavailable'
39
- */
40
-
41
- export const getFilters = ({ query: qs, filter }: Request) => {
42
- const query = qs.query as string;
43
- return applyFilters({ filter, query });
44
- };
@@ -1,50 +0,0 @@
1
- import { Request } from 'express';
2
-
3
- import { applyFilter } from '../services/pg-get';
4
-
5
- /**
6
- * @swagger
7
- *
8
- * paths:
9
- * /aggregated-nodes/pg:
10
- * get:
11
- * summary: "Get the aggregated nodes corresponding to a set of filters and search query (uses PG only)"
12
- * parameters:
13
- * - in: query
14
- * name: filter
15
- * schema:
16
- * type: string
17
- * example: "regions=Africa|Albania;products=abyssinianKaleStraw|abyssinianKaleSeedWhole;periods=1970-1973"
18
- * description: Set of filters
19
- * - in: query
20
- * name: page
21
- * schema:
22
- * type: integer
23
- * example: 1
24
- * - in: query
25
- * name: query
26
- * schema:
27
- * type: string
28
- * example: Wheat
29
- * description: Free search text
30
- * responses:
31
- * 200:
32
- * content:
33
- * application/json:
34
- * schema:
35
- * type: object
36
- * 400:
37
- * $ref: '#/components/responses/BadRequestError'
38
- * 404:
39
- * $ref: '#/components/responses/NotFoundError'
40
- * 500:
41
- * $ref: '#/components/responses/UnexpectedError'
42
- * 503:
43
- * $ref: '#/components/responses/ServiceUnavailable'
44
- */
45
-
46
- export const get = ({ query: qs, filter }: Request) => {
47
- const page = Number(qs.page) || 1;
48
- const query = qs.query as string;
49
- return applyFilter({ page, filter, query });
50
- };
@@ -1,242 +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.rows).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.rows).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.rows).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.rows).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.rows).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.rows).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.rows).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: 8,
228
- impactAssessmentCount: 4,
229
- products: ['apples', 'barley', 'cheese', 'fishFingerlings', 'wheatGrain'],
230
- periods: ['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
- });
241
- });
242
- });
@@ -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
- };
@@ -1,52 +0,0 @@
1
- import { getFilterClauses } from './pg-get';
2
- import { query as doQuery } from '../../../database';
3
- import { IFilter, IFilters } from '../model';
4
-
5
- const getFiltersQuery = (filter: IFilter): [string, any[]] => {
6
- const { filterClauses, params } = getFilterClauses(filter);
7
- return [
8
- `
9
- SELECT
10
- COUNT(DISTINCT c) as "cycleCount",
11
- COUNT(DISTINCT ia) AS "impactAssessmentCount",
12
- ARRAY_AGG(DISTINCT product ORDER BY product) as products,
13
- ARRAY_AGG(DISTINCT c.generated_period ORDER BY c.generated_period) as periods,
14
- ARRAY_AGG(DISTINCT practice ORDER BY practice) as practices,
15
- ARRAY_AGG(
16
- DISTINCT (c.jsonld_pivoted->>'aggregatedQualityScore')::integer
17
- ORDER BY (c.jsonld_pivoted->>'aggregatedQualityScore')::integer
18
- ) AS "minAggregatedQualityScore",
19
- ARRAY_AGG(
20
- DISTINCT s.jsonld_pivoted['country']->>'@id'
21
- ORDER BY s.jsonld_pivoted['country']->>'@id'
22
- ) AS regions,
23
- ARRAY_AGG(
24
- DISTINCT c.jsonld_pivoted->>'defaultMethodClassification'
25
- ORDER BY c.jsonld_pivoted->>'defaultMethodClassification'
26
- ) AS "defaultMethodClassifications"
27
- FROM aggregated_cycles AS c
28
- LEFT JOIN aggregated_impact_assessments AS ia
29
- ON c.hestia_id = ia.cycle_hestia_id
30
- AND ${filterClauses.products.iaFilter}
31
- JOIN aggregated_sites AS s
32
- ON c.site_hestia_id = s.hestia_id
33
- AND ${filterClauses.regions}
34
- CROSS JOIN LATERAL
35
- jsonb_object_keys(c.jsonld_pivoted['products']) as products(product),
36
- jsonb_object_keys(c.jsonld_pivoted['practices']) as practices(practice)
37
- WHERE ${filterClauses.practices}
38
- AND ${filterClauses.defaultMethodClassifications}
39
- AND ${filterClauses.products.cycleFilter}
40
- AND ${filterClauses.minAggregatedQualityScore}
41
- AND ${filterClauses.periods}
42
- `,
43
- params
44
- ];
45
- };
46
-
47
- export const applyFilters = async ({ filter }: { filter: IFilter; query?: string }) => {
48
- // TODO: handle query later
49
- const [pgQuery, params] = getFiltersQuery(filter);
50
- const res = await doQuery<IFilters>(pgQuery, params);
51
- return res?.rows[0];
52
- };