@twin.org/document-management-service 0.0.1-next.2
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/LICENSE +201 -0
- package/README.md +21 -0
- package/dist/cjs/index.cjs +866 -0
- package/dist/esm/index.mjs +857 -0
- package/dist/types/documentManagementRoutes.d.ts +45 -0
- package/dist/types/documentManagementService.d.ts +90 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/models/IDocumentManagementServiceConfig.d.ts +5 -0
- package/dist/types/models/IDocumentManagementStorageServiceConstructorOptions.d.ts +25 -0
- package/dist/types/restEntryPoints.d.ts +2 -0
- package/docs/changelog.md +5 -0
- package/docs/examples.md +1 -0
- package/docs/open-api/spec.json +5443 -0
- package/docs/reference/classes/DocumentManagementService.md +328 -0
- package/docs/reference/functions/documentManagementGet.md +31 -0
- package/docs/reference/functions/documentManagementQuery.md +31 -0
- package/docs/reference/functions/documentManagementRemove.md +31 -0
- package/docs/reference/functions/documentManagementSet.md +31 -0
- package/docs/reference/functions/generateRestRoutesDocumentManagement.md +25 -0
- package/docs/reference/index.md +23 -0
- package/docs/reference/interfaces/IDocumentManagementServiceConfig.md +3 -0
- package/docs/reference/interfaces/IDocumentManagementServiceConstructorOptions.md +53 -0
- package/docs/reference/variables/restEntryPoints.md +3 -0
- package/docs/reference/variables/tagsDocumentManagement.md +5 -0
- package/locales/en.json +14 -0
- package/package.json +50 -0
|
@@ -0,0 +1,857 @@
|
|
|
1
|
+
import { HttpParameterHelper } from '@twin.org/api-models';
|
|
2
|
+
import { Guards, ComponentFactory, Converter, Coerce, Is, ObjectHelper, BaseError, GeneralError, Urn, NotFoundError } from '@twin.org/core';
|
|
3
|
+
import { DocumentTypes } from '@twin.org/document-management-models';
|
|
4
|
+
import { SchemaOrgTypes, SchemaOrgDataTypes } from '@twin.org/standards-schema-org';
|
|
5
|
+
import { UneceDocumentCodes } from '@twin.org/standards-unece';
|
|
6
|
+
import { HttpStatusCode, HeaderTypes, MimeTypes } from '@twin.org/web';
|
|
7
|
+
import { AttestationTypes } from '@twin.org/attestation-models';
|
|
8
|
+
import { AuditableItemGraphTypes } from '@twin.org/auditable-item-graph-models';
|
|
9
|
+
import { BlobStorageTypes } from '@twin.org/blob-storage-models';
|
|
10
|
+
import { Sha256 } from '@twin.org/crypto';
|
|
11
|
+
import { JsonLdProcessor } from '@twin.org/data-json-ld';
|
|
12
|
+
|
|
13
|
+
// Copyright 2024 IOTA Stiftung.
|
|
14
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
15
|
+
/**
|
|
16
|
+
* The source used when communicating about these routes.
|
|
17
|
+
*/
|
|
18
|
+
const ROUTES_SOURCE = "documentManagementStorageRoutes";
|
|
19
|
+
/**
|
|
20
|
+
* The tag to associate with the routes.
|
|
21
|
+
*/
|
|
22
|
+
const tagsDocumentManagement = [
|
|
23
|
+
{
|
|
24
|
+
name: "Document Management",
|
|
25
|
+
description: "Endpoints which are modelled to access a document management contract."
|
|
26
|
+
}
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* The REST routes for document management.
|
|
30
|
+
* @param baseRouteName Prefix to prepend to the paths.
|
|
31
|
+
* @param componentName The name of the component to use in the routes stored in the ComponentFactory.
|
|
32
|
+
* @returns The generated routes.
|
|
33
|
+
*/
|
|
34
|
+
function generateRestRoutesDocumentManagement(baseRouteName, componentName) {
|
|
35
|
+
const documentManagementSetRoute = {
|
|
36
|
+
operationId: "DocumentManagementSet",
|
|
37
|
+
summary: "Store a document in an auditable item graph vertex and add its content to blob storage.",
|
|
38
|
+
tag: tagsDocumentManagement[0].name,
|
|
39
|
+
method: "POST",
|
|
40
|
+
path: `${baseRouteName}/:auditableItemGraphId`,
|
|
41
|
+
handler: async (httpRequestContext, request) => documentManagementSet(httpRequestContext, componentName, request),
|
|
42
|
+
requestType: {
|
|
43
|
+
type: "IDocumentManagementSetRequest",
|
|
44
|
+
examples: [
|
|
45
|
+
{
|
|
46
|
+
id: "DocumentManagementSetRequestExample",
|
|
47
|
+
request: {
|
|
48
|
+
pathParams: {
|
|
49
|
+
auditableItemGraphId: "aig:123456"
|
|
50
|
+
},
|
|
51
|
+
body: {
|
|
52
|
+
documentId: "2721000",
|
|
53
|
+
documentIdFormat: "bol",
|
|
54
|
+
documentCode: UneceDocumentCodes.BillOfLading,
|
|
55
|
+
blob: "SGVsbG8gV29ybGQ=",
|
|
56
|
+
annotationObject: {
|
|
57
|
+
"@context": "https://schema.org",
|
|
58
|
+
"@type": "DigitalDocument",
|
|
59
|
+
name: "myfile.pdf"
|
|
60
|
+
},
|
|
61
|
+
createAttestation: true
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
responseType: [
|
|
68
|
+
{
|
|
69
|
+
type: "ICreatedResponse",
|
|
70
|
+
examples: [
|
|
71
|
+
{
|
|
72
|
+
id: "DocumentManagementSetResponseExample",
|
|
73
|
+
response: {
|
|
74
|
+
statusCode: HttpStatusCode.created,
|
|
75
|
+
headers: {
|
|
76
|
+
[HeaderTypes.Location]: "documents:123456:705:2721000"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
};
|
|
84
|
+
const documentManagementGetRoute = {
|
|
85
|
+
operationId: "DocumentManagementGet",
|
|
86
|
+
summary: "Get the data for a document from document management",
|
|
87
|
+
tag: tagsDocumentManagement[0].name,
|
|
88
|
+
method: "GET",
|
|
89
|
+
path: `${baseRouteName}/:auditableItemGraphId/:documentId`,
|
|
90
|
+
handler: async (httpRequestContext, request) => documentManagementGet(httpRequestContext, componentName, request),
|
|
91
|
+
requestType: {
|
|
92
|
+
type: "IDocumentManagementGetRequest",
|
|
93
|
+
examples: [
|
|
94
|
+
{
|
|
95
|
+
id: "DocumentManagementGetRequestExample",
|
|
96
|
+
request: {
|
|
97
|
+
pathParams: {
|
|
98
|
+
auditableItemGraphId: "aig:1234",
|
|
99
|
+
documentId: "documents:123456:705:2721000"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
responseType: [
|
|
106
|
+
{
|
|
107
|
+
type: "IDocumentManagementGetResponse",
|
|
108
|
+
examples: [
|
|
109
|
+
{
|
|
110
|
+
id: "DocumentManagementGetResponseExample",
|
|
111
|
+
response: {
|
|
112
|
+
body: {
|
|
113
|
+
"@context": [
|
|
114
|
+
DocumentTypes.ContextRoot,
|
|
115
|
+
DocumentTypes.ContextRootCommon,
|
|
116
|
+
SchemaOrgTypes.ContextRoot
|
|
117
|
+
],
|
|
118
|
+
type: DocumentTypes.Document,
|
|
119
|
+
id: "documents:705:2721000:rev-0",
|
|
120
|
+
documentId: "2721000",
|
|
121
|
+
documentIdFormat: "bol",
|
|
122
|
+
documentCode: UneceDocumentCodes.BillOfLading,
|
|
123
|
+
documentRevision: 0,
|
|
124
|
+
blobStorageId: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
|
|
125
|
+
blobHash: "sha256:123456",
|
|
126
|
+
dateCreated: "2024-01-01T00:00:00Z",
|
|
127
|
+
annotationObject: {
|
|
128
|
+
"@context": "https://schema.org",
|
|
129
|
+
"@type": "DigitalDocument",
|
|
130
|
+
name: "myfile.pdf"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
type: "IDocumentManagementGetResponse",
|
|
139
|
+
mimeType: MimeTypes.JsonLd,
|
|
140
|
+
examples: [
|
|
141
|
+
{
|
|
142
|
+
id: "DocumentManagementGetResponseExample",
|
|
143
|
+
response: {
|
|
144
|
+
body: {
|
|
145
|
+
"@context": [
|
|
146
|
+
DocumentTypes.ContextRoot,
|
|
147
|
+
DocumentTypes.ContextRootCommon,
|
|
148
|
+
SchemaOrgTypes.ContextRoot
|
|
149
|
+
],
|
|
150
|
+
type: DocumentTypes.Document,
|
|
151
|
+
id: "documents:705:2721000:rev-0",
|
|
152
|
+
documentId: "2721000",
|
|
153
|
+
documentIdFormat: "bol",
|
|
154
|
+
documentCode: UneceDocumentCodes.BillOfLading,
|
|
155
|
+
documentRevision: 0,
|
|
156
|
+
blobStorageId: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
|
|
157
|
+
blobHash: "sha256:123456",
|
|
158
|
+
dateCreated: "2024-01-01T00:00:00Z",
|
|
159
|
+
annotationObject: {
|
|
160
|
+
"@context": "https://schema.org",
|
|
161
|
+
"@type": "DigitalDocument",
|
|
162
|
+
name: "myfile.pdf"
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
type: "INotFoundResponse"
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
};
|
|
174
|
+
const documentManagementRemoveRoute = {
|
|
175
|
+
operationId: "DocumentManagementRemove",
|
|
176
|
+
summary: "Remove an document from an auditable item graph vertex",
|
|
177
|
+
tag: tagsDocumentManagement[0].name,
|
|
178
|
+
method: "DELETE",
|
|
179
|
+
path: `${baseRouteName}/:auditableItemGraphId/:documentId`,
|
|
180
|
+
handler: async (httpRequestContext, request) => documentManagementRemove(httpRequestContext, componentName, request),
|
|
181
|
+
requestType: {
|
|
182
|
+
type: "IDocumentManagementRemoveRequest",
|
|
183
|
+
examples: [
|
|
184
|
+
{
|
|
185
|
+
id: "DocumentManagementRemoveRequestExample",
|
|
186
|
+
request: {
|
|
187
|
+
pathParams: {
|
|
188
|
+
auditableItemGraphId: "aig:1234",
|
|
189
|
+
documentId: "documents:123456:705:2721000"
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
]
|
|
194
|
+
},
|
|
195
|
+
responseType: [
|
|
196
|
+
{
|
|
197
|
+
type: "INoContentResponse"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
type: "INotFoundResponse"
|
|
201
|
+
}
|
|
202
|
+
]
|
|
203
|
+
};
|
|
204
|
+
const documentManagementQueryRoute = {
|
|
205
|
+
operationId: "DocumentManagementQuery",
|
|
206
|
+
summary: "Query the items from an auditable item graph vertex",
|
|
207
|
+
tag: tagsDocumentManagement[0].name,
|
|
208
|
+
method: "GET",
|
|
209
|
+
path: `${baseRouteName}/:auditableItemGraphId`,
|
|
210
|
+
handler: async (httpRequestContext, request) => documentManagementQuery(httpRequestContext, componentName, request),
|
|
211
|
+
requestType: {
|
|
212
|
+
type: "IDocumentManagementQueryRequest",
|
|
213
|
+
examples: [
|
|
214
|
+
{
|
|
215
|
+
id: "DocumentManagementQueryRequestExample",
|
|
216
|
+
request: {
|
|
217
|
+
pathParams: {
|
|
218
|
+
auditableItemGraphId: "aig:123456"
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
]
|
|
223
|
+
},
|
|
224
|
+
responseType: [
|
|
225
|
+
{
|
|
226
|
+
type: "IDocumentManagementQueryResponse",
|
|
227
|
+
examples: [
|
|
228
|
+
{
|
|
229
|
+
id: "DocumentManagementQueryResponseExample",
|
|
230
|
+
response: {
|
|
231
|
+
body: {
|
|
232
|
+
"@context": [DocumentTypes.ContextRoot, DocumentTypes.ContextRootCommon],
|
|
233
|
+
type: DocumentTypes.DocumentList,
|
|
234
|
+
documents: [
|
|
235
|
+
{
|
|
236
|
+
"@context": [
|
|
237
|
+
DocumentTypes.ContextRoot,
|
|
238
|
+
DocumentTypes.ContextRootCommon,
|
|
239
|
+
SchemaOrgTypes.ContextRoot
|
|
240
|
+
],
|
|
241
|
+
type: DocumentTypes.Document,
|
|
242
|
+
id: "documents:705:2721000:rev-0",
|
|
243
|
+
documentId: "2721000",
|
|
244
|
+
documentIdFormat: "bol",
|
|
245
|
+
documentCode: UneceDocumentCodes.BillOfLading,
|
|
246
|
+
documentRevision: 0,
|
|
247
|
+
blobStorageId: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
|
|
248
|
+
blobHash: "sha256:123456",
|
|
249
|
+
dateCreated: "2024-01-01T00:00:00Z",
|
|
250
|
+
annotationObject: {
|
|
251
|
+
"@context": "https://schema.org",
|
|
252
|
+
"@type": "DigitalDocument",
|
|
253
|
+
name: "myfile.pdf"
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
]
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
]
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
type: "IDocumentManagementQueryResponse",
|
|
264
|
+
mimeType: MimeTypes.JsonLd,
|
|
265
|
+
examples: [
|
|
266
|
+
{
|
|
267
|
+
id: "DocumentManagementListResponseJsonLdExample",
|
|
268
|
+
response: {
|
|
269
|
+
body: {
|
|
270
|
+
"@context": [DocumentTypes.ContextRoot, DocumentTypes.ContextRootCommon],
|
|
271
|
+
type: DocumentTypes.DocumentList,
|
|
272
|
+
documents: [
|
|
273
|
+
{
|
|
274
|
+
"@context": [
|
|
275
|
+
DocumentTypes.ContextRoot,
|
|
276
|
+
DocumentTypes.ContextRootCommon,
|
|
277
|
+
SchemaOrgTypes.ContextRoot
|
|
278
|
+
],
|
|
279
|
+
type: DocumentTypes.Document,
|
|
280
|
+
id: "documents:705:2721000:rev-0",
|
|
281
|
+
documentId: "2721000",
|
|
282
|
+
documentIdFormat: "bol",
|
|
283
|
+
documentCode: UneceDocumentCodes.BillOfLading,
|
|
284
|
+
documentRevision: 0,
|
|
285
|
+
blobStorageId: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
|
|
286
|
+
blobHash: "sha256:123456",
|
|
287
|
+
dateCreated: "2024-01-01T00:00:00Z",
|
|
288
|
+
annotationObject: {
|
|
289
|
+
"@context": "https://schema.org",
|
|
290
|
+
"@type": "DigitalDocument",
|
|
291
|
+
name: "myfile.pdf"
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
]
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
]
|
|
299
|
+
}
|
|
300
|
+
]
|
|
301
|
+
};
|
|
302
|
+
return [
|
|
303
|
+
documentManagementSetRoute,
|
|
304
|
+
documentManagementGetRoute,
|
|
305
|
+
documentManagementRemoveRoute,
|
|
306
|
+
documentManagementQueryRoute
|
|
307
|
+
];
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Set a document in to an auditable item graph vertex.
|
|
311
|
+
* @param httpRequestContext The request context for the API.
|
|
312
|
+
* @param componentName The name of the component to use in the routes.
|
|
313
|
+
* @param request The request.
|
|
314
|
+
* @returns The response object with additional http response properties.
|
|
315
|
+
*/
|
|
316
|
+
async function documentManagementSet(httpRequestContext, componentName, request) {
|
|
317
|
+
Guards.object(ROUTES_SOURCE, "request", request);
|
|
318
|
+
Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
|
|
319
|
+
Guards.stringValue(ROUTES_SOURCE, "request.pathParams.auditableItemGraphId", request.pathParams.auditableItemGraphId);
|
|
320
|
+
Guards.object(ROUTES_SOURCE, "request.body", request.body);
|
|
321
|
+
Guards.stringBase64(ROUTES_SOURCE, "request.body.blob", request.body.blob);
|
|
322
|
+
const component = ComponentFactory.get(componentName);
|
|
323
|
+
const id = await component.set(request.pathParams.auditableItemGraphId, request.body.documentId, request.body.documentIdFormat, request.body.documentCode, Converter.base64ToBytes(request.body.blob), request.body.annotationObject, request.body.createAttestation, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
|
|
324
|
+
return {
|
|
325
|
+
statusCode: HttpStatusCode.created,
|
|
326
|
+
headers: {
|
|
327
|
+
location: id
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Get the document from the auditable item graph vertex.
|
|
333
|
+
* @param httpRequestContext The request context for the API.
|
|
334
|
+
* @param componentName The name of the component to use in the routes.
|
|
335
|
+
* @param request The request.
|
|
336
|
+
* @returns The response object with additional http response properties.
|
|
337
|
+
*/
|
|
338
|
+
async function documentManagementGet(httpRequestContext, componentName, request) {
|
|
339
|
+
Guards.object(ROUTES_SOURCE, "request", request);
|
|
340
|
+
Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
|
|
341
|
+
Guards.stringValue(ROUTES_SOURCE, "request.pathParams.auditableItemGraphId", request.pathParams.auditableItemGraphId);
|
|
342
|
+
Guards.stringValue(ROUTES_SOURCE, "request.pathParams.documentId", request.pathParams.documentId);
|
|
343
|
+
const mimeType = request.headers?.[HeaderTypes.Accept] === MimeTypes.JsonLd ? "jsonld" : "json";
|
|
344
|
+
const component = ComponentFactory.get(componentName);
|
|
345
|
+
const result = await component.get(request.pathParams.auditableItemGraphId, request.pathParams.documentId, {
|
|
346
|
+
includeBlobStorageMetadata: Coerce.boolean(request.query?.includeBlobStorageMetadata),
|
|
347
|
+
includeBlobStorageData: Coerce.boolean(request.query?.includeBlobStorageData),
|
|
348
|
+
includeAttestation: Coerce.boolean(request.query?.includeAttestation),
|
|
349
|
+
includeRemoved: Coerce.boolean(request.query?.includeRemoved),
|
|
350
|
+
maxRevisionCount: Coerce.integer(request.query?.maxRevisionCount)
|
|
351
|
+
}, request.query?.revisionCursor, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
|
|
352
|
+
return {
|
|
353
|
+
headers: {
|
|
354
|
+
[HeaderTypes.ContentType]: mimeType === "json" ? MimeTypes.Json : MimeTypes.JsonLd
|
|
355
|
+
},
|
|
356
|
+
body: result
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Remove the document from the auditable item graph vertex.
|
|
361
|
+
* @param httpRequestContext The request context for the API.
|
|
362
|
+
* @param componentName The name of the component to use in the routes.
|
|
363
|
+
* @param request The request.
|
|
364
|
+
* @returns The response object with additional http response properties.
|
|
365
|
+
*/
|
|
366
|
+
async function documentManagementRemove(httpRequestContext, componentName, request) {
|
|
367
|
+
Guards.object(ROUTES_SOURCE, "request", request);
|
|
368
|
+
Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
|
|
369
|
+
Guards.stringValue(ROUTES_SOURCE, "request.pathParams.auditableItemGraphId", request.pathParams.auditableItemGraphId);
|
|
370
|
+
Guards.stringValue(ROUTES_SOURCE, "request.pathParams.documentId", request.pathParams.documentId);
|
|
371
|
+
const component = ComponentFactory.get(componentName);
|
|
372
|
+
await component.remove(request.pathParams.auditableItemGraphId, request.pathParams.documentId, {
|
|
373
|
+
removeAllRevisions: request.query?.removeAllRevisions
|
|
374
|
+
}, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
|
|
375
|
+
return {
|
|
376
|
+
statusCode: HttpStatusCode.noContent
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Query the documents from an auditable item graph vertex.
|
|
381
|
+
* @param httpRequestContext The request context for the API.
|
|
382
|
+
* @param componentName The name of the component to use in the routes.
|
|
383
|
+
* @param request The request.
|
|
384
|
+
* @returns The response object with additional http response properties.
|
|
385
|
+
*/
|
|
386
|
+
async function documentManagementQuery(httpRequestContext, componentName, request) {
|
|
387
|
+
Guards.object(ROUTES_SOURCE, "request", request);
|
|
388
|
+
Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
|
|
389
|
+
Guards.stringValue(ROUTES_SOURCE, "request.pathParams.auditableItemGraphId", request.pathParams.auditableItemGraphId);
|
|
390
|
+
const mimeType = request.headers?.[HeaderTypes.Accept] === MimeTypes.JsonLd ? "jsonld" : "json";
|
|
391
|
+
const component = ComponentFactory.get(componentName);
|
|
392
|
+
const result = await component.query(request.pathParams.auditableItemGraphId, HttpParameterHelper.arrayFromString(request.query?.documentCodes), {
|
|
393
|
+
includeRemoved: Coerce.boolean(request.query?.includeRemoved),
|
|
394
|
+
includeMostRecentRevisions: Coerce.boolean(request.query?.includeMostRecentRevisions)
|
|
395
|
+
}, request.query?.cursor, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
|
|
396
|
+
return {
|
|
397
|
+
headers: {
|
|
398
|
+
[HeaderTypes.ContentType]: mimeType === "json" ? MimeTypes.Json : MimeTypes.JsonLd
|
|
399
|
+
},
|
|
400
|
+
body: result
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Service for performing document management operations.
|
|
406
|
+
*/
|
|
407
|
+
class DocumentManagementService {
|
|
408
|
+
/**
|
|
409
|
+
* The namespace supported by the document management service.
|
|
410
|
+
*/
|
|
411
|
+
static NAMESPACE = "documents";
|
|
412
|
+
/**
|
|
413
|
+
* Default Page Size for cursor.
|
|
414
|
+
* @internal
|
|
415
|
+
*/
|
|
416
|
+
static _DEFAULT_PAGE_SIZE = 20;
|
|
417
|
+
/**
|
|
418
|
+
* Runtime name for the class.
|
|
419
|
+
*/
|
|
420
|
+
CLASS_NAME = "DocumentManagementService";
|
|
421
|
+
/**
|
|
422
|
+
* The component for the auditable item graph.
|
|
423
|
+
* @internal
|
|
424
|
+
*/
|
|
425
|
+
_auditableItemGraphComponent;
|
|
426
|
+
/**
|
|
427
|
+
* The connector for the blob component.
|
|
428
|
+
* @internal
|
|
429
|
+
*/
|
|
430
|
+
_blobStorageComponent;
|
|
431
|
+
/**
|
|
432
|
+
* The connector for the attestation.
|
|
433
|
+
* @internal
|
|
434
|
+
*/
|
|
435
|
+
_attestationComponent;
|
|
436
|
+
/**
|
|
437
|
+
* Create a new instance of DocumentManagementService.
|
|
438
|
+
* @param options The options for the service.
|
|
439
|
+
*/
|
|
440
|
+
constructor(options) {
|
|
441
|
+
this._auditableItemGraphComponent = ComponentFactory.get(options?.auditableItemGraphComponentType ?? "auditable-item-graph");
|
|
442
|
+
this._blobStorageComponent = ComponentFactory.get(options?.blobStorageComponentType ?? "blob-storage");
|
|
443
|
+
this._attestationComponent = ComponentFactory.get(options?.attestationComponentType ?? "attestation");
|
|
444
|
+
SchemaOrgDataTypes.registerRedirects();
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Store a document in an auditable item graph vertex and add its content to blob storage.
|
|
448
|
+
* If the document id already exists and the blob data is different a new revision will be created.
|
|
449
|
+
* For any other changes the current revision will be updated.
|
|
450
|
+
* @param auditableItemGraphId The auditable item graph vertex id to create the document on.
|
|
451
|
+
* @param documentId The document id to create.
|
|
452
|
+
* @param documentIdFormat The format of the document identifier.
|
|
453
|
+
* @param documentCode The code for the document type.
|
|
454
|
+
* @param blob The data to create the document.
|
|
455
|
+
* @param annotationObject Additional information to associate with the document.
|
|
456
|
+
* @param createAttestation Flag to create an attestation for the document, defaults to false.
|
|
457
|
+
* @param userIdentity The identity to perform the auditable item graph operation with.
|
|
458
|
+
* @param nodeIdentity The node identity to use for vault operations.
|
|
459
|
+
* @returns The identifier for the document which includes the auditable item graph identifier.
|
|
460
|
+
*/
|
|
461
|
+
async set(auditableItemGraphId, documentId, documentIdFormat, documentCode, blob, annotationObject, createAttestation, userIdentity, nodeIdentity) {
|
|
462
|
+
Guards.stringValue(this.CLASS_NAME, "auditableItemGraphId", auditableItemGraphId);
|
|
463
|
+
Guards.stringValue(this.CLASS_NAME, "documentId", documentId);
|
|
464
|
+
Guards.arrayOneOf(this.CLASS_NAME, "documentCode", documentCode, Object.values(UneceDocumentCodes));
|
|
465
|
+
Guards.uint8Array(this.CLASS_NAME, "blob", blob);
|
|
466
|
+
try {
|
|
467
|
+
const vertex = await this._auditableItemGraphComponent.get(auditableItemGraphId);
|
|
468
|
+
vertex.resources = vertex.resources ?? [];
|
|
469
|
+
// Get all the docs from the AIG vertex
|
|
470
|
+
const vertexDocs = this.filterDocumentsFromVertex(vertex);
|
|
471
|
+
// Reduce the list to those with a matching id and code
|
|
472
|
+
const matchingDocIds = this.findMatchingDocs(vertexDocs, documentId, documentCode, true);
|
|
473
|
+
// Calculate the hash for the blob.
|
|
474
|
+
const blobHash = this.generateBlobHash(blob);
|
|
475
|
+
let documentRevision;
|
|
476
|
+
if (Is.arrayValue(matchingDocIds) && matchingDocIds[0].blobHash === blobHash) {
|
|
477
|
+
documentRevision = matchingDocIds[0].documentRevision;
|
|
478
|
+
// If there is already a doc with the matching blob hash no need to create a new revision
|
|
479
|
+
// instead we just update the annotation object if it has changed.
|
|
480
|
+
if (!ObjectHelper.equal(matchingDocIds[0].annotationObject, annotationObject, false)) {
|
|
481
|
+
matchingDocIds[0].dateModified = new Date().toISOString();
|
|
482
|
+
matchingDocIds[0].annotationObject = annotationObject;
|
|
483
|
+
await this._auditableItemGraphComponent.update(vertex, userIdentity, nodeIdentity);
|
|
484
|
+
}
|
|
485
|
+
return matchingDocIds[0].id;
|
|
486
|
+
}
|
|
487
|
+
// Nothing matches the current blob hash so upload it to blob storage
|
|
488
|
+
const blobStorageId = await this._blobStorageComponent.create(Converter.bytesToBase64(blob), undefined, undefined, undefined, undefined, userIdentity, nodeIdentity);
|
|
489
|
+
documentRevision = matchingDocIds.length;
|
|
490
|
+
// We are creating a new document, if there is already docs with the same id and code we use the list length
|
|
491
|
+
// to determine the next revision number.
|
|
492
|
+
const document = {
|
|
493
|
+
"@context": [
|
|
494
|
+
DocumentTypes.ContextRoot,
|
|
495
|
+
DocumentTypes.ContextRootCommon,
|
|
496
|
+
SchemaOrgTypes.ContextRoot
|
|
497
|
+
],
|
|
498
|
+
type: DocumentTypes.Document,
|
|
499
|
+
id: this.createIdentifier(documentCode, documentId, documentRevision),
|
|
500
|
+
documentId,
|
|
501
|
+
documentIdFormat,
|
|
502
|
+
documentCode,
|
|
503
|
+
documentRevision,
|
|
504
|
+
blobStorageId,
|
|
505
|
+
blobHash,
|
|
506
|
+
dateCreated: new Date(Date.now()).toISOString()
|
|
507
|
+
};
|
|
508
|
+
// If the attestation flag is set then create it
|
|
509
|
+
if (createAttestation ?? false) {
|
|
510
|
+
document.attestationId = await this._attestationComponent.create(document, undefined, userIdentity, nodeIdentity);
|
|
511
|
+
}
|
|
512
|
+
// We assign the annotation object after the attestation was created
|
|
513
|
+
// as we don't want to include it in the attestation
|
|
514
|
+
document.annotationObject = annotationObject;
|
|
515
|
+
// Add the new revision in to the AIG
|
|
516
|
+
vertex.resources.push({
|
|
517
|
+
"@context": AuditableItemGraphTypes.ContextRoot,
|
|
518
|
+
type: AuditableItemGraphTypes.Resource,
|
|
519
|
+
resourceObject: document
|
|
520
|
+
});
|
|
521
|
+
await this._auditableItemGraphComponent.update(vertex, userIdentity, nodeIdentity);
|
|
522
|
+
return document.id;
|
|
523
|
+
}
|
|
524
|
+
catch (error) {
|
|
525
|
+
if (BaseError.someErrorName(error, "NotFoundError")) {
|
|
526
|
+
throw error;
|
|
527
|
+
}
|
|
528
|
+
throw new GeneralError(this.CLASS_NAME, "setFailed", undefined, error);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Get a specific document from an auditable item graph vertex.
|
|
533
|
+
* @param auditableItemGraphId The auditable item graph vertex id to get the document from.
|
|
534
|
+
* @param identifier The identifier of the document to get.
|
|
535
|
+
* @param options Additional options for the get operation.
|
|
536
|
+
* @param options.includeBlobStorageMetadata Flag to include the blob storage metadata for the document, defaults to false.
|
|
537
|
+
* @param options.includeBlobStorageData Flag to include the blob storage data for the document, defaults to false.
|
|
538
|
+
* @param options.includeAttestation Flag to include the attestation information for the document, defaults to false.
|
|
539
|
+
* @param options.includeRemoved Flag to include deleted documents, defaults to false.
|
|
540
|
+
* @param options.maxRevisionCount Max number of revisions to return, defaults to 0.
|
|
541
|
+
* @param revisionCursor The cursor to get the next chunk of revisions.
|
|
542
|
+
* @param userIdentity The identity to perform the auditable item graph operation with.
|
|
543
|
+
* @param nodeIdentity The node identity to use for vault operations.
|
|
544
|
+
* @returns The documents and revisions if requested, ordered by revision descending, cursor is set if there are more document revisions.
|
|
545
|
+
*/
|
|
546
|
+
async get(auditableItemGraphId, identifier, options, revisionCursor, userIdentity, nodeIdentity) {
|
|
547
|
+
Urn.guard(this.CLASS_NAME, "auditableItemGraphId", auditableItemGraphId);
|
|
548
|
+
Urn.guard(this.CLASS_NAME, "identifier", identifier);
|
|
549
|
+
try {
|
|
550
|
+
const includeBlobStorageMetadata = options?.includeBlobStorageMetadata ?? false;
|
|
551
|
+
const includeBlobStorageData = options?.includeBlobStorageData ?? false;
|
|
552
|
+
const includeAttestation = options?.includeAttestation ?? false;
|
|
553
|
+
const includeRemoved = options?.includeRemoved ?? false;
|
|
554
|
+
const revCursor = Math.max(Coerce.integer(revisionCursor) ?? 0, 0);
|
|
555
|
+
const maxRevisionCount = Math.max(Coerce.integer(options?.maxRevisionCount) ?? 0);
|
|
556
|
+
const documentIdParts = this.parseDocumentId(identifier);
|
|
557
|
+
const vertex = await this._auditableItemGraphComponent.get(auditableItemGraphId);
|
|
558
|
+
// Get all the docs from the AIG vertex
|
|
559
|
+
const vertexDocs = this.filterDocumentsFromVertex(vertex);
|
|
560
|
+
// Reduce the list to those with a matching id and code
|
|
561
|
+
const matchingDocIds = this.findMatchingDocs(vertexDocs, documentIdParts.documentId, documentIdParts.documentCode, includeRemoved);
|
|
562
|
+
// Populate the document and revisions with the options set
|
|
563
|
+
const document = await this.getDocumentAndRevisions(matchingDocIds, identifier, {
|
|
564
|
+
includeBlobStorageMetadata,
|
|
565
|
+
includeBlobStorageData,
|
|
566
|
+
includeAttestation,
|
|
567
|
+
includeRemoved,
|
|
568
|
+
maxRevisionCount
|
|
569
|
+
}, revCursor, userIdentity, nodeIdentity);
|
|
570
|
+
return JsonLdProcessor.compact(document, document["@context"]);
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
if (BaseError.someErrorName(error, "NotFoundError")) {
|
|
574
|
+
throw error;
|
|
575
|
+
}
|
|
576
|
+
throw new GeneralError(this.CLASS_NAME, "getFailed", undefined, error);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Remove a specific document from an auditable item graph vertex.
|
|
581
|
+
* The documents dateDeleted will be set, but can still be queried with the includeRemoved flag.
|
|
582
|
+
* @param auditableItemGraphId The auditable item graph vertex id to remove the document from.
|
|
583
|
+
* @param identifier The identifier of the document to remove.
|
|
584
|
+
* @param options Additional options for the remove operation.
|
|
585
|
+
* @param options.removeAllRevisions Flag to remove all revisions of the document, defaults to false.
|
|
586
|
+
* @param userIdentity The identity to perform the auditable item graph operation with.
|
|
587
|
+
* @param nodeIdentity The node identity to use for vault operations.
|
|
588
|
+
* @returns Nothing.
|
|
589
|
+
*/
|
|
590
|
+
async remove(auditableItemGraphId, identifier, options, userIdentity, nodeIdentity) {
|
|
591
|
+
Urn.guard(this.CLASS_NAME, "auditableItemGraphId", auditableItemGraphId);
|
|
592
|
+
Urn.guard(this.CLASS_NAME, "identifier", identifier);
|
|
593
|
+
try {
|
|
594
|
+
const documentIdParts = this.parseDocumentId(identifier);
|
|
595
|
+
const vertex = await this._auditableItemGraphComponent.get(auditableItemGraphId);
|
|
596
|
+
// Get all the docs from the AIG vertex
|
|
597
|
+
const vertexDocs = this.filterDocumentsFromVertex(vertex);
|
|
598
|
+
// Reduce the list to those with a matching id and code
|
|
599
|
+
const matchingDocIds = this.findMatchingDocs(vertexDocs, documentIdParts.documentId, documentIdParts.documentCode, false);
|
|
600
|
+
const removeAllRevisions = options?.removeAllRevisions ?? false;
|
|
601
|
+
const now = Date.now();
|
|
602
|
+
if (removeAllRevisions) {
|
|
603
|
+
for (const doc of matchingDocIds) {
|
|
604
|
+
doc.dateDeleted = new Date(now).toISOString();
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
else {
|
|
608
|
+
const matchingRevision = matchingDocIds.find(d => d.documentRevision === documentIdParts.documentRevision);
|
|
609
|
+
if (matchingRevision) {
|
|
610
|
+
matchingRevision.dateDeleted = new Date(now).toISOString();
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
613
|
+
throw new NotFoundError(this.CLASS_NAME, "documentRevisionNotFound", identifier);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
await this._auditableItemGraphComponent.update(vertex, userIdentity, nodeIdentity);
|
|
617
|
+
}
|
|
618
|
+
catch (error) {
|
|
619
|
+
if (BaseError.someErrorName(error, "NotFoundError")) {
|
|
620
|
+
throw error;
|
|
621
|
+
}
|
|
622
|
+
throw new GeneralError(this.CLASS_NAME, "removeFailed", undefined, error);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Query an auditable item graph vertex for documents.
|
|
627
|
+
* @param auditableItemGraphId The auditable item graph vertex to get the documents from.
|
|
628
|
+
* @param documentCodes The document codes to query for, if undefined gets all document codes.
|
|
629
|
+
* @param options Additional options for the query operation.
|
|
630
|
+
* @param options.includeMostRecentRevisions Include the most recent 5 revisions, use the individual get to retrieve more.
|
|
631
|
+
* @param options.includeRemoved Flag to include deleted documents, defaults to false.
|
|
632
|
+
* @param cursor The cursor to get the next chunk of documents.
|
|
633
|
+
* @param userIdentity The identity to perform the auditable item graph operation with.
|
|
634
|
+
* @param nodeIdentity The node identity to use for vault operations.
|
|
635
|
+
* @returns The most recent revisions of each document, cursor is set if there are more documents.
|
|
636
|
+
*/
|
|
637
|
+
async query(auditableItemGraphId, documentCodes, options, cursor, userIdentity, nodeIdentity) {
|
|
638
|
+
Urn.guard(this.CLASS_NAME, "auditableItemGraphId", auditableItemGraphId);
|
|
639
|
+
try {
|
|
640
|
+
const includeRemoved = options?.includeRemoved ?? false;
|
|
641
|
+
const includeMostRecentRevisions = options?.includeMostRecentRevisions ?? false;
|
|
642
|
+
const docCursor = Math.max(Coerce.integer(cursor) ?? 0, 0);
|
|
643
|
+
const vertex = await this._auditableItemGraphComponent.get(auditableItemGraphId);
|
|
644
|
+
// Get all the docs from the AIG vertex
|
|
645
|
+
const vertexDocs = this.filterDocumentsFromVertex(vertex);
|
|
646
|
+
let matchingDocIds = vertexDocs;
|
|
647
|
+
if (Is.arrayValue(documentCodes)) {
|
|
648
|
+
matchingDocIds = vertexDocs.filter(d => documentCodes.includes(d.documentCode));
|
|
649
|
+
}
|
|
650
|
+
const documentIdGroups = {};
|
|
651
|
+
let docGroupIds = [];
|
|
652
|
+
for (const doc of matchingDocIds) {
|
|
653
|
+
const docId = `${doc.documentId}:${doc.documentCode}`;
|
|
654
|
+
if (!docGroupIds.includes(docId)) {
|
|
655
|
+
docGroupIds.push(docId);
|
|
656
|
+
}
|
|
657
|
+
documentIdGroups[docId] ??= [];
|
|
658
|
+
documentIdGroups[docId].push(doc);
|
|
659
|
+
}
|
|
660
|
+
let nextDocCursor;
|
|
661
|
+
if (docGroupIds.length > docCursor + DocumentManagementService._DEFAULT_PAGE_SIZE) {
|
|
662
|
+
nextDocCursor = (docCursor + DocumentManagementService._DEFAULT_PAGE_SIZE).toString();
|
|
663
|
+
}
|
|
664
|
+
docGroupIds = docGroupIds.slice(docCursor, docCursor + DocumentManagementService._DEFAULT_PAGE_SIZE);
|
|
665
|
+
const finalDocs = [];
|
|
666
|
+
for (const docId of docGroupIds) {
|
|
667
|
+
finalDocs.push(await this.getDocumentAndRevisions(documentIdGroups[docId], docId, {
|
|
668
|
+
includeAttestation: false,
|
|
669
|
+
includeBlobStorageData: false,
|
|
670
|
+
includeBlobStorageMetadata: false,
|
|
671
|
+
includeRemoved,
|
|
672
|
+
maxRevisionCount: includeMostRecentRevisions ? 5 : 0
|
|
673
|
+
}, 0, userIdentity, nodeIdentity));
|
|
674
|
+
}
|
|
675
|
+
const docList = {
|
|
676
|
+
"@context": [
|
|
677
|
+
DocumentTypes.ContextRoot,
|
|
678
|
+
DocumentTypes.ContextRootCommon,
|
|
679
|
+
SchemaOrgTypes.ContextRoot
|
|
680
|
+
],
|
|
681
|
+
type: DocumentTypes.DocumentList,
|
|
682
|
+
documents: finalDocs,
|
|
683
|
+
cursor: nextDocCursor
|
|
684
|
+
};
|
|
685
|
+
return JsonLdProcessor.compact(docList, docList["@context"]);
|
|
686
|
+
}
|
|
687
|
+
catch (error) {
|
|
688
|
+
if (BaseError.someErrorName(error, "NotFoundError")) {
|
|
689
|
+
throw error;
|
|
690
|
+
}
|
|
691
|
+
throw new GeneralError(this.CLASS_NAME, "queryFailed", undefined, error);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Encode the document id.
|
|
696
|
+
* @param documentId The document identifier.
|
|
697
|
+
* @returns The encoded identifier.
|
|
698
|
+
* @internal
|
|
699
|
+
*/
|
|
700
|
+
encodeDocumentIdentifier(documentId, documentRevision) {
|
|
701
|
+
return `${documentId}:rev-${documentRevision}`;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Decode the document id.
|
|
705
|
+
* @param documentId The document identifier.
|
|
706
|
+
* @returns The decoded identifier.
|
|
707
|
+
* @internal
|
|
708
|
+
*/
|
|
709
|
+
decodeDocumentIdentifier(documentId) {
|
|
710
|
+
const parts = documentId.split(":");
|
|
711
|
+
const lastPart = parts[parts.length - 1];
|
|
712
|
+
let revision;
|
|
713
|
+
if (lastPart.startsWith("rev-")) {
|
|
714
|
+
revision = Number.parseInt(lastPart.slice(4), 10);
|
|
715
|
+
parts.pop();
|
|
716
|
+
}
|
|
717
|
+
return { documentId: parts.join(":"), documentRevision: revision };
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Create a full identifier for a document.
|
|
721
|
+
* @param documentCode The document code.
|
|
722
|
+
* @param documentId The document identifier.
|
|
723
|
+
* @param documentRevision The document revision.
|
|
724
|
+
* @returns The full identifier.
|
|
725
|
+
* @internal
|
|
726
|
+
*/
|
|
727
|
+
createIdentifier(documentCode, documentId, documentRevision) {
|
|
728
|
+
const docCode = this.parseDocumentCode(documentCode);
|
|
729
|
+
return `documents:${docCode}:${this.encodeDocumentIdentifier(documentId, documentRevision)}`;
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Parse the document identifier from the full identifier.
|
|
733
|
+
* @param identifier The full identifier to parse.
|
|
734
|
+
* @returns The document identifier.
|
|
735
|
+
* @internal
|
|
736
|
+
*/
|
|
737
|
+
parseDocumentId(identifier) {
|
|
738
|
+
const urn = Urn.fromValidString(identifier);
|
|
739
|
+
const remainingParts = urn.namespaceSpecificParts();
|
|
740
|
+
if (remainingParts.length < 2) {
|
|
741
|
+
throw new GeneralError(this.CLASS_NAME, "invalidDocumentId", { identifier });
|
|
742
|
+
}
|
|
743
|
+
const documentCode = `unece:DocumentCodeList#${remainingParts[0]}`;
|
|
744
|
+
const { documentId, documentRevision } = this.decodeDocumentIdentifier(urn.namespaceSpecific(1));
|
|
745
|
+
return { documentCode, documentId, documentRevision };
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Parse the document code from the full identifier.
|
|
749
|
+
* @param documentCode The document code to parse.
|
|
750
|
+
* @returns The document code.
|
|
751
|
+
* @internal
|
|
752
|
+
*/
|
|
753
|
+
parseDocumentCode(documentCode) {
|
|
754
|
+
// Document codes are in the format unece:DocumentCodeList#1, so we need to split the string to get the code.
|
|
755
|
+
const documentCodeParts = documentCode.split("#");
|
|
756
|
+
if (documentCodeParts.length !== 2) {
|
|
757
|
+
throw new GeneralError(this.CLASS_NAME, "invalidDocumentCode", { documentCode });
|
|
758
|
+
}
|
|
759
|
+
const docCode = Number.parseInt(documentCodeParts[1], 10);
|
|
760
|
+
if (!Is.number(docCode)) {
|
|
761
|
+
throw new GeneralError(this.CLASS_NAME, "invalidDocumentCode", { documentCode });
|
|
762
|
+
}
|
|
763
|
+
return docCode;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Get the documents from a vertex.
|
|
767
|
+
* @param vertex The vertex to get the documents from.
|
|
768
|
+
* @returns The documents.
|
|
769
|
+
* @internal
|
|
770
|
+
*/
|
|
771
|
+
filterDocumentsFromVertex(vertex) {
|
|
772
|
+
return (vertex.resources
|
|
773
|
+
?.filter(resource => ObjectHelper.extractProperty(resource.resourceObject, ["@type", "type"], false) ===
|
|
774
|
+
DocumentTypes.Document)
|
|
775
|
+
.map(resource => resource.resourceObject) ?? []);
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Find matching documents in the list of existing documents.
|
|
779
|
+
* @param documents The documents to search.
|
|
780
|
+
* @param documentId The document id.
|
|
781
|
+
* @param documentCode The document code.
|
|
782
|
+
* @param includeRemoved Include deleted documents.
|
|
783
|
+
* @returns The matching documents.
|
|
784
|
+
* @internal
|
|
785
|
+
*/
|
|
786
|
+
findMatchingDocs(documents, documentId, documentCode, includeRemoved) {
|
|
787
|
+
return documents
|
|
788
|
+
.filter(d => d.documentId === documentId &&
|
|
789
|
+
d.documentCode === documentCode &&
|
|
790
|
+
(includeRemoved || Is.empty(d.dateDeleted)))
|
|
791
|
+
.sort((a, b) => b.documentRevision - a.documentRevision);
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Generate a hash for the blob data.
|
|
795
|
+
* @param blob The blob data to hash.
|
|
796
|
+
* @returns The hash.
|
|
797
|
+
* @internal
|
|
798
|
+
*/
|
|
799
|
+
generateBlobHash(blob) {
|
|
800
|
+
return `sha256:${Converter.bytesToBase64(Sha256.sum256(blob))}`;
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Get the documents from the auditable item graph vertex.
|
|
804
|
+
* @param matchingDocIds The documents which match document type and id.
|
|
805
|
+
* @param identifier The full document identifier.
|
|
806
|
+
* @param options Additional options for the get operation.
|
|
807
|
+
* @param options.includeBlobStorageMetadata Flag to include the blob storage metadata for the document, defaults to false.
|
|
808
|
+
* @param options.includeBlobStorageData Flag to include the blob storage data for the document, defaults to false.
|
|
809
|
+
* @param options.includeAttestation Flag to include the attestation information for the document, defaults to false.
|
|
810
|
+
* @param options.includeRemoved Flag to include deleted documents, defaults to false.
|
|
811
|
+
* @param options.maxRevisionCount Max number of revisions to return, defaults to 0.
|
|
812
|
+
* @param revisionCursor The cursor to get the next chunk of revisions.
|
|
813
|
+
* @param userIdentity The identity to perform the auditable item graph operation with.
|
|
814
|
+
* @param nodeIdentity The node identity to use for vault operations.
|
|
815
|
+
* @returns The finalised list of documents.
|
|
816
|
+
* @internal
|
|
817
|
+
*/
|
|
818
|
+
async getDocumentAndRevisions(matchingDocIds, identifier, options, revisionCursor, userIdentity, nodeIdentity) {
|
|
819
|
+
const document = matchingDocIds.shift();
|
|
820
|
+
if (Is.empty(document)) {
|
|
821
|
+
throw new NotFoundError(this.CLASS_NAME, "documentRevisionNotFound", identifier);
|
|
822
|
+
}
|
|
823
|
+
let revisions;
|
|
824
|
+
let nextRevisionCursor;
|
|
825
|
+
if (options.maxRevisionCount > 0) {
|
|
826
|
+
revisions = matchingDocIds.slice(revisionCursor, revisionCursor + options.maxRevisionCount);
|
|
827
|
+
nextRevisionCursor =
|
|
828
|
+
matchingDocIds.length > revisionCursor + options.maxRevisionCount
|
|
829
|
+
? (revisionCursor + options.maxRevisionCount).toString()
|
|
830
|
+
: undefined;
|
|
831
|
+
}
|
|
832
|
+
if (options.includeBlobStorageMetadata || options.includeBlobStorageData) {
|
|
833
|
+
const blobEntry = await this._blobStorageComponent.get(document.blobStorageId, options.includeBlobStorageData, userIdentity, nodeIdentity);
|
|
834
|
+
document.blobStorageEntry = blobEntry;
|
|
835
|
+
document["@context"].push(BlobStorageTypes.ContextRoot);
|
|
836
|
+
}
|
|
837
|
+
if (options.includeAttestation && Is.stringValue(document.attestationId)) {
|
|
838
|
+
const attestationInformation = await this._attestationComponent.get(document.attestationId);
|
|
839
|
+
document.attestationInformation = attestationInformation;
|
|
840
|
+
document["@context"].push(AttestationTypes.ContextRoot);
|
|
841
|
+
}
|
|
842
|
+
document.revisions = Is.arrayValue(revisions) ? revisions : undefined;
|
|
843
|
+
document.revisionCursor = nextRevisionCursor;
|
|
844
|
+
return document;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
const restEntryPoints = [
|
|
849
|
+
{
|
|
850
|
+
name: "documents",
|
|
851
|
+
defaultBaseRoute: "documents",
|
|
852
|
+
tags: tagsDocumentManagement,
|
|
853
|
+
generateRoutes: generateRestRoutesDocumentManagement
|
|
854
|
+
}
|
|
855
|
+
];
|
|
856
|
+
|
|
857
|
+
export { DocumentManagementService, documentManagementGet, documentManagementQuery, documentManagementRemove, documentManagementSet, generateRestRoutesDocumentManagement, restEntryPoints, tagsDocumentManagement };
|