@opentermsarchive/engine 14.1.0 → 15.0.0
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/src/archivist/recorder/repositories/git/dataMapper.js +4 -0
- package/src/archivist/recorder/repositories/git/git.js +90 -8
- package/src/archivist/recorder/repositories/git/index.js +91 -4
- package/src/archivist/recorder/repositories/git/index.test.js +288 -0
- package/src/archivist/recorder/repositories/interface.js +34 -0
- package/src/archivist/recorder/repositories/mongo/index.js +53 -0
- package/src/archivist/recorder/repositories/mongo/index.test.js +171 -0
- package/src/collection-api/middlewares/errors.js +1 -1
- package/src/collection-api/routes/index.js +2 -1
- package/src/collection-api/routes/services.js +34 -21
- package/src/collection-api/routes/versions.js +521 -27
- package/src/collection-api/routes/versions.test.js +655 -25
|
@@ -1,35 +1,545 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
|
|
3
3
|
import { toISODateWithoutMilliseconds } from '../../archivist/utils/date.js';
|
|
4
|
+
import logger from '../logger.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
* @param {object} versionsRepository
|
|
7
|
-
* @
|
|
7
|
+
* @param {object} versionsRepository The versions repository instance
|
|
8
|
+
* @param {object} snapshotsRepository The snapshots repository instance
|
|
9
|
+
* @returns {express.Router} The router instance
|
|
8
10
|
* @private
|
|
9
11
|
* @swagger
|
|
10
12
|
* tags:
|
|
11
13
|
* name: Versions
|
|
12
14
|
* description: Versions API
|
|
13
15
|
* components:
|
|
16
|
+
* parameters:
|
|
17
|
+
* LimitParam:
|
|
18
|
+
* in: query
|
|
19
|
+
* name: limit
|
|
20
|
+
* description: |
|
|
21
|
+
* The maximum number of versions to return.
|
|
22
|
+
*
|
|
23
|
+
* **Note for Git storage**: Pagination uses Git's `--skip` and `--max-count` options,
|
|
24
|
+
* which work in topological order rather than strictly chronological order.
|
|
25
|
+
* This means paginated results may not be in perfect chronological sequence,
|
|
26
|
+
* but this is an acceptable performance trade-off.
|
|
27
|
+
* schema:
|
|
28
|
+
* type: integer
|
|
29
|
+
* minimum: 1
|
|
30
|
+
* maximum: 500
|
|
31
|
+
* default: 100
|
|
32
|
+
* required: false
|
|
33
|
+
* OffsetParam:
|
|
34
|
+
* in: query
|
|
35
|
+
* name: offset
|
|
36
|
+
* description: |
|
|
37
|
+
* The number of versions to skip before returning results.
|
|
38
|
+
*
|
|
39
|
+
* **Note for Git storage**: Pagination uses Git's `--skip` and `--max-count` options,
|
|
40
|
+
* which work in topological order rather than strictly chronological order.
|
|
41
|
+
* schema:
|
|
42
|
+
* type: integer
|
|
43
|
+
* minimum: 0
|
|
44
|
+
* default: 0
|
|
45
|
+
* required: false
|
|
14
46
|
* schemas:
|
|
15
47
|
* Version:
|
|
16
48
|
* type: object
|
|
17
49
|
* description: Version content and metadata
|
|
18
50
|
* properties:
|
|
51
|
+
* id:
|
|
52
|
+
* type: string
|
|
53
|
+
* description: The ID of the version.
|
|
54
|
+
* serviceId:
|
|
55
|
+
* type: string
|
|
56
|
+
* description: The ID of the service.
|
|
57
|
+
* termsType:
|
|
58
|
+
* type: string
|
|
59
|
+
* description: The type of terms.
|
|
19
60
|
* fetchDate:
|
|
20
61
|
* type: string
|
|
21
62
|
* format: date-time
|
|
22
63
|
* description: The ISO 8601 datetime string when the version was recorded.
|
|
64
|
+
* isFirstRecord:
|
|
65
|
+
* type: boolean
|
|
66
|
+
* description: Whether this version is the first one recorded for this service and terms type.
|
|
67
|
+
* isTechnicalUpgrade:
|
|
68
|
+
* type: boolean
|
|
69
|
+
* description: Whether this version is a technical upgrade (a re-render of an existing snapshot) rather than a content change at the source.
|
|
70
|
+
* content:
|
|
71
|
+
* type: string
|
|
72
|
+
* description: The JSON-escaped Markdown content of the version
|
|
73
|
+
* VersionListItem:
|
|
74
|
+
* type: object
|
|
75
|
+
* properties:
|
|
23
76
|
* id:
|
|
24
77
|
* type: string
|
|
25
78
|
* description: The ID of the version.
|
|
26
|
-
*
|
|
79
|
+
* serviceId:
|
|
27
80
|
* type: string
|
|
28
|
-
* description: The
|
|
81
|
+
* description: The ID of the service.
|
|
82
|
+
* termsType:
|
|
83
|
+
* type: string
|
|
84
|
+
* description: The type of terms.
|
|
85
|
+
* fetchDate:
|
|
86
|
+
* type: string
|
|
87
|
+
* format: date-time
|
|
88
|
+
* description: The ISO 8601 datetime string when the version was recorded.
|
|
89
|
+
* isFirstRecord:
|
|
90
|
+
* type: boolean
|
|
91
|
+
* description: Whether this version is the first one recorded for this service and terms type.
|
|
92
|
+
* isTechnicalUpgrade:
|
|
93
|
+
* type: boolean
|
|
94
|
+
* description: Whether this version is a technical upgrade (a re-render of an existing snapshot) rather than a content change at the source.
|
|
95
|
+
* VersionListItemWithStats:
|
|
96
|
+
* allOf:
|
|
97
|
+
* - $ref: '#/components/schemas/VersionListItem'
|
|
98
|
+
* - type: object
|
|
99
|
+
* properties:
|
|
100
|
+
* additions:
|
|
101
|
+
* type: integer
|
|
102
|
+
* nullable: true
|
|
103
|
+
* description: The number of lines added in this version, or null if not available.
|
|
104
|
+
* deletions:
|
|
105
|
+
* type: integer
|
|
106
|
+
* nullable: true
|
|
107
|
+
* description: The number of lines deleted in this version, or null if not available.
|
|
108
|
+
* PaginatedVersionsResponse:
|
|
109
|
+
* type: object
|
|
110
|
+
* properties:
|
|
111
|
+
* data:
|
|
112
|
+
* type: array
|
|
113
|
+
* description: The list of versions.
|
|
114
|
+
* items:
|
|
115
|
+
* $ref: '#/components/schemas/VersionListItem'
|
|
116
|
+
* count:
|
|
117
|
+
* type: integer
|
|
118
|
+
* description: The total number of versions found.
|
|
119
|
+
* limit:
|
|
120
|
+
* type: integer
|
|
121
|
+
* description: The maximum number of versions returned in this response.
|
|
122
|
+
* offset:
|
|
123
|
+
* type: integer
|
|
124
|
+
* description: The number of versions skipped before returning results.
|
|
125
|
+
* PaginatedVersionsWithStatsResponse:
|
|
126
|
+
* type: object
|
|
127
|
+
* properties:
|
|
128
|
+
* data:
|
|
129
|
+
* type: array
|
|
130
|
+
* description: The list of versions with diff statistics.
|
|
131
|
+
* items:
|
|
132
|
+
* $ref: '#/components/schemas/VersionListItemWithStats'
|
|
133
|
+
* count:
|
|
134
|
+
* type: integer
|
|
135
|
+
* description: The total number of versions found.
|
|
136
|
+
* limit:
|
|
137
|
+
* type: integer
|
|
138
|
+
* description: The maximum number of versions returned in this response.
|
|
139
|
+
* offset:
|
|
140
|
+
* type: integer
|
|
141
|
+
* description: The number of versions skipped before returning results.
|
|
142
|
+
* VersionWithLinks:
|
|
143
|
+
* allOf:
|
|
144
|
+
* - $ref: '#/components/schemas/Version'
|
|
145
|
+
* - type: object
|
|
146
|
+
* properties:
|
|
147
|
+
* additions:
|
|
148
|
+
* type: integer
|
|
149
|
+
* nullable: true
|
|
150
|
+
* description: The number of lines added in this version, or null if not available.
|
|
151
|
+
* deletions:
|
|
152
|
+
* type: integer
|
|
153
|
+
* nullable: true
|
|
154
|
+
* description: The number of lines deleted in this version, or null if not available.
|
|
155
|
+
* fetchUrls:
|
|
156
|
+
* type: array
|
|
157
|
+
* description: The URLs of the source documents that were fetched to produce this version.
|
|
158
|
+
* items:
|
|
159
|
+
* type: string
|
|
160
|
+
* format: uri
|
|
161
|
+
* links:
|
|
162
|
+
* type: object
|
|
163
|
+
* description: Navigation links to related versions.
|
|
164
|
+
* properties:
|
|
165
|
+
* first:
|
|
166
|
+
* type: string
|
|
167
|
+
* description: The ID of the first version for this service and terms type.
|
|
168
|
+
* nullable: true
|
|
169
|
+
* prev:
|
|
170
|
+
* type: string
|
|
171
|
+
* description: The ID of the previous version, or null if this is the first.
|
|
172
|
+
* nullable: true
|
|
173
|
+
* next:
|
|
174
|
+
* type: string
|
|
175
|
+
* description: The ID of the next version, or null if this is the last.
|
|
176
|
+
* nullable: true
|
|
177
|
+
* last:
|
|
178
|
+
* type: string
|
|
179
|
+
* description: The ID of the last version for this service and terms type.
|
|
180
|
+
* nullable: true
|
|
181
|
+
* ErrorResponse:
|
|
182
|
+
* type: object
|
|
183
|
+
* properties:
|
|
184
|
+
* error:
|
|
185
|
+
* type: string
|
|
186
|
+
* description: Error message.
|
|
187
|
+
* responses:
|
|
188
|
+
* BadRequestError:
|
|
189
|
+
* description: Invalid pagination parameters.
|
|
190
|
+
* content:
|
|
191
|
+
* application/json:
|
|
192
|
+
* schema:
|
|
193
|
+
* $ref: '#/components/schemas/ErrorResponse'
|
|
194
|
+
* NotFoundError:
|
|
195
|
+
* description: Resource not found.
|
|
196
|
+
* content:
|
|
197
|
+
* application/json:
|
|
198
|
+
* schema:
|
|
199
|
+
* $ref: '#/components/schemas/ErrorResponse'
|
|
29
200
|
*/
|
|
30
|
-
export default function versionsRouter(versionsRepository) {
|
|
201
|
+
export default function versionsRouter(versionsRepository, snapshotsRepository) {
|
|
31
202
|
const router = express.Router();
|
|
32
203
|
|
|
204
|
+
function parsePaginationParams(query) {
|
|
205
|
+
const limit = query.limit ? parseInt(query.limit, 10) : 100;
|
|
206
|
+
const offset = query.offset ? parseInt(query.offset, 10) : 0;
|
|
207
|
+
|
|
208
|
+
return { limit, offset };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function validatePaginationParams(limit, offset) {
|
|
212
|
+
if (Number.isNaN(limit) || limit < 1) {
|
|
213
|
+
return { error: 'Invalid limit parameter. Must be a positive integer.' };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (limit > 500) {
|
|
217
|
+
return { error: 'Invalid limit parameter. Must not exceed 500.' };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (Number.isNaN(offset) || offset < 0) {
|
|
221
|
+
return { error: 'Invalid offset parameter. Must be a non-negative integer.' };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function mapVersionToListItem(version) {
|
|
228
|
+
return {
|
|
229
|
+
id: version.id,
|
|
230
|
+
serviceId: version.serviceId,
|
|
231
|
+
termsType: version.termsType,
|
|
232
|
+
fetchDate: toISODateWithoutMilliseconds(version.fetchDate),
|
|
233
|
+
isFirstRecord: version.isFirstRecord,
|
|
234
|
+
isTechnicalUpgrade: version.isTechnicalUpgrade,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function getFetchUrls(snapshotIds) {
|
|
239
|
+
if (!snapshotIds?.length) {
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const snapshots = await Promise.all(snapshotIds.map(async id => {
|
|
244
|
+
const snapshot = await snapshotsRepository.findMetadataById(id);
|
|
245
|
+
|
|
246
|
+
if (!snapshot) {
|
|
247
|
+
logger.warn(`Could not resolve source snapshot ${id}; its fetch URL will be missing from the version. The snapshots repository is likely missing or out of sync with the versions repository.`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return snapshot;
|
|
251
|
+
}));
|
|
252
|
+
|
|
253
|
+
return snapshots
|
|
254
|
+
.filter(Boolean)
|
|
255
|
+
.map(snapshot => snapshot.metadata?.['x-source-document-location'])
|
|
256
|
+
.filter(Boolean);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Builds the full detail response shared by every single-version endpoint, so they cannot drift apart
|
|
260
|
+
async function buildVersionDetail(version) {
|
|
261
|
+
const [ navigationIds, stats, fetchUrls ] = await Promise.all([
|
|
262
|
+
versionsRepository.getNavigationIds(version.serviceId, version.termsType, version.id),
|
|
263
|
+
versionsRepository.getDiffStats(version.id),
|
|
264
|
+
getFetchUrls(version.snapshotIds),
|
|
265
|
+
]);
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
id: version.id,
|
|
269
|
+
serviceId: version.serviceId,
|
|
270
|
+
termsType: version.termsType,
|
|
271
|
+
fetchDate: toISODateWithoutMilliseconds(version.fetchDate),
|
|
272
|
+
content: version.content,
|
|
273
|
+
isFirstRecord: version.isFirstRecord,
|
|
274
|
+
isTechnicalUpgrade: version.isTechnicalUpgrade,
|
|
275
|
+
fetchUrls,
|
|
276
|
+
links: {
|
|
277
|
+
first: navigationIds.first,
|
|
278
|
+
prev: navigationIds.prev,
|
|
279
|
+
next: navigationIds.next,
|
|
280
|
+
last: navigationIds.last,
|
|
281
|
+
},
|
|
282
|
+
...stats,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* @private
|
|
288
|
+
* @swagger
|
|
289
|
+
* /versions:
|
|
290
|
+
* get:
|
|
291
|
+
* summary: Get all versions.
|
|
292
|
+
* tags: [Versions]
|
|
293
|
+
* produces:
|
|
294
|
+
* - application/json
|
|
295
|
+
* parameters:
|
|
296
|
+
* - $ref: '#/components/parameters/LimitParam'
|
|
297
|
+
* - $ref: '#/components/parameters/OffsetParam'
|
|
298
|
+
* responses:
|
|
299
|
+
* 200:
|
|
300
|
+
* description: A JSON object containing the list of all versions and metadata.
|
|
301
|
+
* content:
|
|
302
|
+
* application/json:
|
|
303
|
+
* schema:
|
|
304
|
+
* $ref: '#/components/schemas/PaginatedVersionsResponse'
|
|
305
|
+
* 400:
|
|
306
|
+
* $ref: '#/components/responses/BadRequestError'
|
|
307
|
+
*/
|
|
308
|
+
router.get('/versions', async (req, res) => {
|
|
309
|
+
const { limit, offset } = parsePaginationParams(req.query);
|
|
310
|
+
const validationError = validatePaginationParams(limit, offset);
|
|
311
|
+
|
|
312
|
+
if (validationError) {
|
|
313
|
+
return res.status(400).json(validationError);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const paginatedVersions = await versionsRepository.findAll({ limit, offset });
|
|
317
|
+
|
|
318
|
+
const versionsList = paginatedVersions.map(mapVersionToListItem);
|
|
319
|
+
|
|
320
|
+
const response = {
|
|
321
|
+
data: versionsList,
|
|
322
|
+
count: await versionsRepository.count(),
|
|
323
|
+
limit,
|
|
324
|
+
offset,
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
return res.status(200).json(response);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* @private
|
|
332
|
+
* @swagger
|
|
333
|
+
* /versions/{serviceId}:
|
|
334
|
+
* get:
|
|
335
|
+
* summary: Get all versions for a specific service.
|
|
336
|
+
* tags: [Versions]
|
|
337
|
+
* produces:
|
|
338
|
+
* - application/json
|
|
339
|
+
* parameters:
|
|
340
|
+
* - in: path
|
|
341
|
+
* name: serviceId
|
|
342
|
+
* description: The ID of the service whose versions will be returned.
|
|
343
|
+
* schema:
|
|
344
|
+
* type: string
|
|
345
|
+
* required: true
|
|
346
|
+
* - $ref: '#/components/parameters/LimitParam'
|
|
347
|
+
* - $ref: '#/components/parameters/OffsetParam'
|
|
348
|
+
* responses:
|
|
349
|
+
* 200:
|
|
350
|
+
* description: A JSON object containing the list of versions and metadata.
|
|
351
|
+
* content:
|
|
352
|
+
* application/json:
|
|
353
|
+
* schema:
|
|
354
|
+
* $ref: '#/components/schemas/PaginatedVersionsResponse'
|
|
355
|
+
* 400:
|
|
356
|
+
* $ref: '#/components/responses/BadRequestError'
|
|
357
|
+
* 404:
|
|
358
|
+
* $ref: '#/components/responses/NotFoundError'
|
|
359
|
+
*/
|
|
360
|
+
router.get('/versions/:serviceId', async (req, res) => {
|
|
361
|
+
const { serviceId } = req.params;
|
|
362
|
+
const { limit, offset } = parsePaginationParams(req.query);
|
|
363
|
+
const validationError = validatePaginationParams(limit, offset);
|
|
364
|
+
|
|
365
|
+
if (validationError) {
|
|
366
|
+
return res.status(400).json(validationError);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const totalCount = await versionsRepository.count(serviceId);
|
|
370
|
+
|
|
371
|
+
if (totalCount === 0) {
|
|
372
|
+
return res.status(404).json({ error: `No versions found for service "${serviceId}"` });
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const paginatedVersions = await versionsRepository.findByService(serviceId, { limit, offset });
|
|
376
|
+
|
|
377
|
+
const versionsList = paginatedVersions.map(mapVersionToListItem);
|
|
378
|
+
|
|
379
|
+
const response = {
|
|
380
|
+
data: versionsList,
|
|
381
|
+
count: totalCount,
|
|
382
|
+
limit,
|
|
383
|
+
offset,
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
return res.status(200).json(response);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* @private
|
|
391
|
+
* @swagger
|
|
392
|
+
* /versions/{serviceId}/{termsType}:
|
|
393
|
+
* get:
|
|
394
|
+
* summary: Get all versions of some terms for a specific service.
|
|
395
|
+
* tags: [Versions]
|
|
396
|
+
* produces:
|
|
397
|
+
* - application/json
|
|
398
|
+
* parameters:
|
|
399
|
+
* - in: path
|
|
400
|
+
* name: serviceId
|
|
401
|
+
* description: The ID of the service whose versions will be returned.
|
|
402
|
+
* schema:
|
|
403
|
+
* type: string
|
|
404
|
+
* required: true
|
|
405
|
+
* - in: path
|
|
406
|
+
* name: termsType
|
|
407
|
+
* description: The type of terms whose versions will be returned.
|
|
408
|
+
* schema:
|
|
409
|
+
* type: string
|
|
410
|
+
* required: true
|
|
411
|
+
* - $ref: '#/components/parameters/LimitParam'
|
|
412
|
+
* - $ref: '#/components/parameters/OffsetParam'
|
|
413
|
+
* responses:
|
|
414
|
+
* 200:
|
|
415
|
+
* description: A JSON object containing the list of versions with diff statistics and metadata.
|
|
416
|
+
* content:
|
|
417
|
+
* application/json:
|
|
418
|
+
* schema:
|
|
419
|
+
* $ref: '#/components/schemas/PaginatedVersionsWithStatsResponse'
|
|
420
|
+
* 400:
|
|
421
|
+
* $ref: '#/components/responses/BadRequestError'
|
|
422
|
+
* 404:
|
|
423
|
+
* $ref: '#/components/responses/NotFoundError'
|
|
424
|
+
*/
|
|
425
|
+
router.get('/versions/:serviceId/:termsType', async (req, res) => {
|
|
426
|
+
const { serviceId, termsType } = req.params;
|
|
427
|
+
const { limit, offset } = parsePaginationParams(req.query);
|
|
428
|
+
const validationError = validatePaginationParams(limit, offset);
|
|
429
|
+
|
|
430
|
+
if (validationError) {
|
|
431
|
+
return res.status(400).json(validationError);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const totalCount = await versionsRepository.count(serviceId, termsType);
|
|
435
|
+
|
|
436
|
+
if (totalCount === 0) {
|
|
437
|
+
return res.status(404).json({ error: `No versions found for service "${serviceId}" and terms type "${termsType}"` });
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const paginatedVersions = await versionsRepository.findByServiceAndTermsType(serviceId, termsType, { limit, offset });
|
|
441
|
+
|
|
442
|
+
const versionsList = await Promise.all(paginatedVersions.map(async version => {
|
|
443
|
+
const stats = await versionsRepository.getDiffStats(version.id);
|
|
444
|
+
|
|
445
|
+
return {
|
|
446
|
+
...mapVersionToListItem(version),
|
|
447
|
+
...stats,
|
|
448
|
+
};
|
|
449
|
+
}));
|
|
450
|
+
|
|
451
|
+
const response = {
|
|
452
|
+
data: versionsList,
|
|
453
|
+
count: totalCount,
|
|
454
|
+
limit,
|
|
455
|
+
offset,
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
return res.status(200).json(response);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* @private
|
|
463
|
+
* @swagger
|
|
464
|
+
* /version/{versionId}:
|
|
465
|
+
* get:
|
|
466
|
+
* summary: Get a specific version by its ID.
|
|
467
|
+
* tags: [Versions]
|
|
468
|
+
* produces:
|
|
469
|
+
* - application/json
|
|
470
|
+
* parameters:
|
|
471
|
+
* - in: path
|
|
472
|
+
* name: versionId
|
|
473
|
+
* description: The ID of the version to retrieve.
|
|
474
|
+
* schema:
|
|
475
|
+
* type: string
|
|
476
|
+
* required: true
|
|
477
|
+
* responses:
|
|
478
|
+
* 200:
|
|
479
|
+
* description: A JSON object containing the version content, metadata, and navigation links.
|
|
480
|
+
* content:
|
|
481
|
+
* application/json:
|
|
482
|
+
* schema:
|
|
483
|
+
* $ref: '#/components/schemas/VersionWithLinks'
|
|
484
|
+
* 404:
|
|
485
|
+
* $ref: '#/components/responses/NotFoundError'
|
|
486
|
+
*/
|
|
487
|
+
router.get('/version/:versionId', async (req, res) => {
|
|
488
|
+
const { versionId } = req.params;
|
|
489
|
+
|
|
490
|
+
const version = await versionsRepository.findById(versionId);
|
|
491
|
+
|
|
492
|
+
if (!version) {
|
|
493
|
+
return res.status(404).json({ error: `No version found with ID "${versionId}"` });
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return res.status(200).json(await buildVersionDetail(version));
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* @private
|
|
501
|
+
* @swagger
|
|
502
|
+
* /version/{serviceId}/{termsType}/latest:
|
|
503
|
+
* get:
|
|
504
|
+
* summary: Get the latest version of some terms for a service.
|
|
505
|
+
* tags: [Versions]
|
|
506
|
+
* produces:
|
|
507
|
+
* - application/json
|
|
508
|
+
* parameters:
|
|
509
|
+
* - in: path
|
|
510
|
+
* name: serviceId
|
|
511
|
+
* description: The ID of the service whose version will be returned.
|
|
512
|
+
* schema:
|
|
513
|
+
* type: string
|
|
514
|
+
* required: true
|
|
515
|
+
* - in: path
|
|
516
|
+
* name: termsType
|
|
517
|
+
* description: The type of terms whose version will be returned.
|
|
518
|
+
* schema:
|
|
519
|
+
* type: string
|
|
520
|
+
* required: true
|
|
521
|
+
* responses:
|
|
522
|
+
* 200:
|
|
523
|
+
* description: A JSON object containing the version content, metadata, and navigation links.
|
|
524
|
+
* content:
|
|
525
|
+
* application/json:
|
|
526
|
+
* schema:
|
|
527
|
+
* $ref: '#/components/schemas/VersionWithLinks'
|
|
528
|
+
* 404:
|
|
529
|
+
* $ref: '#/components/responses/NotFoundError'
|
|
530
|
+
*/
|
|
531
|
+
router.get('/version/:serviceId/:termsType/latest', async (req, res) => {
|
|
532
|
+
const { serviceId, termsType } = req.params;
|
|
533
|
+
|
|
534
|
+
const version = await versionsRepository.findLatest(serviceId, termsType);
|
|
535
|
+
|
|
536
|
+
if (!version) {
|
|
537
|
+
return res.status(404).json({ error: `No version found for service "${serviceId}" and terms type "${termsType}"` });
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return res.status(200).json(await buildVersionDetail(version));
|
|
541
|
+
});
|
|
542
|
+
|
|
33
543
|
/**
|
|
34
544
|
* @private
|
|
35
545
|
* @swagger
|
|
@@ -61,31 +571,19 @@ export default function versionsRouter(versionsRepository) {
|
|
|
61
571
|
* required: true
|
|
62
572
|
* responses:
|
|
63
573
|
* 200:
|
|
64
|
-
* description: A JSON object containing the version content and
|
|
574
|
+
* description: A JSON object containing the version content, metadata, and navigation links.
|
|
65
575
|
* content:
|
|
66
576
|
* application/json:
|
|
67
577
|
* schema:
|
|
68
|
-
* $ref: '#/components/schemas/
|
|
578
|
+
* $ref: '#/components/schemas/VersionWithLinks'
|
|
69
579
|
* 404:
|
|
70
|
-
*
|
|
71
|
-
* content:
|
|
72
|
-
* application/json:
|
|
73
|
-
* schema:
|
|
74
|
-
* type: object
|
|
75
|
-
* properties:
|
|
76
|
-
* error:
|
|
77
|
-
* type: string
|
|
78
|
-
* description: Error message indicating that no version is found.
|
|
580
|
+
* $ref: '#/components/responses/NotFoundError'
|
|
79
581
|
* 416:
|
|
80
582
|
* description: The requested date is in the future.
|
|
81
583
|
* content:
|
|
82
584
|
* application/json:
|
|
83
585
|
* schema:
|
|
84
|
-
*
|
|
85
|
-
* properties:
|
|
86
|
-
* error:
|
|
87
|
-
* type: string
|
|
88
|
-
* description: Error message indicating that the requested date is in the future.
|
|
586
|
+
* $ref: '#/components/schemas/ErrorResponse'
|
|
89
587
|
*/
|
|
90
588
|
router.get('/version/:serviceId/:termsType/:date', async (req, res) => {
|
|
91
589
|
const { serviceId, termsType, date } = req.params;
|
|
@@ -98,14 +596,10 @@ export default function versionsRouter(versionsRepository) {
|
|
|
98
596
|
const version = await versionsRepository.findByDate(serviceId, termsType, requestedDate);
|
|
99
597
|
|
|
100
598
|
if (!version) {
|
|
101
|
-
return res.status(404).json({ error: `No version found for date ${date}` });
|
|
599
|
+
return res.status(404).json({ error: `No version found for service "${serviceId}" and terms type "${termsType}" at date ${date}` });
|
|
102
600
|
}
|
|
103
601
|
|
|
104
|
-
return res.status(200).json(
|
|
105
|
-
id: version.id,
|
|
106
|
-
fetchDate: toISODateWithoutMilliseconds(version.fetchDate),
|
|
107
|
-
content: version.content,
|
|
108
|
-
});
|
|
602
|
+
return res.status(200).json(await buildVersionDetail(version));
|
|
109
603
|
});
|
|
110
604
|
|
|
111
605
|
return router;
|