@twin.org/blob-storage-service 0.0.1-next.8 → 0.0.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/dist/cjs/index.cjs +643 -114
- package/dist/esm/index.mjs +645 -117
- package/dist/types/blobStorageRoutes.d.ts +17 -3
- package/dist/types/blobStorageService.d.ts +55 -31
- package/dist/types/entities/blobStorageEntry.d.ts +55 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/models/IBlobStorageServiceConfig.d.ts +9 -2
- package/dist/types/models/IBlobStorageServiceConstructorOptions.d.ts +19 -0
- package/dist/types/restEntryPoints.d.ts +5 -0
- package/docs/changelog.md +161 -1
- package/docs/open-api/spec.json +1230 -1853
- package/docs/reference/classes/BlobStorageEntry.md +109 -0
- package/docs/reference/classes/BlobStorageService.md +188 -49
- package/docs/reference/functions/blobStorageCreate.md +9 -3
- package/docs/reference/functions/blobStorageGet.md +9 -3
- package/docs/reference/functions/blobStorageGetContent.md +9 -3
- package/docs/reference/functions/blobStorageList.md +31 -0
- package/docs/reference/functions/blobStorageRemove.md +9 -3
- package/docs/reference/functions/blobStorageUpdate.md +9 -3
- package/docs/reference/functions/generateRestRoutesBlobStorage.md +24 -4
- package/docs/reference/index.md +3 -1
- package/docs/reference/interfaces/IBlobStorageServiceConfig.md +17 -7
- package/docs/reference/interfaces/IBlobStorageServiceConstructorOptions.md +33 -0
- package/docs/reference/variables/restEntryPoints.md +4 -0
- package/locales/en.json +2 -1
- package/package.json +15 -13
- package/dist/types/entities/blobMetadata.d.ts +0 -22
- package/docs/reference/classes/BlobMetadata.md +0 -45
package/dist/esm/index.mjs
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HttpParameterHelper } from '@twin.org/api-models';
|
|
2
|
+
import { BlobStorageTypes, BlobStorageContexts, BlobStorageCompressionType, BlobStorageConnectorFactory } from '@twin.org/blob-storage-models';
|
|
3
|
+
import { StringHelper, Guards, ComponentFactory, Coerce, Is, Converter, GeneralError, Validation, Compression, ObjectHelper, Urn, NotFoundError } from '@twin.org/core';
|
|
4
|
+
import { SchemaOrgContexts, SchemaOrgTypes, SchemaOrgDataTypes } from '@twin.org/standards-schema-org';
|
|
2
5
|
import { HttpStatusCode, HeaderTypes, MimeTypes, MimeTypeHelper } from '@twin.org/web';
|
|
3
|
-
import {
|
|
4
|
-
import { JsonLdHelper } from '@twin.org/data-json-ld';
|
|
6
|
+
import { Sha256 } from '@twin.org/crypto';
|
|
7
|
+
import { JsonLdHelper, JsonLdProcessor } from '@twin.org/data-json-ld';
|
|
8
|
+
import { ComparisonOperator, LogicalOperator, SortDirection, EntitySchemaHelper, property, entity, EntitySchemaFactory } from '@twin.org/entity';
|
|
5
9
|
import { EntityStorageConnectorFactory } from '@twin.org/entity-storage-models';
|
|
6
10
|
import { VaultConnectorFactory, VaultEncryptionType } from '@twin.org/vault-models';
|
|
7
|
-
import { property, entity, EntitySchemaFactory, EntitySchemaHelper } from '@twin.org/entity';
|
|
8
11
|
|
|
12
|
+
// Copyright 2024 IOTA Stiftung.
|
|
13
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
9
14
|
/**
|
|
10
15
|
* The source used when communicating about these routes.
|
|
11
16
|
*/
|
|
@@ -23,13 +28,19 @@ const tagsBlobStorage = [
|
|
|
23
28
|
* The REST routes for blob storage.
|
|
24
29
|
* @param baseRouteName Prefix to prepend to the paths.
|
|
25
30
|
* @param componentName The name of the component to use in the routes stored in the ComponentFactory.
|
|
31
|
+
* @param options Additional options for the routes.
|
|
32
|
+
* @param options.typeName Optional type name to use in the routes, defaults to Blob Storage.
|
|
33
|
+
* @param options.tagName Optional name to use in OpenAPI spec for tag.
|
|
26
34
|
* @returns The generated routes.
|
|
27
35
|
*/
|
|
28
|
-
function generateRestRoutesBlobStorage(baseRouteName, componentName) {
|
|
36
|
+
function generateRestRoutesBlobStorage(baseRouteName, componentName, options) {
|
|
37
|
+
const typeName = options?.typeName ?? "Blob Storage";
|
|
38
|
+
const lowerName = typeName.toLowerCase();
|
|
39
|
+
const camelTypeName = StringHelper.camelCase(typeName);
|
|
29
40
|
const blobStorageCreateRoute = {
|
|
30
|
-
operationId:
|
|
31
|
-
summary:
|
|
32
|
-
tag: tagsBlobStorage[0].name,
|
|
41
|
+
operationId: `${camelTypeName}Create`,
|
|
42
|
+
summary: `Create an entry in ${lowerName}`,
|
|
43
|
+
tag: options?.tagName ?? tagsBlobStorage[0].name,
|
|
33
44
|
method: "POST",
|
|
34
45
|
path: `${baseRouteName}/`,
|
|
35
46
|
handler: async (httpRequestContext, request) => blobStorageCreate(httpRequestContext, componentName, request),
|
|
@@ -37,12 +48,12 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName) {
|
|
|
37
48
|
type: "IBlobStorageCreateRequest",
|
|
38
49
|
examples: [
|
|
39
50
|
{
|
|
40
|
-
id:
|
|
51
|
+
id: `${camelTypeName}CreateRequestExample`,
|
|
41
52
|
request: {
|
|
42
53
|
body: {
|
|
43
54
|
blob: "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==",
|
|
44
55
|
metadata: {
|
|
45
|
-
"@context": "
|
|
56
|
+
"@context": "https://schema.org",
|
|
46
57
|
"@type": "DigitalDocument",
|
|
47
58
|
name: "myfile.pdf"
|
|
48
59
|
}
|
|
@@ -56,7 +67,7 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName) {
|
|
|
56
67
|
type: "ICreatedResponse",
|
|
57
68
|
examples: [
|
|
58
69
|
{
|
|
59
|
-
id:
|
|
70
|
+
id: `${camelTypeName}CreateResponseExample`,
|
|
60
71
|
response: {
|
|
61
72
|
statusCode: HttpStatusCode.created,
|
|
62
73
|
headers: {
|
|
@@ -69,9 +80,9 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName) {
|
|
|
69
80
|
]
|
|
70
81
|
};
|
|
71
82
|
const blobStorageGetRoute = {
|
|
72
|
-
operationId:
|
|
73
|
-
summary:
|
|
74
|
-
tag: tagsBlobStorage[0].name,
|
|
83
|
+
operationId: `${camelTypeName}Get`,
|
|
84
|
+
summary: `Get the metadata for an item from ${lowerName}`,
|
|
85
|
+
tag: options?.tagName ?? tagsBlobStorage[0].name,
|
|
75
86
|
method: "GET",
|
|
76
87
|
path: `${baseRouteName}/:id`,
|
|
77
88
|
handler: async (httpRequestContext, request) => blobStorageGet(httpRequestContext, componentName, request),
|
|
@@ -79,13 +90,13 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName) {
|
|
|
79
90
|
type: "IBlobStorageGetRequest",
|
|
80
91
|
examples: [
|
|
81
92
|
{
|
|
82
|
-
id:
|
|
93
|
+
id: `${camelTypeName}GetRequestExample`,
|
|
83
94
|
request: {
|
|
84
95
|
pathParams: {
|
|
85
96
|
id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70"
|
|
86
97
|
},
|
|
87
98
|
query: {
|
|
88
|
-
includeContent: true
|
|
99
|
+
includeContent: "true"
|
|
89
100
|
}
|
|
90
101
|
}
|
|
91
102
|
}
|
|
@@ -96,11 +107,54 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName) {
|
|
|
96
107
|
type: "IBlobStorageGetResponse",
|
|
97
108
|
examples: [
|
|
98
109
|
{
|
|
99
|
-
id:
|
|
110
|
+
id: `${camelTypeName}GetResponseExample`,
|
|
100
111
|
response: {
|
|
101
112
|
body: {
|
|
113
|
+
"@context": [
|
|
114
|
+
BlobStorageContexts.ContextRoot,
|
|
115
|
+
BlobStorageContexts.ContextRootCommon,
|
|
116
|
+
SchemaOrgContexts.ContextRoot
|
|
117
|
+
],
|
|
118
|
+
type: BlobStorageTypes.Entry,
|
|
119
|
+
id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
|
|
120
|
+
dateCreated: "2024-01-01T00:00:00Z",
|
|
121
|
+
encodingFormat: MimeTypes.Pdf,
|
|
122
|
+
blobSize: 42,
|
|
123
|
+
blobHash: "sha256:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
|
|
124
|
+
fileExtension: "pdf",
|
|
102
125
|
metadata: {
|
|
103
|
-
"@context": "
|
|
126
|
+
"@context": "https://schema.org",
|
|
127
|
+
"@type": "DigitalDocument",
|
|
128
|
+
name: "myfile.pdf"
|
|
129
|
+
},
|
|
130
|
+
blob: "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=="
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
type: "IBlobStorageGetResponse",
|
|
138
|
+
mimeType: MimeTypes.JsonLd,
|
|
139
|
+
examples: [
|
|
140
|
+
{
|
|
141
|
+
id: `${camelTypeName}GetResponseJsonLdExample`,
|
|
142
|
+
response: {
|
|
143
|
+
body: {
|
|
144
|
+
"@context": [
|
|
145
|
+
BlobStorageContexts.ContextRoot,
|
|
146
|
+
BlobStorageContexts.ContextRootCommon,
|
|
147
|
+
SchemaOrgContexts.ContextRoot
|
|
148
|
+
],
|
|
149
|
+
type: BlobStorageTypes.Entry,
|
|
150
|
+
id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
|
|
151
|
+
dateCreated: "2024-01-01T00:00:00Z",
|
|
152
|
+
encodingFormat: MimeTypes.Pdf,
|
|
153
|
+
blobSize: 42,
|
|
154
|
+
blobHash: "sha256:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
|
|
155
|
+
fileExtension: "pdf",
|
|
156
|
+
metadata: {
|
|
157
|
+
"@context": "https://schema.org",
|
|
104
158
|
"@type": "DigitalDocument",
|
|
105
159
|
name: "myfile.pdf"
|
|
106
160
|
},
|
|
@@ -116,9 +170,9 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName) {
|
|
|
116
170
|
]
|
|
117
171
|
};
|
|
118
172
|
const blobStorageGetContentRoute = {
|
|
119
|
-
operationId:
|
|
120
|
-
summary:
|
|
121
|
-
tag: tagsBlobStorage[0].name,
|
|
173
|
+
operationId: `${camelTypeName}GetContent`,
|
|
174
|
+
summary: `Get the content for an item in ${lowerName}`,
|
|
175
|
+
tag: options?.tagName ?? tagsBlobStorage[0].name,
|
|
122
176
|
method: "GET",
|
|
123
177
|
path: `${baseRouteName}/:id/content`,
|
|
124
178
|
handler: async (httpRequestContext, request) => blobStorageGetContent(httpRequestContext, componentName, request),
|
|
@@ -126,13 +180,13 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName) {
|
|
|
126
180
|
type: "IBlobStorageGetRequest",
|
|
127
181
|
examples: [
|
|
128
182
|
{
|
|
129
|
-
id:
|
|
183
|
+
id: `${camelTypeName}GetContentRequestExample`,
|
|
130
184
|
request: {
|
|
131
185
|
pathParams: {
|
|
132
186
|
id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70"
|
|
133
187
|
},
|
|
134
188
|
query: {
|
|
135
|
-
download: true,
|
|
189
|
+
download: "true",
|
|
136
190
|
filename: "my-file.pdf"
|
|
137
191
|
}
|
|
138
192
|
}
|
|
@@ -145,8 +199,8 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName) {
|
|
|
145
199
|
mimeType: MimeTypes.OctetStream,
|
|
146
200
|
examples: [
|
|
147
201
|
{
|
|
148
|
-
id:
|
|
149
|
-
description: `The content of the blob, which will be a specific mime type if one can be detected from the content (or set as
|
|
202
|
+
id: `${camelTypeName}GetContentResponseExample`,
|
|
203
|
+
description: `The content of the blob, which will be a specific mime type if one can be detected from the content (or set as encodingFormat in the entry), or defaults to ${MimeTypes.OctetStream}.`,
|
|
150
204
|
response: {
|
|
151
205
|
body: new Uint8Array()
|
|
152
206
|
}
|
|
@@ -159,9 +213,9 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName) {
|
|
|
159
213
|
]
|
|
160
214
|
};
|
|
161
215
|
const blobStorageUpdateRoute = {
|
|
162
|
-
operationId:
|
|
163
|
-
summary:
|
|
164
|
-
tag: tagsBlobStorage[0].name,
|
|
216
|
+
operationId: `${camelTypeName}Update`,
|
|
217
|
+
summary: `Update the metadata for an item in ${lowerName}`,
|
|
218
|
+
tag: options?.tagName ?? tagsBlobStorage[0].name,
|
|
165
219
|
method: "PUT",
|
|
166
220
|
path: `${baseRouteName}/:id`,
|
|
167
221
|
handler: async (httpRequestContext, request) => blobStorageUpdate(httpRequestContext, componentName, request),
|
|
@@ -169,14 +223,14 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName) {
|
|
|
169
223
|
type: "IBlobStorageUpdateRequest",
|
|
170
224
|
examples: [
|
|
171
225
|
{
|
|
172
|
-
id:
|
|
226
|
+
id: `${camelTypeName}UpdateRequestExample`,
|
|
173
227
|
request: {
|
|
174
228
|
pathParams: {
|
|
175
229
|
id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70"
|
|
176
230
|
},
|
|
177
231
|
body: {
|
|
178
232
|
metadata: {
|
|
179
|
-
"@context": "
|
|
233
|
+
"@context": "https://schema.org",
|
|
180
234
|
"@type": "DigitalDocument",
|
|
181
235
|
name: "myfile.pdf"
|
|
182
236
|
}
|
|
@@ -192,9 +246,9 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName) {
|
|
|
192
246
|
]
|
|
193
247
|
};
|
|
194
248
|
const blobStorageRemoveRoute = {
|
|
195
|
-
operationId:
|
|
196
|
-
summary:
|
|
197
|
-
tag: tagsBlobStorage[0].name,
|
|
249
|
+
operationId: `${camelTypeName}Remove`,
|
|
250
|
+
summary: `Remove an item from ${lowerName}`,
|
|
251
|
+
tag: options?.tagName ?? tagsBlobStorage[0].name,
|
|
198
252
|
method: "DELETE",
|
|
199
253
|
path: `${baseRouteName}/:id`,
|
|
200
254
|
handler: async (httpRequestContext, request) => blobStorageRemove(httpRequestContext, componentName, request),
|
|
@@ -202,7 +256,7 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName) {
|
|
|
202
256
|
type: "IBlobStorageRemoveRequest",
|
|
203
257
|
examples: [
|
|
204
258
|
{
|
|
205
|
-
id:
|
|
259
|
+
id: `${camelTypeName}RemoveRequestExample`,
|
|
206
260
|
request: {
|
|
207
261
|
pathParams: {
|
|
208
262
|
id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70"
|
|
@@ -220,12 +274,116 @@ function generateRestRoutesBlobStorage(baseRouteName, componentName) {
|
|
|
220
274
|
}
|
|
221
275
|
]
|
|
222
276
|
};
|
|
277
|
+
const blobStorageListRoute = {
|
|
278
|
+
operationId: `${camelTypeName}Query`,
|
|
279
|
+
summary: `Query the items from ${lowerName}`,
|
|
280
|
+
tag: options?.tagName ?? tagsBlobStorage[0].name,
|
|
281
|
+
method: "GET",
|
|
282
|
+
path: `${baseRouteName}/`,
|
|
283
|
+
handler: async (httpRequestContext, request) => blobStorageList(httpRequestContext, componentName, request),
|
|
284
|
+
requestType: {
|
|
285
|
+
type: "IBlobStorageListRequest",
|
|
286
|
+
examples: [
|
|
287
|
+
{
|
|
288
|
+
id: `${camelTypeName}ListRequestExample`,
|
|
289
|
+
request: {}
|
|
290
|
+
}
|
|
291
|
+
]
|
|
292
|
+
},
|
|
293
|
+
responseType: [
|
|
294
|
+
{
|
|
295
|
+
type: "IBlobStorageListResponse",
|
|
296
|
+
examples: [
|
|
297
|
+
{
|
|
298
|
+
id: `${camelTypeName}ListResponseExample`,
|
|
299
|
+
response: {
|
|
300
|
+
body: {
|
|
301
|
+
"@context": [
|
|
302
|
+
SchemaOrgContexts.ContextRoot,
|
|
303
|
+
BlobStorageContexts.ContextRoot,
|
|
304
|
+
BlobStorageContexts.ContextRootCommon
|
|
305
|
+
],
|
|
306
|
+
type: SchemaOrgTypes.ItemList,
|
|
307
|
+
[SchemaOrgTypes.ItemListElement]: [
|
|
308
|
+
{
|
|
309
|
+
"@context": [
|
|
310
|
+
BlobStorageContexts.ContextRoot,
|
|
311
|
+
BlobStorageContexts.ContextRootCommon,
|
|
312
|
+
SchemaOrgContexts.ContextRoot
|
|
313
|
+
],
|
|
314
|
+
type: BlobStorageTypes.Entry,
|
|
315
|
+
id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
|
|
316
|
+
dateCreated: "2024-01-01T00:00:00Z",
|
|
317
|
+
encodingFormat: MimeTypes.Pdf,
|
|
318
|
+
blobSize: 42,
|
|
319
|
+
blobHash: "sha256:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
|
|
320
|
+
fileExtension: "pdf",
|
|
321
|
+
metadata: {
|
|
322
|
+
"@context": "https://schema.org",
|
|
323
|
+
"@type": "DigitalDocument",
|
|
324
|
+
name: "myfile.pdf"
|
|
325
|
+
},
|
|
326
|
+
blob: "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=="
|
|
327
|
+
}
|
|
328
|
+
]
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
]
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
type: "IBlobStorageListResponse",
|
|
336
|
+
mimeType: MimeTypes.JsonLd,
|
|
337
|
+
examples: [
|
|
338
|
+
{
|
|
339
|
+
id: `${camelTypeName}ListResponseJsonLdExample`,
|
|
340
|
+
response: {
|
|
341
|
+
body: {
|
|
342
|
+
"@context": [
|
|
343
|
+
SchemaOrgContexts.ContextRoot,
|
|
344
|
+
BlobStorageContexts.ContextRoot,
|
|
345
|
+
BlobStorageContexts.ContextRootCommon
|
|
346
|
+
],
|
|
347
|
+
type: SchemaOrgTypes.ItemList,
|
|
348
|
+
[SchemaOrgTypes.ItemListElement]: [
|
|
349
|
+
{
|
|
350
|
+
"@context": [
|
|
351
|
+
BlobStorageContexts.ContextRoot,
|
|
352
|
+
BlobStorageContexts.ContextRootCommon,
|
|
353
|
+
SchemaOrgContexts.ContextRoot
|
|
354
|
+
],
|
|
355
|
+
type: BlobStorageTypes.Entry,
|
|
356
|
+
id: "blob-memory:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
|
|
357
|
+
dateCreated: "2024-01-01T00:00:00Z",
|
|
358
|
+
encodingFormat: MimeTypes.Pdf,
|
|
359
|
+
blobSize: 42,
|
|
360
|
+
blobHash: "sha256:c57d94b088f4c6d2cb32ded014813d0c786aa00134c8ee22f84b1e2545602a70",
|
|
361
|
+
fileExtension: "pdf",
|
|
362
|
+
metadata: {
|
|
363
|
+
"@context": "https://schema.org",
|
|
364
|
+
"@type": "DigitalDocument",
|
|
365
|
+
name: "myfile.pdf"
|
|
366
|
+
},
|
|
367
|
+
blob: "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=="
|
|
368
|
+
}
|
|
369
|
+
]
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
]
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
type: "INotFoundResponse"
|
|
377
|
+
}
|
|
378
|
+
]
|
|
379
|
+
};
|
|
223
380
|
return [
|
|
224
381
|
blobStorageCreateRoute,
|
|
225
382
|
blobStorageGetRoute,
|
|
226
383
|
blobStorageGetContentRoute,
|
|
227
384
|
blobStorageUpdateRoute,
|
|
228
|
-
blobStorageRemoveRoute
|
|
385
|
+
blobStorageRemoveRoute,
|
|
386
|
+
blobStorageListRoute
|
|
229
387
|
];
|
|
230
388
|
}
|
|
231
389
|
/**
|
|
@@ -240,7 +398,11 @@ async function blobStorageCreate(httpRequestContext, componentName, request) {
|
|
|
240
398
|
Guards.object(ROUTES_SOURCE, "request.body", request.body);
|
|
241
399
|
Guards.stringBase64(ROUTES_SOURCE, "request.body.blob", request.body.blob);
|
|
242
400
|
const component = ComponentFactory.get(componentName);
|
|
243
|
-
const id = await component.create(request.body.blob, request.body.
|
|
401
|
+
const id = await component.create(request.body.blob, request.body.encodingFormat, request.body.fileExtension, request.body.metadata, {
|
|
402
|
+
disableEncryption: request.body.disableEncryption,
|
|
403
|
+
overrideVaultKeyId: request.body.overrideVaultKeyId,
|
|
404
|
+
namespace: request.body.namespace
|
|
405
|
+
}, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
|
|
244
406
|
return {
|
|
245
407
|
statusCode: HttpStatusCode.created,
|
|
246
408
|
headers: {
|
|
@@ -259,9 +421,17 @@ async function blobStorageGet(httpRequestContext, componentName, request) {
|
|
|
259
421
|
Guards.object(ROUTES_SOURCE, "request", request);
|
|
260
422
|
Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
|
|
261
423
|
Guards.stringValue(ROUTES_SOURCE, "request.pathParams.id", request.pathParams.id);
|
|
424
|
+
const mimeType = request.headers?.[HeaderTypes.Accept] === MimeTypes.JsonLd ? "jsonld" : "json";
|
|
262
425
|
const component = ComponentFactory.get(componentName);
|
|
263
|
-
const result = await component.get(request.pathParams.id,
|
|
426
|
+
const result = await component.get(request.pathParams.id, {
|
|
427
|
+
includeContent: Coerce.boolean(request.query?.includeContent),
|
|
428
|
+
decompress: Coerce.boolean(request.query?.decompress),
|
|
429
|
+
overrideVaultKeyId: request.query?.overrideVaultKeyId
|
|
430
|
+
}, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
|
|
264
431
|
return {
|
|
432
|
+
headers: {
|
|
433
|
+
[HeaderTypes.ContentType]: mimeType === "json" ? MimeTypes.Json : MimeTypes.JsonLd
|
|
434
|
+
},
|
|
265
435
|
body: result
|
|
266
436
|
};
|
|
267
437
|
}
|
|
@@ -277,18 +447,34 @@ async function blobStorageGetContent(httpRequestContext, componentName, request)
|
|
|
277
447
|
Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
|
|
278
448
|
Guards.stringValue(ROUTES_SOURCE, "request.pathParams.id", request.pathParams.id);
|
|
279
449
|
const component = ComponentFactory.get(componentName);
|
|
280
|
-
const
|
|
281
|
-
const
|
|
450
|
+
const decompress = Coerce.boolean(request.query?.decompress);
|
|
451
|
+
const download = Coerce.boolean(request.query?.download) ?? false;
|
|
452
|
+
const result = await component.get(request.pathParams.id, {
|
|
453
|
+
includeContent: true,
|
|
454
|
+
decompress,
|
|
455
|
+
overrideVaultKeyId: request.query?.overrideVaultKeyId
|
|
456
|
+
}, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
|
|
457
|
+
const encodingFormat = result?.encodingFormat ?? MimeTypes.OctetStream;
|
|
458
|
+
let compressedEncodingFormat;
|
|
459
|
+
let compressedExtension = "";
|
|
460
|
+
// If the entry is compressed and we are not decompressing
|
|
461
|
+
// we need to override the encoding format to the compressed type
|
|
462
|
+
// and append an additional extension to the filename.
|
|
463
|
+
if (result.compression && !decompress) {
|
|
464
|
+
compressedEncodingFormat =
|
|
465
|
+
result.compression === BlobStorageCompressionType.Gzip ? MimeTypes.Gzip : MimeTypes.Zlib;
|
|
466
|
+
compressedExtension = `.${MimeTypeHelper.defaultExtension(compressedEncodingFormat)}`;
|
|
467
|
+
}
|
|
282
468
|
let filename = request.query?.filename;
|
|
283
469
|
if (!Is.stringValue(filename)) {
|
|
284
|
-
filename = `file.${result.
|
|
470
|
+
filename = `file.${result.fileExtension ?? MimeTypeHelper.defaultExtension(encodingFormat)}${compressedExtension}`;
|
|
285
471
|
}
|
|
286
472
|
return {
|
|
287
473
|
body: Is.stringBase64(result.blob) ? Converter.base64ToBytes(result.blob) : new Uint8Array(),
|
|
288
474
|
attachment: {
|
|
289
|
-
mimeType,
|
|
475
|
+
mimeType: compressedEncodingFormat ?? encodingFormat,
|
|
290
476
|
filename,
|
|
291
|
-
inline: !
|
|
477
|
+
inline: !download
|
|
292
478
|
}
|
|
293
479
|
};
|
|
294
480
|
}
|
|
@@ -304,7 +490,7 @@ async function blobStorageUpdate(httpRequestContext, componentName, request) {
|
|
|
304
490
|
Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
|
|
305
491
|
Guards.stringValue(ROUTES_SOURCE, "request.pathParams.id", request.pathParams.id);
|
|
306
492
|
const component = ComponentFactory.get(componentName);
|
|
307
|
-
await component.update(request.pathParams.id, request.body.
|
|
493
|
+
await component.update(request.pathParams.id, request.body.encodingFormat, request.body.fileExtension, request.body.metadata, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
|
|
308
494
|
return {
|
|
309
495
|
statusCode: HttpStatusCode.noContent
|
|
310
496
|
};
|
|
@@ -321,11 +507,30 @@ async function blobStorageRemove(httpRequestContext, componentName, request) {
|
|
|
321
507
|
Guards.object(ROUTES_SOURCE, "request.pathParams", request.pathParams);
|
|
322
508
|
Guards.stringValue(ROUTES_SOURCE, "request.pathParams.id", request.pathParams.id);
|
|
323
509
|
const component = ComponentFactory.get(componentName);
|
|
324
|
-
await component.remove(request.pathParams.id);
|
|
510
|
+
await component.remove(request.pathParams.id, httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
|
|
325
511
|
return {
|
|
326
512
|
statusCode: HttpStatusCode.noContent
|
|
327
513
|
};
|
|
328
514
|
}
|
|
515
|
+
/**
|
|
516
|
+
* List the entries from blob storage.
|
|
517
|
+
* @param httpRequestContext The request context for the API.
|
|
518
|
+
* @param componentName The name of the component to use in the routes.
|
|
519
|
+
* @param request The request.
|
|
520
|
+
* @returns The response object with additional http response properties.
|
|
521
|
+
*/
|
|
522
|
+
async function blobStorageList(httpRequestContext, componentName, request) {
|
|
523
|
+
Guards.object(ROUTES_SOURCE, "request", request);
|
|
524
|
+
const mimeType = request.headers?.[HeaderTypes.Accept] === MimeTypes.JsonLd ? "jsonld" : "json";
|
|
525
|
+
const component = ComponentFactory.get(componentName);
|
|
526
|
+
const result = await component.query(HttpParameterHelper.objectFromString(request.query?.conditions), request.query?.orderBy, request.query?.orderByDirection, request.query?.cursor, Coerce.number(request.query?.pageSize), httpRequestContext.userIdentity, httpRequestContext.nodeIdentity);
|
|
527
|
+
return {
|
|
528
|
+
headers: {
|
|
529
|
+
[HeaderTypes.ContentType]: mimeType === "json" ? MimeTypes.Json : MimeTypes.JsonLd
|
|
530
|
+
},
|
|
531
|
+
body: result
|
|
532
|
+
};
|
|
533
|
+
}
|
|
329
534
|
|
|
330
535
|
// Copyright 2024 IOTA Stiftung.
|
|
331
536
|
// SPDX-License-Identifier: Apache-2.0.
|
|
@@ -351,7 +556,7 @@ class BlobStorageService {
|
|
|
351
556
|
* The storage connector for the metadata.
|
|
352
557
|
* @internal
|
|
353
558
|
*/
|
|
354
|
-
|
|
559
|
+
_entryEntityStorage;
|
|
355
560
|
/**
|
|
356
561
|
* The vault connector for the encryption, can be undefined if no encryption required.
|
|
357
562
|
* @internal
|
|
@@ -362,71 +567,116 @@ class BlobStorageService {
|
|
|
362
567
|
* @internal
|
|
363
568
|
*/
|
|
364
569
|
_vaultKeyId;
|
|
570
|
+
/**
|
|
571
|
+
* Include the node identity when performing storage operations, defaults to true.
|
|
572
|
+
* @internal
|
|
573
|
+
*/
|
|
574
|
+
_includeNodeIdentity;
|
|
575
|
+
/**
|
|
576
|
+
* Include the user identity when performing storage operations, defaults to true.
|
|
577
|
+
* @internal
|
|
578
|
+
*/
|
|
579
|
+
_includeUserIdentity;
|
|
365
580
|
/**
|
|
366
581
|
* Create a new instance of BlobStorageService.
|
|
367
|
-
* @param options The
|
|
368
|
-
* @param options.metadataEntityStorageType The type of the storage connector for the metadata, defaults to "blob-metadata".
|
|
369
|
-
* @param options.vaultConnectorType The type of the vault connector for encryption, if undefined no encryption will be performed.
|
|
370
|
-
* @param options.config The configuration for the service.
|
|
582
|
+
* @param options The options for the service.
|
|
371
583
|
*/
|
|
372
584
|
constructor(options) {
|
|
373
585
|
const names = BlobStorageConnectorFactory.names();
|
|
374
586
|
if (names.length === 0) {
|
|
375
587
|
throw new GeneralError(this.CLASS_NAME, "noConnectors");
|
|
376
588
|
}
|
|
377
|
-
this.
|
|
589
|
+
this._entryEntityStorage = EntityStorageConnectorFactory.get(options?.entryEntityStorageType ?? "blob-storage-entry");
|
|
378
590
|
if (Is.stringValue(options?.vaultConnectorType)) {
|
|
379
|
-
this._vaultConnector = VaultConnectorFactory.
|
|
591
|
+
this._vaultConnector = VaultConnectorFactory.get(options.vaultConnectorType);
|
|
380
592
|
}
|
|
381
593
|
this._defaultNamespace = options?.config?.defaultNamespace ?? names[0];
|
|
382
|
-
this._vaultKeyId = options?.config?.vaultKeyId
|
|
594
|
+
this._vaultKeyId = options?.config?.vaultKeyId;
|
|
595
|
+
this._includeNodeIdentity = options?.config?.includeNodeIdentity ?? true;
|
|
596
|
+
this._includeUserIdentity = options?.config?.includeUserIdentity ?? true;
|
|
597
|
+
SchemaOrgDataTypes.registerRedirects();
|
|
383
598
|
}
|
|
384
599
|
/**
|
|
385
600
|
* Create the blob with some metadata.
|
|
386
601
|
* @param blob The data for the blob in base64 format.
|
|
387
|
-
* @param
|
|
388
|
-
* @param
|
|
602
|
+
* @param encodingFormat Mime type for the blob, will be detected if left undefined.
|
|
603
|
+
* @param fileExtension Extension for the blob, will be detected if left undefined.
|
|
389
604
|
* @param metadata Data for the custom metadata as JSON-LD.
|
|
390
|
-
* @param
|
|
391
|
-
* @param
|
|
605
|
+
* @param options Optional options for the creation of the blob.
|
|
606
|
+
* @param options.disableEncryption Disables encryption if enabled by default.
|
|
607
|
+
* @param options.overrideVaultKeyId Use a different vault key id for encryption, if not provided the default vault key id will be used.
|
|
608
|
+
* @param options.compress Optional compression type to use for the blob, defaults to no compression.*
|
|
609
|
+
* @param options.namespace The namespace to use for storing, defaults to component configured namespace.
|
|
610
|
+
* @param userIdentity The user identity to use with storage operations.
|
|
611
|
+
* @param nodeIdentity The node identity to use with storage operations.
|
|
392
612
|
* @returns The id of the stored blob in urn format.
|
|
393
613
|
*/
|
|
394
|
-
async create(blob,
|
|
614
|
+
async create(blob, encodingFormat, fileExtension, metadata, options, userIdentity, nodeIdentity) {
|
|
395
615
|
Guards.stringBase64(this.CLASS_NAME, "blob", blob);
|
|
396
|
-
if (this.
|
|
616
|
+
if (this._includeUserIdentity) {
|
|
617
|
+
Guards.stringValue(this.CLASS_NAME, "userIdentity", userIdentity);
|
|
618
|
+
}
|
|
619
|
+
const disableEncryption = options?.disableEncryption ?? false;
|
|
620
|
+
const vaultKeyId = options?.overrideVaultKeyId ?? this._vaultKeyId;
|
|
621
|
+
const encryptionEnabled = !disableEncryption && Is.stringValue(vaultKeyId);
|
|
622
|
+
if (this._includeNodeIdentity || encryptionEnabled) {
|
|
397
623
|
Guards.stringValue(this.CLASS_NAME, "nodeIdentity", nodeIdentity);
|
|
398
624
|
}
|
|
399
625
|
try {
|
|
400
|
-
const connectorNamespace = namespace ?? this._defaultNamespace;
|
|
626
|
+
const connectorNamespace = options?.namespace ?? this._defaultNamespace;
|
|
401
627
|
const blobStorageConnector = BlobStorageConnectorFactory.get(connectorNamespace);
|
|
402
628
|
// Convert the base64 data into bytes
|
|
403
629
|
let storeBlob = Converter.base64ToBytes(blob);
|
|
630
|
+
const blobSize = storeBlob.length;
|
|
404
631
|
// See if we can detect the mime type and default extension for the data.
|
|
405
632
|
// If not already supplied by the caller. We have to perform this operation
|
|
406
633
|
// on the unencrypted data.
|
|
407
|
-
if (!Is.stringValue(
|
|
408
|
-
|
|
634
|
+
if (!Is.stringValue(encodingFormat)) {
|
|
635
|
+
encodingFormat = await MimeTypeHelper.detect(storeBlob);
|
|
409
636
|
}
|
|
410
|
-
if (!Is.stringValue(
|
|
411
|
-
|
|
637
|
+
if (!Is.stringValue(fileExtension) && Is.stringValue(encodingFormat)) {
|
|
638
|
+
fileExtension = await MimeTypeHelper.defaultExtension(encodingFormat);
|
|
412
639
|
}
|
|
413
640
|
if (Is.object(metadata)) {
|
|
414
641
|
const validationFailures = [];
|
|
415
642
|
JsonLdHelper.validate(metadata, validationFailures);
|
|
416
643
|
Validation.asValidationError(this.CLASS_NAME, "metadata", validationFailures);
|
|
417
644
|
}
|
|
645
|
+
const blobHash = `sha256:${Converter.bytesToBase64(Sha256.sum256(storeBlob))}`;
|
|
646
|
+
if (!Is.empty(options?.compress)) {
|
|
647
|
+
storeBlob = await Compression.compress(storeBlob, options.compress);
|
|
648
|
+
}
|
|
418
649
|
// If we have a vault connector then encrypt the data.
|
|
419
|
-
if (
|
|
420
|
-
|
|
650
|
+
if (encryptionEnabled) {
|
|
651
|
+
if (Is.empty(this._vaultConnector)) {
|
|
652
|
+
throw new GeneralError(this.CLASS_NAME, "vaultConnectorNotConfigured");
|
|
653
|
+
}
|
|
654
|
+
storeBlob = await this._vaultConnector.encrypt(`${nodeIdentity}/${vaultKeyId}`, VaultEncryptionType.ChaCha20Poly1305, storeBlob);
|
|
421
655
|
}
|
|
422
656
|
// Set the blob in the storage connector, which may now be encrypted
|
|
423
657
|
const blobId = await blobStorageConnector.set(storeBlob);
|
|
424
|
-
|
|
658
|
+
// Now store the entry in entity storage
|
|
659
|
+
const blobEntry = {
|
|
425
660
|
id: blobId,
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
661
|
+
dateCreated: new Date(Date.now()).toISOString(),
|
|
662
|
+
blobSize,
|
|
663
|
+
blobHash,
|
|
664
|
+
encodingFormat,
|
|
665
|
+
fileExtension,
|
|
666
|
+
metadata,
|
|
667
|
+
isEncrypted: encryptionEnabled,
|
|
668
|
+
compression: options?.compress
|
|
669
|
+
};
|
|
670
|
+
const conditions = [];
|
|
671
|
+
if (this._includeUserIdentity) {
|
|
672
|
+
ObjectHelper.propertySet(blobEntry, "userIdentity", userIdentity);
|
|
673
|
+
conditions.push({ property: "userIdentity", value: userIdentity });
|
|
674
|
+
}
|
|
675
|
+
if (this._includeNodeIdentity) {
|
|
676
|
+
ObjectHelper.propertySet(blobEntry, "nodeIdentity", nodeIdentity);
|
|
677
|
+
conditions.push({ property: "nodeIdentity", value: nodeIdentity });
|
|
678
|
+
}
|
|
679
|
+
await this._entryEntityStorage.set(blobEntry, conditions);
|
|
430
680
|
return blobId;
|
|
431
681
|
}
|
|
432
682
|
catch (error) {
|
|
@@ -434,21 +684,40 @@ class BlobStorageService {
|
|
|
434
684
|
}
|
|
435
685
|
}
|
|
436
686
|
/**
|
|
437
|
-
* Get the blob
|
|
687
|
+
* Get the blob entry.
|
|
438
688
|
* @param id The id of the blob to get in urn format.
|
|
439
|
-
* @param
|
|
440
|
-
* @param
|
|
441
|
-
* @
|
|
689
|
+
* @param options Optional options for the retrieval of the blob.
|
|
690
|
+
* @param options.includeContent Include the content, or just get the metadata.
|
|
691
|
+
* @param options.overrideVaultKeyId Use a different vault key id for decryption, if not provided the default vault key id will be used.
|
|
692
|
+
* @param options.decompress If the content should be decompressed, if it was compressed when stored, defaults to true.
|
|
693
|
+
* @param userIdentity The user identity to use with storage operations.
|
|
694
|
+
* @param nodeIdentity The node identity to use with storage operations.
|
|
695
|
+
* @returns The entry and data for the blob if it can be found.
|
|
442
696
|
* @throws Not found error if the blob cannot be found.
|
|
443
697
|
*/
|
|
444
|
-
async get(id,
|
|
698
|
+
async get(id, options, userIdentity, nodeIdentity) {
|
|
445
699
|
Urn.guard(this.CLASS_NAME, "id", id);
|
|
446
|
-
|
|
700
|
+
const includeContent = options?.includeContent ?? false;
|
|
701
|
+
const vaultKeyId = options?.overrideVaultKeyId ?? this._vaultKeyId;
|
|
702
|
+
const conditions = [];
|
|
703
|
+
if (this._includeUserIdentity) {
|
|
704
|
+
Guards.stringValue(this.CLASS_NAME, "userIdentity", userIdentity);
|
|
705
|
+
conditions.push({
|
|
706
|
+
property: "userIdentity",
|
|
707
|
+
comparison: ComparisonOperator.Equals,
|
|
708
|
+
value: userIdentity
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
if (this._includeNodeIdentity || (Is.notEmpty(this._vaultConnector) && includeContent)) {
|
|
447
712
|
Guards.stringValue(this.CLASS_NAME, "nodeIdentity", nodeIdentity);
|
|
713
|
+
conditions.push({
|
|
714
|
+
property: "nodeIdentity",
|
|
715
|
+
comparison: ComparisonOperator.Equals,
|
|
716
|
+
value: nodeIdentity
|
|
717
|
+
});
|
|
448
718
|
}
|
|
449
719
|
try {
|
|
450
|
-
|
|
451
|
-
const blobMetadata = await this._metadataEntityStorage.get(id);
|
|
720
|
+
const blobEntry = await this.internalGet(id, userIdentity, nodeIdentity);
|
|
452
721
|
let returnBlob;
|
|
453
722
|
if (includeContent) {
|
|
454
723
|
const blobStorageConnector = this.getConnector(id);
|
|
@@ -456,17 +725,20 @@ class BlobStorageService {
|
|
|
456
725
|
if (Is.undefined(returnBlob)) {
|
|
457
726
|
throw new NotFoundError(this.CLASS_NAME, "blobNotFound", id);
|
|
458
727
|
}
|
|
459
|
-
// If
|
|
460
|
-
|
|
461
|
-
|
|
728
|
+
// If the data is encrypted then decrypt it.
|
|
729
|
+
const decryptionEnabled = blobEntry.isEncrypted && Is.stringValue(vaultKeyId);
|
|
730
|
+
if (decryptionEnabled) {
|
|
731
|
+
if (Is.empty(this._vaultConnector)) {
|
|
732
|
+
throw new GeneralError(this.CLASS_NAME, "vaultConnectorNotConfigured");
|
|
733
|
+
}
|
|
734
|
+
returnBlob = await this._vaultConnector.decrypt(`${nodeIdentity}/${vaultKeyId}`, VaultEncryptionType.ChaCha20Poly1305, returnBlob);
|
|
735
|
+
}
|
|
736
|
+
if (!Is.empty(blobEntry.compression) && (options?.decompress ?? true)) {
|
|
737
|
+
returnBlob = await Compression.decompress(returnBlob, blobEntry.compression);
|
|
462
738
|
}
|
|
463
739
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
mimeType: blobMetadata?.mimeType,
|
|
467
|
-
extension: blobMetadata?.extension,
|
|
468
|
-
metadata: blobMetadata?.metadata
|
|
469
|
-
};
|
|
740
|
+
const jsonLd = this.entryToJsonLd(blobEntry, returnBlob);
|
|
741
|
+
return JsonLdProcessor.compact(jsonLd, jsonLd["@context"]);
|
|
470
742
|
}
|
|
471
743
|
catch (error) {
|
|
472
744
|
throw new GeneralError(this.CLASS_NAME, "getFailed", undefined, error);
|
|
@@ -474,18 +746,26 @@ class BlobStorageService {
|
|
|
474
746
|
}
|
|
475
747
|
/**
|
|
476
748
|
* Update the blob with metadata.
|
|
477
|
-
* @param id The id of the blob
|
|
478
|
-
* @param
|
|
479
|
-
* @param
|
|
749
|
+
* @param id The id of the blob entry to update.
|
|
750
|
+
* @param encodingFormat Mime type for the blob, will be detected if left undefined.
|
|
751
|
+
* @param fileExtension Extension for the blob, will be detected if left undefined.
|
|
480
752
|
* @param metadata Data for the custom metadata as JSON-LD.
|
|
753
|
+
* @param userIdentity The user identity to use with storage operations.
|
|
754
|
+
* @param nodeIdentity The node identity to use with storage operations.
|
|
481
755
|
* @returns Nothing.
|
|
482
756
|
* @throws Not found error if the blob cannot be found.
|
|
483
757
|
*/
|
|
484
|
-
async update(id,
|
|
758
|
+
async update(id, encodingFormat, fileExtension, metadata, userIdentity, nodeIdentity) {
|
|
485
759
|
Urn.guard(this.CLASS_NAME, "id", id);
|
|
760
|
+
if (this._includeUserIdentity) {
|
|
761
|
+
Guards.stringValue(this.CLASS_NAME, "userIdentity", userIdentity);
|
|
762
|
+
}
|
|
763
|
+
if (this._includeNodeIdentity || Is.notEmpty(this._vaultConnector)) {
|
|
764
|
+
Guards.stringValue(this.CLASS_NAME, "nodeIdentity", nodeIdentity);
|
|
765
|
+
}
|
|
486
766
|
try {
|
|
487
|
-
const
|
|
488
|
-
if (Is.undefined(
|
|
767
|
+
const blobEntry = await this._entryEntityStorage.get(id);
|
|
768
|
+
if (Is.undefined(blobEntry)) {
|
|
489
769
|
throw new NotFoundError(this.CLASS_NAME, "blobNotFound", id);
|
|
490
770
|
}
|
|
491
771
|
if (Is.object(metadata)) {
|
|
@@ -493,12 +773,29 @@ class BlobStorageService {
|
|
|
493
773
|
await JsonLdHelper.validate(metadata, validationFailures);
|
|
494
774
|
Validation.asValidationError(this.CLASS_NAME, "metadata", validationFailures);
|
|
495
775
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
776
|
+
// Now store the entry in entity storage
|
|
777
|
+
const updatedBlobEntry = {
|
|
778
|
+
id: blobEntry.id,
|
|
779
|
+
dateCreated: blobEntry.dateCreated,
|
|
780
|
+
dateModified: new Date(Date.now()).toISOString(),
|
|
781
|
+
blobSize: blobEntry.blobSize,
|
|
782
|
+
blobHash: blobEntry.blobHash,
|
|
783
|
+
encodingFormat: encodingFormat ?? blobEntry.encodingFormat,
|
|
784
|
+
fileExtension: fileExtension ?? blobEntry.fileExtension,
|
|
785
|
+
metadata: metadata ?? blobEntry.metadata,
|
|
786
|
+
isEncrypted: blobEntry.isEncrypted,
|
|
787
|
+
compression: blobEntry.compression
|
|
788
|
+
};
|
|
789
|
+
const conditions = [];
|
|
790
|
+
if (this._includeUserIdentity) {
|
|
791
|
+
ObjectHelper.propertySet(updatedBlobEntry, "userIdentity", userIdentity);
|
|
792
|
+
conditions.push({ property: "userIdentity", value: userIdentity });
|
|
793
|
+
}
|
|
794
|
+
if (this._includeNodeIdentity) {
|
|
795
|
+
ObjectHelper.propertySet(updatedBlobEntry, "nodeIdentity", nodeIdentity);
|
|
796
|
+
conditions.push({ property: "nodeIdentity", value: nodeIdentity });
|
|
797
|
+
}
|
|
798
|
+
await this._entryEntityStorage.set(updatedBlobEntry, conditions);
|
|
502
799
|
}
|
|
503
800
|
catch (error) {
|
|
504
801
|
throw new GeneralError(this.CLASS_NAME, "updateFailed", undefined, error);
|
|
@@ -507,22 +804,100 @@ class BlobStorageService {
|
|
|
507
804
|
/**
|
|
508
805
|
* Remove the blob.
|
|
509
806
|
* @param id The id of the blob to remove in urn format.
|
|
807
|
+
* @param userIdentity The user identity to use with storage operations.
|
|
808
|
+
* @param nodeIdentity The node identity to use with storage operations.
|
|
510
809
|
* @returns Nothing.
|
|
511
810
|
*/
|
|
512
|
-
async remove(id) {
|
|
811
|
+
async remove(id, userIdentity, nodeIdentity) {
|
|
513
812
|
Urn.guard(this.CLASS_NAME, "id", id);
|
|
813
|
+
if (this._includeUserIdentity) {
|
|
814
|
+
Guards.stringValue(this.CLASS_NAME, "userIdentity", userIdentity);
|
|
815
|
+
}
|
|
816
|
+
if (this._includeNodeIdentity || Is.notEmpty(this._vaultConnector)) {
|
|
817
|
+
Guards.stringValue(this.CLASS_NAME, "nodeIdentity", nodeIdentity);
|
|
818
|
+
}
|
|
514
819
|
try {
|
|
515
820
|
const blobStorageConnector = this.getConnector(id);
|
|
821
|
+
const conditions = [];
|
|
822
|
+
if (this._includeUserIdentity) {
|
|
823
|
+
conditions.push({ property: "userIdentity", value: userIdentity });
|
|
824
|
+
}
|
|
825
|
+
if (this._includeNodeIdentity) {
|
|
826
|
+
conditions.push({ property: "nodeIdentity", value: nodeIdentity });
|
|
827
|
+
}
|
|
828
|
+
await this._entryEntityStorage.remove(id, conditions);
|
|
516
829
|
const removed = await blobStorageConnector.remove(id);
|
|
517
830
|
if (!removed) {
|
|
518
831
|
throw new NotFoundError(this.CLASS_NAME, "blobNotFound", id);
|
|
519
832
|
}
|
|
520
|
-
await this._metadataEntityStorage.remove(id);
|
|
521
833
|
}
|
|
522
834
|
catch (error) {
|
|
523
835
|
throw new GeneralError(this.CLASS_NAME, "removeFailed", undefined, error);
|
|
524
836
|
}
|
|
525
837
|
}
|
|
838
|
+
/**
|
|
839
|
+
* Query all the blob storage entries which match the conditions.
|
|
840
|
+
* @param conditions The conditions to match for the entries.
|
|
841
|
+
* @param orderBy The order for the results, defaults to created.
|
|
842
|
+
* @param orderByDirection The direction for the order, defaults to descending.
|
|
843
|
+
* @param cursor The cursor to request the next page of entries.
|
|
844
|
+
* @param pageSize The suggested number of entries to return in each chunk, in some scenarios can return a different amount.
|
|
845
|
+
* @param userIdentity The user identity to use with storage operations.
|
|
846
|
+
* @param nodeIdentity The node identity to use with storage operations.
|
|
847
|
+
* @returns All the entries for the storage matching the conditions,
|
|
848
|
+
* and a cursor which can be used to request more entities.
|
|
849
|
+
*/
|
|
850
|
+
async query(conditions, orderBy, orderByDirection, cursor, pageSize, userIdentity, nodeIdentity) {
|
|
851
|
+
const finalConditions = {
|
|
852
|
+
conditions: [],
|
|
853
|
+
logicalOperator: LogicalOperator.And
|
|
854
|
+
};
|
|
855
|
+
if (this._includeNodeIdentity) {
|
|
856
|
+
Guards.stringValue(this.CLASS_NAME, "nodeIdentity", nodeIdentity);
|
|
857
|
+
finalConditions.conditions.push({
|
|
858
|
+
property: "nodeIdentity",
|
|
859
|
+
comparison: ComparisonOperator.Equals,
|
|
860
|
+
value: nodeIdentity
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
if (this._includeUserIdentity) {
|
|
864
|
+
Guards.stringValue(this.CLASS_NAME, "userIdentity", userIdentity);
|
|
865
|
+
finalConditions.conditions.push({
|
|
866
|
+
property: "userIdentity",
|
|
867
|
+
comparison: ComparisonOperator.Equals,
|
|
868
|
+
value: userIdentity
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
if (!Is.empty(conditions)) {
|
|
872
|
+
finalConditions.conditions.push(conditions);
|
|
873
|
+
}
|
|
874
|
+
const orderProperty = orderBy ?? "dateCreated";
|
|
875
|
+
const orderDirection = orderByDirection ?? SortDirection.Descending;
|
|
876
|
+
const result = await this._entryEntityStorage.query(finalConditions.conditions.length > 0 ? finalConditions : undefined, [
|
|
877
|
+
{
|
|
878
|
+
property: orderProperty,
|
|
879
|
+
sortDirection: orderDirection
|
|
880
|
+
}
|
|
881
|
+
], undefined, cursor, pageSize);
|
|
882
|
+
let context = [
|
|
883
|
+
SchemaOrgContexts.ContextRoot,
|
|
884
|
+
BlobStorageContexts.ContextRoot,
|
|
885
|
+
BlobStorageContexts.ContextRootCommon
|
|
886
|
+
];
|
|
887
|
+
const entriesJsonLd = [];
|
|
888
|
+
for (const entry of result.entities) {
|
|
889
|
+
// The entries are never Partial as we don't allow custom property requests.
|
|
890
|
+
entriesJsonLd.push(this.entryToJsonLd(entry));
|
|
891
|
+
context = JsonLdProcessor.combineContexts(context, entry.metadata?.["@context"]);
|
|
892
|
+
}
|
|
893
|
+
const jsonLd = {
|
|
894
|
+
"@context": context,
|
|
895
|
+
type: SchemaOrgTypes.ItemList,
|
|
896
|
+
[SchemaOrgTypes.ItemListElement]: entriesJsonLd,
|
|
897
|
+
[SchemaOrgTypes.NextItem]: result.cursor
|
|
898
|
+
};
|
|
899
|
+
return JsonLdProcessor.compact(jsonLd, jsonLd["@context"]);
|
|
900
|
+
}
|
|
526
901
|
/**
|
|
527
902
|
* Get the connector from the uri.
|
|
528
903
|
* @param id The id of the blob storage item in urn format.
|
|
@@ -539,53 +914,206 @@ class BlobStorageService {
|
|
|
539
914
|
}
|
|
540
915
|
return BlobStorageConnectorFactory.get(idUri.namespaceMethod());
|
|
541
916
|
}
|
|
917
|
+
/**
|
|
918
|
+
* Get an entity.
|
|
919
|
+
* @param id The id of the entity to get, or the index value if secondaryIndex is set.
|
|
920
|
+
* @param secondaryIndex Get the item using a secondary index.
|
|
921
|
+
* @param userIdentity The user identity to use with storage operations.
|
|
922
|
+
* @param nodeIdentity The node identity to use with storage operations.
|
|
923
|
+
* @returns The object if it can be found or throws.
|
|
924
|
+
* @internal
|
|
925
|
+
*/
|
|
926
|
+
async internalGet(id, userIdentity, nodeIdentity) {
|
|
927
|
+
const conditions = [];
|
|
928
|
+
if (this._includeUserIdentity) {
|
|
929
|
+
Guards.stringValue(this.CLASS_NAME, "userIdentity", userIdentity);
|
|
930
|
+
conditions.push({
|
|
931
|
+
property: "userIdentity",
|
|
932
|
+
comparison: ComparisonOperator.Equals,
|
|
933
|
+
value: userIdentity
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
if (this._includeNodeIdentity) {
|
|
937
|
+
Guards.stringValue(this.CLASS_NAME, "nodeIdentity", nodeIdentity);
|
|
938
|
+
conditions.push({
|
|
939
|
+
property: "nodeIdentity",
|
|
940
|
+
comparison: ComparisonOperator.Equals,
|
|
941
|
+
value: nodeIdentity
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
let entity;
|
|
945
|
+
if (conditions.length === 0) {
|
|
946
|
+
entity = await this._entryEntityStorage.get(id);
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
const schema = this._entryEntityStorage.getSchema();
|
|
950
|
+
const primaryKey = EntitySchemaHelper.getPrimaryKey(schema);
|
|
951
|
+
conditions.unshift({
|
|
952
|
+
property: primaryKey.property,
|
|
953
|
+
comparison: ComparisonOperator.Equals,
|
|
954
|
+
value: id
|
|
955
|
+
});
|
|
956
|
+
const results = await this._entryEntityStorage.query({
|
|
957
|
+
conditions,
|
|
958
|
+
logicalOperator: LogicalOperator.And
|
|
959
|
+
}, undefined, undefined, undefined, 1);
|
|
960
|
+
entity = results.entities[0];
|
|
961
|
+
}
|
|
962
|
+
if (Is.empty(entity)) {
|
|
963
|
+
throw new NotFoundError(this.CLASS_NAME, "entityNotFound", id);
|
|
964
|
+
}
|
|
965
|
+
return ObjectHelper.omit(entity, ["nodeIdentity", "userIdentity"]);
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Convert the entry to JSON-LD.
|
|
969
|
+
* @param entry The entry to convert.
|
|
970
|
+
* @param blob The optional blob to return.
|
|
971
|
+
* @returns The JSON-LD representation of the entry.
|
|
972
|
+
* @internal
|
|
973
|
+
*/
|
|
974
|
+
entryToJsonLd(entry, blob) {
|
|
975
|
+
const jsonLd = {
|
|
976
|
+
"@context": JsonLdProcessor.combineContexts([
|
|
977
|
+
BlobStorageContexts.ContextRoot,
|
|
978
|
+
BlobStorageContexts.ContextRootCommon,
|
|
979
|
+
SchemaOrgContexts.ContextRoot
|
|
980
|
+
], entry?.metadata?.["@context"]),
|
|
981
|
+
id: entry.id,
|
|
982
|
+
type: BlobStorageTypes.Entry,
|
|
983
|
+
dateCreated: entry.dateCreated,
|
|
984
|
+
dateModified: entry.dateModified,
|
|
985
|
+
blobSize: entry.blobSize,
|
|
986
|
+
blobHash: entry.blobHash,
|
|
987
|
+
encodingFormat: entry?.encodingFormat,
|
|
988
|
+
fileExtension: entry?.fileExtension,
|
|
989
|
+
metadata: entry?.metadata,
|
|
990
|
+
blob: Is.uint8Array(blob) ? Converter.bytesToBase64(blob) : undefined,
|
|
991
|
+
isEncrypted: entry.isEncrypted,
|
|
992
|
+
compression: entry.compression
|
|
993
|
+
};
|
|
994
|
+
return jsonLd;
|
|
995
|
+
}
|
|
542
996
|
}
|
|
543
997
|
|
|
544
998
|
/**
|
|
545
|
-
* Class representing
|
|
999
|
+
* Class representing entry for the blob storage.
|
|
546
1000
|
*/
|
|
547
|
-
let
|
|
1001
|
+
let BlobStorageEntry = class BlobStorageEntry {
|
|
548
1002
|
/**
|
|
549
1003
|
* The id for the blob.
|
|
550
1004
|
*/
|
|
551
1005
|
id;
|
|
1006
|
+
/**
|
|
1007
|
+
* The date/time when the entry was created.
|
|
1008
|
+
*/
|
|
1009
|
+
dateCreated;
|
|
1010
|
+
/**
|
|
1011
|
+
* The date/time when the entry was modified.
|
|
1012
|
+
*/
|
|
1013
|
+
dateModified;
|
|
1014
|
+
/**
|
|
1015
|
+
* The length of the data in the blob.
|
|
1016
|
+
*/
|
|
1017
|
+
blobSize;
|
|
1018
|
+
/**
|
|
1019
|
+
* The hash of the data in the blob.
|
|
1020
|
+
*/
|
|
1021
|
+
blobHash;
|
|
552
1022
|
/**
|
|
553
1023
|
* The mime type for the blob.
|
|
554
1024
|
*/
|
|
555
|
-
|
|
1025
|
+
encodingFormat;
|
|
556
1026
|
/**
|
|
557
1027
|
* The extension.
|
|
558
1028
|
*/
|
|
559
|
-
|
|
1029
|
+
fileExtension;
|
|
560
1030
|
/**
|
|
561
1031
|
* The metadata for the blob as JSON-LD.
|
|
562
1032
|
*/
|
|
563
1033
|
metadata;
|
|
1034
|
+
/**
|
|
1035
|
+
* Is the entry encrypted.
|
|
1036
|
+
*/
|
|
1037
|
+
isEncrypted;
|
|
1038
|
+
/**
|
|
1039
|
+
* Is the entry compressed.
|
|
1040
|
+
*/
|
|
1041
|
+
compression;
|
|
1042
|
+
/**
|
|
1043
|
+
* The user identity that created the blob.
|
|
1044
|
+
*/
|
|
1045
|
+
userIdentity;
|
|
1046
|
+
/**
|
|
1047
|
+
* The node identity that created the blob.
|
|
1048
|
+
*/
|
|
1049
|
+
nodeIdentity;
|
|
564
1050
|
};
|
|
565
1051
|
__decorate([
|
|
566
1052
|
property({ type: "string", isPrimary: true }),
|
|
567
1053
|
__metadata("design:type", String)
|
|
568
|
-
],
|
|
1054
|
+
], BlobStorageEntry.prototype, "id", void 0);
|
|
569
1055
|
__decorate([
|
|
570
|
-
property({ type: "string" }),
|
|
1056
|
+
property({ type: "string", format: "date-time", sortDirection: SortDirection.Descending }),
|
|
1057
|
+
__metadata("design:type", String)
|
|
1058
|
+
], BlobStorageEntry.prototype, "dateCreated", void 0);
|
|
1059
|
+
__decorate([
|
|
1060
|
+
property({
|
|
1061
|
+
type: "string",
|
|
1062
|
+
format: "date-time",
|
|
1063
|
+
sortDirection: SortDirection.Descending,
|
|
1064
|
+
optional: true
|
|
1065
|
+
}),
|
|
571
1066
|
__metadata("design:type", String)
|
|
572
|
-
],
|
|
1067
|
+
], BlobStorageEntry.prototype, "dateModified", void 0);
|
|
1068
|
+
__decorate([
|
|
1069
|
+
property({ type: "number" }),
|
|
1070
|
+
__metadata("design:type", Number)
|
|
1071
|
+
], BlobStorageEntry.prototype, "blobSize", void 0);
|
|
573
1072
|
__decorate([
|
|
574
1073
|
property({ type: "string" }),
|
|
575
1074
|
__metadata("design:type", String)
|
|
576
|
-
],
|
|
1075
|
+
], BlobStorageEntry.prototype, "blobHash", void 0);
|
|
1076
|
+
__decorate([
|
|
1077
|
+
property({ type: "string", optional: true }),
|
|
1078
|
+
__metadata("design:type", String)
|
|
1079
|
+
], BlobStorageEntry.prototype, "encodingFormat", void 0);
|
|
1080
|
+
__decorate([
|
|
1081
|
+
property({ type: "string", optional: true }),
|
|
1082
|
+
__metadata("design:type", String)
|
|
1083
|
+
], BlobStorageEntry.prototype, "fileExtension", void 0);
|
|
577
1084
|
__decorate([
|
|
578
|
-
property({ type: "object", itemTypeRef: "IJsonLdNodeObject" }),
|
|
1085
|
+
property({ type: "object", itemTypeRef: "IJsonLdNodeObject", optional: true }),
|
|
579
1086
|
__metadata("design:type", Object)
|
|
580
|
-
],
|
|
581
|
-
|
|
1087
|
+
], BlobStorageEntry.prototype, "metadata", void 0);
|
|
1088
|
+
__decorate([
|
|
1089
|
+
property({ type: "boolean" }),
|
|
1090
|
+
__metadata("design:type", Boolean)
|
|
1091
|
+
], BlobStorageEntry.prototype, "isEncrypted", void 0);
|
|
1092
|
+
__decorate([
|
|
1093
|
+
property({ type: "string", optional: true }),
|
|
1094
|
+
__metadata("design:type", String)
|
|
1095
|
+
], BlobStorageEntry.prototype, "compression", void 0);
|
|
1096
|
+
__decorate([
|
|
1097
|
+
property({ type: "string", optional: true }),
|
|
1098
|
+
__metadata("design:type", String)
|
|
1099
|
+
], BlobStorageEntry.prototype, "userIdentity", void 0);
|
|
1100
|
+
__decorate([
|
|
1101
|
+
property({ type: "string", optional: true }),
|
|
1102
|
+
__metadata("design:type", String)
|
|
1103
|
+
], BlobStorageEntry.prototype, "nodeIdentity", void 0);
|
|
1104
|
+
BlobStorageEntry = __decorate([
|
|
582
1105
|
entity()
|
|
583
|
-
],
|
|
1106
|
+
], BlobStorageEntry);
|
|
584
1107
|
|
|
1108
|
+
/**
|
|
1109
|
+
* These are dummy entry points for the blob storage service.
|
|
1110
|
+
* In reality your application would create its own entry points based on the
|
|
1111
|
+
* blob types it wants to store, using a custom defaultBaseRoute.
|
|
1112
|
+
*/
|
|
585
1113
|
const restEntryPoints = [
|
|
586
1114
|
{
|
|
587
|
-
name: "
|
|
588
|
-
defaultBaseRoute: "blob",
|
|
1115
|
+
name: "blob-storage",
|
|
1116
|
+
defaultBaseRoute: "blob-storage",
|
|
589
1117
|
tags: tagsBlobStorage,
|
|
590
1118
|
generateRoutes: generateRestRoutesBlobStorage
|
|
591
1119
|
}
|
|
@@ -597,7 +1125,7 @@ const restEntryPoints = [
|
|
|
597
1125
|
* Initialize the schema for the blob storage entities.
|
|
598
1126
|
*/
|
|
599
1127
|
function initSchema() {
|
|
600
|
-
EntitySchemaFactory.register("
|
|
1128
|
+
EntitySchemaFactory.register("BlobStorageEntry", () => EntitySchemaHelper.getSchema(BlobStorageEntry));
|
|
601
1129
|
}
|
|
602
1130
|
|
|
603
|
-
export {
|
|
1131
|
+
export { BlobStorageEntry, BlobStorageService, blobStorageCreate, blobStorageGet, blobStorageGetContent, blobStorageList, blobStorageRemove, blobStorageUpdate, generateRestRoutesBlobStorage, initSchema, restEntryPoints, tagsBlobStorage };
|