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