@opentermsarchive/engine 0.29.1 → 0.30.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opentermsarchive/engine",
3
- "version": "0.29.1",
3
+ "version": "0.30.1",
4
4
  "description": "Tracks and makes visible changes to the terms of online services",
5
5
  "homepage": "https://github.com/OpenTermsArchive/engine#readme",
6
6
  "bugs": {
@@ -93,6 +93,7 @@
93
93
  "sib-api-v3-sdk": "^8.2.1",
94
94
  "simple-git": "^3.8.0",
95
95
  "swagger-jsdoc": "^6.2.8",
96
+ "swagger-ui-express": "4.6.2",
96
97
  "winston": "^3.3.3",
97
98
  "winston-mail": "^2.0.0"
98
99
  },
@@ -78,6 +78,10 @@ export default async options => {
78
78
  if (!schemaOnly && service) {
79
79
  service.getTermsTypes()
80
80
  .filter(termsType => {
81
+ if (!service.terms[termsType]?.latest) { // If this terms type has been deleted and there is only a historical record for it, but no current valid declaration
82
+ return false;
83
+ }
84
+
81
85
  if (servicesTermsTypes[serviceId] && servicesTermsTypes[serviceId].length > 0) {
82
86
  return servicesTermsTypes[serviceId].includes(termsType);
83
87
  }
@@ -0,0 +1,38 @@
1
+ import path from 'path';
2
+ import { fileURLToPath } from 'url';
3
+
4
+ import express from 'express';
5
+ import swaggerJsdoc from 'swagger-jsdoc';
6
+ import swaggerUi from 'swagger-ui-express';
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+
10
+ export default function specsRouter(basePath) {
11
+ const router = express.Router();
12
+ const specs = swaggerJsdoc({
13
+ definition: {
14
+ openapi: '3.1.0',
15
+ info: {
16
+ title: 'Open Terms Archive API',
17
+ version: '1.0.0',
18
+ license: {
19
+ name: 'EUPL-1.2',
20
+ url: 'https://eupl.eu/1.2/',
21
+ },
22
+ },
23
+ servers: [{ url: basePath }],
24
+ },
25
+ apis: [`${__dirname}/*.js`],
26
+ });
27
+
28
+ router.use('/docs', swaggerUi.serve);
29
+ router.get('/docs', (req, res) => {
30
+ if (req.get('Accept')?.match('json')) { // send OpenAPI spec to machines, Swagger UI to humans
31
+ return res.json(specs);
32
+ }
33
+
34
+ return swaggerUi.setup(specs)(req, res);
35
+ });
36
+
37
+ return router;
38
+ }
@@ -0,0 +1,72 @@
1
+ import { expect } from 'chai';
2
+ import config from 'config';
3
+ import request from 'supertest';
4
+
5
+ import app from '../server.js';
6
+
7
+ const basePath = config.get('api.basePath');
8
+
9
+ describe('Docs API', () => {
10
+ describe('GET /docs', () => {
11
+ let response;
12
+
13
+ context('When requested as JSON', () => {
14
+ before(async () => {
15
+ response = await request(app).get(`${basePath}/v1/docs/`).set('Accept', 'application/json');
16
+ });
17
+
18
+ it('responds with 200 status code', () => {
19
+ expect(response.status).to.equal(200);
20
+ });
21
+
22
+ it('responds with Content-Type application/json', () => {
23
+ expect(response.type).to.equal('application/json');
24
+ });
25
+
26
+ describe('body response defines', () => {
27
+ let subject;
28
+
29
+ before(async () => {
30
+ subject = response.body;
31
+ });
32
+
33
+ it('OpenAPI version', () => {
34
+ expect(subject).to.have.property('openapi');
35
+ });
36
+
37
+ it('paths', () => {
38
+ expect(subject).to.have.property('paths');
39
+ });
40
+
41
+ describe('with endpoints', () => {
42
+ before(async () => {
43
+ subject = response.body.paths;
44
+ });
45
+
46
+ it('/services', () => {
47
+ expect(subject).to.have.property('/services');
48
+ });
49
+
50
+ it('/service/{serviceId}', () => {
51
+ expect(subject).to.have.property('/service/{serviceId}');
52
+ });
53
+ });
54
+ });
55
+ });
56
+
57
+ context('When requested as HTML', () => {
58
+ before(async () => {
59
+ response = await request(app).get(`${basePath}/v1/docs/`);
60
+ console.log(response);
61
+ });
62
+
63
+ it('responds with 200 status code', () => {
64
+ expect(response.status).to.equal(200);
65
+ });
66
+
67
+ it('responds with Content-Type text/html', () => {
68
+ expect(response.type).to.equal('text/html');
69
+ });
70
+ });
71
+ });
72
+ });
@@ -1,11 +1,32 @@
1
1
  import express from 'express';
2
+ import helmet from 'helmet';
2
3
 
4
+ import docsRouter from './docs.js';
3
5
  import servicesRouter from './services.js';
4
- import specsRouter from './specs.js';
5
6
 
6
- const apiRouter = express.Router();
7
+ export default function apiRouter(basePath) {
8
+ const router = express.Router();
7
9
 
8
- apiRouter.use('/specs', specsRouter);
9
- apiRouter.use('/services', servicesRouter);
10
+ const defaultDirectives = helmet.contentSecurityPolicy.getDefaultDirectives();
10
11
 
11
- export default apiRouter;
12
+ delete defaultDirectives['upgrade-insecure-requests'];
13
+
14
+ router.use(helmet({
15
+ contentSecurityPolicy: {
16
+ useDefaults: false,
17
+ directives: defaultDirectives,
18
+ },
19
+ })); // Do not enable `upgrade-insecure-requests` directive set by Helmet for docs routes to ensure insecure requests won't be upgraded to secure requests for swaggerUI assets; see https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/96#issuecomment-924193910 and https://github.com/scottie1984/swagger-ui-express/issues/212#issuecomment-825803088
20
+
21
+ router.use(docsRouter(basePath));
22
+
23
+ router.use(helmet()); // then, enable all `helmet` HTTP response headers for all others routes
24
+
25
+ router.get('/', (req, res) => {
26
+ res.json({ message: 'Welcome to an instance of the Open Terms Archive API. Documentation is available at /docs. Learn more on Open Terms Archive on https://opentermsarchive.org.' });
27
+ });
28
+
29
+ router.use(servicesRouter);
30
+
31
+ return router;
32
+ }
@@ -74,7 +74,7 @@ const router = express.Router();
74
74
  * items:
75
75
  * $ref: '#/components/schemas/Service'
76
76
  */
77
- router.get('/', (req, res) => {
77
+ router.get('/services', (req, res) => {
78
78
  res.status(200).json(Object.values(services).map(service => ({
79
79
  id: service.id,
80
80
  name: service.name,
@@ -113,7 +113,7 @@ router.get('/', (req, res) => {
113
113
  * 404:
114
114
  * description: No service matching the provided ID is found.
115
115
  */
116
- router.get('/:serviceId', (req, res) => {
116
+ router.get('/service/:serviceId', (req, res) => {
117
117
  const matchedServiceID = Object.keys(services).find(key => key.toLowerCase() === req.params.serviceId?.toLowerCase());
118
118
  const service = services[matchedServiceID];
119
119
 
@@ -39,13 +39,13 @@ describe('Services API', () => {
39
39
  });
40
40
  });
41
41
 
42
- describe('GET /services/:serviceId', () => {
42
+ describe('GET /service/:serviceId', () => {
43
43
  let response;
44
44
  const SERVICE_ID = 'Service B!';
45
45
  const CASE_INSENSITIVE_SERVICE_ID = 'service b!';
46
46
 
47
47
  before(async () => {
48
- response = await request(app).get(`${basePath}/v1/services/${encodeURI(SERVICE_ID)}`);
48
+ response = await request(app).get(`${basePath}/v1/service/${encodeURI(SERVICE_ID)}`);
49
49
  });
50
50
 
51
51
  it('responds with 200 status code', () => {
@@ -94,7 +94,7 @@ describe('Services API', () => {
94
94
 
95
95
  context('With a case-insensitive service ID parameter', () => {
96
96
  before(async () => {
97
- response = await request(app).get(`${basePath}/v1/services/${encodeURI(CASE_INSENSITIVE_SERVICE_ID)}`);
97
+ response = await request(app).get(`${basePath}/v1/service/${encodeURI(CASE_INSENSITIVE_SERVICE_ID)}`);
98
98
  });
99
99
 
100
100
  it('responds with 200 status code', () => {
@@ -140,7 +140,7 @@ describe('Services API', () => {
140
140
 
141
141
  context('When no matching service is found', () => {
142
142
  before(async () => {
143
- response = await request(app).get(`${basePath}/v1/nonExistentService`);
143
+ response = await request(app).get(`${basePath}/v1/service/nonExistentService`);
144
144
  });
145
145
 
146
146
  it('responds with 404 status code', () => {
package/src/api/server.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import config from 'config';
2
2
  import express from 'express';
3
- import helmet from 'helmet';
4
3
 
5
4
  import logger from './logger.js';
6
5
  import errorsMiddleware from './middlewares/errors.js';
@@ -9,13 +8,13 @@ import apiRouter from './routes/index.js';
9
8
 
10
9
  const app = express();
11
10
 
12
- app.use(helmet());
13
-
14
11
  if (process.env.NODE_ENV !== 'test') {
15
12
  app.use(loggerMiddleware);
16
13
  }
17
14
 
18
- app.use(`${config.get('api.basePath')}/v1`, apiRouter);
15
+ const basePath = `${config.get('api.basePath')}/v1`;
16
+
17
+ app.use(basePath, apiRouter(basePath));
19
18
  app.use(errorsMiddleware);
20
19
 
21
20
  app.listen(config.get('api.port'));
@@ -1,28 +0,0 @@
1
- import path from 'path';
2
- import { fileURLToPath } from 'url';
3
-
4
- import express from 'express';
5
- import swaggerJsdoc from 'swagger-jsdoc';
6
-
7
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
- const router = express.Router();
9
-
10
- router.get('/', (req, res) => {
11
- res.json(swaggerJsdoc({
12
- definition: {
13
- swagger: '2.0',
14
- openapi: '3.1.0',
15
- info: {
16
- title: 'Open Terms Archive API',
17
- version: '1.0.0',
18
- license: {
19
- name: 'EUPL-1.2',
20
- url: 'https://eupl.eu/1.2/',
21
- },
22
- },
23
- },
24
- apis: [`${__dirname}/*.js`],
25
- }));
26
- });
27
-
28
- export default router;
@@ -1,59 +0,0 @@
1
- import { expect } from 'chai';
2
- import config from 'config';
3
- import request from 'supertest';
4
-
5
- import app from '../server.js';
6
-
7
- const basePath = config.get('api.basePath');
8
-
9
- describe('Specs API', () => {
10
- describe('GET /specs', () => {
11
- let response;
12
-
13
- before(async () => {
14
- response = await request(app).get(`${basePath}/v1/specs`);
15
- });
16
-
17
- it('responds with 200 status code', () => {
18
- expect(response.status).to.equal(200);
19
- });
20
-
21
- it('responds with Content-Type application/json', () => {
22
- expect(response.type).to.equal('application/json');
23
- });
24
-
25
- describe('body response defines', () => {
26
- let subject;
27
-
28
- before(async () => {
29
- subject = response.body;
30
- });
31
-
32
- it('openapi version', () => {
33
- expect(subject).to.have.property('openapi');
34
- });
35
-
36
- it('swagger version', () => {
37
- expect(subject).to.have.property('swagger');
38
- });
39
-
40
- it('paths', () => {
41
- expect(subject).to.have.property('paths');
42
- });
43
-
44
- describe('with endpoints', () => {
45
- before(async () => {
46
- subject = response.body.paths;
47
- });
48
-
49
- it('/services', () => {
50
- expect(subject).to.have.property('/services');
51
- });
52
-
53
- it('/service/{serviceId}', () => {
54
- expect(subject).to.have.property('/service/{serviceId}');
55
- });
56
- });
57
- });
58
- });
59
- });