@twin.org/auditable-item-graph-service 0.0.2-next.7 → 0.0.3-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/auditableItemGraphRoutes.js +529 -0
- package/dist/es/auditableItemGraphRoutes.js.map +1 -0
- package/dist/es/auditableItemGraphService.js +919 -0
- package/dist/es/auditableItemGraphService.js.map +1 -0
- package/dist/es/entities/auditableItemGraphAlias.js +62 -0
- package/dist/es/entities/auditableItemGraphAlias.js.map +1 -0
- package/dist/es/entities/auditableItemGraphChangeset.js +61 -0
- package/dist/es/entities/auditableItemGraphChangeset.js.map +1 -0
- package/dist/es/entities/auditableItemGraphEdge.js +70 -0
- package/dist/es/entities/auditableItemGraphEdge.js.map +1 -0
- package/dist/es/entities/auditableItemGraphPatch.js +45 -0
- package/dist/es/entities/auditableItemGraphPatch.js.map +1 -0
- package/dist/es/entities/auditableItemGraphResource.js +54 -0
- package/dist/es/entities/auditableItemGraphResource.js.map +1 -0
- package/dist/es/entities/auditableItemGraphVertex.js +99 -0
- package/dist/es/entities/auditableItemGraphVertex.js.map +1 -0
- package/dist/es/index.js +15 -0
- package/dist/es/index.js.map +1 -0
- package/dist/es/models/IAuditableItemGraphServiceConfig.js +4 -0
- package/dist/es/models/IAuditableItemGraphServiceConfig.js.map +1 -0
- package/dist/es/models/IAuditableItemGraphServiceConstructorOptions.js +2 -0
- package/dist/es/models/IAuditableItemGraphServiceConstructorOptions.js.map +1 -0
- package/dist/es/models/IAuditableItemGraphServiceContext.js +2 -0
- package/dist/es/models/IAuditableItemGraphServiceContext.js.map +1 -0
- package/dist/es/restEntryPoints.js +10 -0
- package/dist/es/restEntryPoints.js.map +1 -0
- package/dist/es/schema.js +21 -0
- package/dist/es/schema.js.map +1 -0
- package/dist/types/auditableItemGraphService.d.ts +16 -16
- package/dist/types/entities/auditableItemGraphChangeset.d.ts +2 -2
- package/dist/types/entities/auditableItemGraphVertex.d.ts +5 -5
- package/dist/types/index.d.ts +12 -12
- package/dist/types/models/IAuditableItemGraphServiceConstructorOptions.d.ts +1 -1
- package/dist/types/models/IAuditableItemGraphServiceContext.d.ts +3 -6
- package/docs/changelog.md +14 -0
- package/docs/open-api/spec.json +124 -145
- package/docs/reference/classes/AuditableItemGraphChangeset.md +2 -2
- package/docs/reference/classes/AuditableItemGraphService.md +27 -43
- package/docs/reference/classes/AuditableItemGraphVertex.md +3 -3
- package/package.json +23 -9
- package/dist/cjs/index.cjs +0 -1846
- package/dist/esm/index.mjs +0 -1836
|
@@ -0,0 +1,919 @@
|
|
|
1
|
+
// Copyright 2024 IOTA Stiftung.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
+
import { AuditableItemGraphContexts, AuditableItemGraphTopics, AuditableItemGraphTypes, VerifyDepth } from "@twin.org/auditable-item-graph-models";
|
|
4
|
+
import { ContextIdKeys, ContextIdStore } from "@twin.org/context";
|
|
5
|
+
import { ArrayHelper, ComponentFactory, Converter, GeneralError, Guards, Is, JsonHelper, NotFoundError, ObjectHelper, RandomHelper, StringHelper, Urn, Validation } from "@twin.org/core";
|
|
6
|
+
import { JsonLdHelper, JsonLdProcessor } from "@twin.org/data-json-ld";
|
|
7
|
+
import { ComparisonOperator, LogicalOperator, SortDirection } from "@twin.org/entity";
|
|
8
|
+
import { EntityStorageConnectorFactory } from "@twin.org/entity-storage-models";
|
|
9
|
+
import { ImmutableProofContexts, ImmutableProofFailure, ImmutableProofTypes } from "@twin.org/immutable-proof-models";
|
|
10
|
+
import { SchemaOrgContexts, SchemaOrgDataTypes, SchemaOrgTypes } from "@twin.org/standards-schema-org";
|
|
11
|
+
/**
|
|
12
|
+
* Class for performing auditable item graph operations.
|
|
13
|
+
*/
|
|
14
|
+
export class AuditableItemGraphService {
|
|
15
|
+
/**
|
|
16
|
+
* Runtime name for the class.
|
|
17
|
+
*/
|
|
18
|
+
static CLASS_NAME = "AuditableItemGraphService";
|
|
19
|
+
/**
|
|
20
|
+
* The namespace for the service.
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
static NAMESPACE = "aig";
|
|
24
|
+
/**
|
|
25
|
+
* The namespace for the service changeset.
|
|
26
|
+
*/
|
|
27
|
+
static NAMESPACE_CHANGESET = "changeset";
|
|
28
|
+
/**
|
|
29
|
+
* The namespace for the service edge.
|
|
30
|
+
*/
|
|
31
|
+
static NAMESPACE_EDGE = "edge";
|
|
32
|
+
/**
|
|
33
|
+
* The keys to pick when creating the proof for the stream.
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
static _PROOF_KEYS_CHANGESET = [
|
|
37
|
+
"id",
|
|
38
|
+
"vertexId",
|
|
39
|
+
"userIdentity",
|
|
40
|
+
"dateCreated",
|
|
41
|
+
"patches"
|
|
42
|
+
];
|
|
43
|
+
/**
|
|
44
|
+
* The immutable proof component.
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
_immutableProofComponent;
|
|
48
|
+
/**
|
|
49
|
+
* The entity storage for vertices.
|
|
50
|
+
* @internal
|
|
51
|
+
*/
|
|
52
|
+
_vertexStorage;
|
|
53
|
+
/**
|
|
54
|
+
* The entity storage for changesets.
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
_changesetStorage;
|
|
58
|
+
/**
|
|
59
|
+
* The event bus component.
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
_eventBusComponent;
|
|
63
|
+
/**
|
|
64
|
+
* Create a new instance of AuditableItemGraphService.
|
|
65
|
+
* @param options The dependencies for the auditable item graph connector.
|
|
66
|
+
*/
|
|
67
|
+
constructor(options) {
|
|
68
|
+
this._immutableProofComponent = ComponentFactory.get(options?.immutableProofComponentType ?? "immutable-proof");
|
|
69
|
+
this._vertexStorage = EntityStorageConnectorFactory.get(options?.vertexEntityStorageType ?? "auditable-item-graph-vertex");
|
|
70
|
+
this._changesetStorage = EntityStorageConnectorFactory.get(options?.changesetEntityStorageType ?? "auditable-item-graph-changeset");
|
|
71
|
+
if (Is.stringValue(options?.eventBusComponentType)) {
|
|
72
|
+
this._eventBusComponent = ComponentFactory.get(options.eventBusComponentType);
|
|
73
|
+
}
|
|
74
|
+
SchemaOrgDataTypes.registerRedirects();
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Returns the class name of the component.
|
|
78
|
+
* @returns The class name of the component.
|
|
79
|
+
*/
|
|
80
|
+
className() {
|
|
81
|
+
return AuditableItemGraphService.CLASS_NAME;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Create a new graph vertex.
|
|
85
|
+
* @param vertex The vertex to create.
|
|
86
|
+
* @param vertex.annotationObject The annotation object for the vertex as JSON-LD.
|
|
87
|
+
* @param vertex.aliases Alternative aliases that can be used to identify the vertex.
|
|
88
|
+
* @param vertex.resources The resources attached to the vertex.
|
|
89
|
+
* @param vertex.edges The edges connected to the vertex.
|
|
90
|
+
* @returns The id of the new graph item.
|
|
91
|
+
*/
|
|
92
|
+
async create(vertex) {
|
|
93
|
+
Guards.object(AuditableItemGraphService.CLASS_NAME, "vertex", vertex);
|
|
94
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
95
|
+
try {
|
|
96
|
+
if (Is.object(vertex.annotationObject)) {
|
|
97
|
+
const validationFailures = [];
|
|
98
|
+
await JsonLdHelper.validate(vertex.annotationObject, validationFailures);
|
|
99
|
+
Validation.asValidationError(AuditableItemGraphService.CLASS_NAME, "vertex.annotationObject", validationFailures);
|
|
100
|
+
}
|
|
101
|
+
const id = Converter.bytesToHex(RandomHelper.generate(32), false);
|
|
102
|
+
const context = {
|
|
103
|
+
now: new Date(Date.now()).toISOString(),
|
|
104
|
+
contextIds
|
|
105
|
+
};
|
|
106
|
+
const vertexModel = {
|
|
107
|
+
id,
|
|
108
|
+
organizationIdentity: contextIds?.[ContextIdKeys.Organization],
|
|
109
|
+
dateCreated: context.now
|
|
110
|
+
};
|
|
111
|
+
const originalEntity = ObjectHelper.clone(vertexModel);
|
|
112
|
+
vertexModel.annotationObject = vertex.annotationObject;
|
|
113
|
+
await this.updateAliasList(context, vertexModel, vertex.aliases);
|
|
114
|
+
await this.updateResourceList(context, vertexModel, vertex.resources);
|
|
115
|
+
await this.updateEdgeList(context, vertexModel, vertex.edges);
|
|
116
|
+
delete originalEntity.aliasIndex;
|
|
117
|
+
delete originalEntity.resourceTypeIndex;
|
|
118
|
+
await this.addChangeset(context, originalEntity, vertexModel, true);
|
|
119
|
+
await this._vertexStorage.set({
|
|
120
|
+
...vertexModel,
|
|
121
|
+
...this.buildIndexes(vertexModel)
|
|
122
|
+
});
|
|
123
|
+
const fullId = new Urn(AuditableItemGraphService.NAMESPACE, id).toString();
|
|
124
|
+
await this._eventBusComponent?.publish(AuditableItemGraphTopics.VertexCreated, { id: fullId });
|
|
125
|
+
return fullId;
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
throw new GeneralError(AuditableItemGraphService.CLASS_NAME, "createFailed", undefined, error);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get a graph vertex.
|
|
133
|
+
* @param id The id of the vertex to get.
|
|
134
|
+
* @param options Additional options for the get operation.
|
|
135
|
+
* @param options.includeDeleted Whether to include deleted/updated aliases, resource, edges, defaults to false.
|
|
136
|
+
* @param options.includeChangesets Whether to include the changesets of the vertex, defaults to false.
|
|
137
|
+
* @param options.verifySignatureDepth How many signatures to verify, defaults to "none".
|
|
138
|
+
* @returns The vertex if found.
|
|
139
|
+
* @throws NotFoundError if the vertex is not found.
|
|
140
|
+
*/
|
|
141
|
+
async get(id, options) {
|
|
142
|
+
Guards.stringValue(AuditableItemGraphService.CLASS_NAME, "id", id);
|
|
143
|
+
const urnParsed = Urn.fromValidString(id);
|
|
144
|
+
if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.NAMESPACE) {
|
|
145
|
+
throw new GeneralError(AuditableItemGraphService.CLASS_NAME, "namespaceMismatch", {
|
|
146
|
+
namespace: AuditableItemGraphService.NAMESPACE,
|
|
147
|
+
id
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const vertexId = urnParsed.namespaceSpecific(0);
|
|
152
|
+
const vertexEntity = await this._vertexStorage.get(vertexId);
|
|
153
|
+
if (Is.empty(vertexEntity)) {
|
|
154
|
+
throw new NotFoundError(AuditableItemGraphService.CLASS_NAME, "vertexNotFound", id);
|
|
155
|
+
}
|
|
156
|
+
const vertexModel = this.vertexEntityToJsonLd(vertexEntity);
|
|
157
|
+
const includeChangesets = options?.includeChangesets ?? false;
|
|
158
|
+
const verifySignatureDepth = options?.verifySignatureDepth ?? "none";
|
|
159
|
+
let verified;
|
|
160
|
+
let changesets;
|
|
161
|
+
if (verifySignatureDepth === VerifyDepth.Current ||
|
|
162
|
+
verifySignatureDepth === VerifyDepth.All ||
|
|
163
|
+
includeChangesets) {
|
|
164
|
+
const verifyResult = await this.verifyChangesets(vertexModel, verifySignatureDepth);
|
|
165
|
+
verified = verifyResult.verified;
|
|
166
|
+
changesets = verifyResult.changesets;
|
|
167
|
+
vertexModel["@context"].push(ImmutableProofContexts.ContextRoot);
|
|
168
|
+
}
|
|
169
|
+
if (!(options?.includeDeleted ?? false)) {
|
|
170
|
+
if (Is.arrayValue(vertexModel.aliases)) {
|
|
171
|
+
vertexModel.aliases = vertexModel.aliases.filter(a => Is.undefined(a.dateDeleted));
|
|
172
|
+
if (vertexModel.aliases.length === 0) {
|
|
173
|
+
delete vertexModel.aliases;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (Is.arrayValue(vertexModel.resources)) {
|
|
177
|
+
vertexModel.resources = vertexModel.resources.filter(r => Is.undefined(r.dateDeleted));
|
|
178
|
+
if (vertexModel.resources.length === 0) {
|
|
179
|
+
delete vertexModel.resources;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (Is.arrayValue(vertexModel.edges)) {
|
|
183
|
+
vertexModel.edges = vertexModel.edges.filter(r => Is.undefined(r.dateDeleted));
|
|
184
|
+
if (vertexModel.edges.length === 0) {
|
|
185
|
+
delete vertexModel.edges;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (includeChangesets) {
|
|
190
|
+
vertexModel.changesets = changesets;
|
|
191
|
+
}
|
|
192
|
+
if (verifySignatureDepth !== VerifyDepth.None) {
|
|
193
|
+
vertexModel.verified = verified;
|
|
194
|
+
}
|
|
195
|
+
const result = await JsonLdProcessor.compact(vertexModel, vertexModel["@context"]);
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
throw new GeneralError(AuditableItemGraphService.CLASS_NAME, "getFailed", undefined, error);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Update a graph vertex.
|
|
204
|
+
* @param vertex The vertex to update.
|
|
205
|
+
* @param vertex.id The id of the vertex to update.
|
|
206
|
+
* @param vertex.annotationObject The annotation object for the vertex as JSON-LD.
|
|
207
|
+
* @param vertex.aliases Alternative aliases that can be used to identify the vertex.
|
|
208
|
+
* @param vertex.resources The resources attached to the vertex.
|
|
209
|
+
* @param vertex.edges The edges connected to the vertex.
|
|
210
|
+
* @returns Nothing.
|
|
211
|
+
*/
|
|
212
|
+
async update(vertex) {
|
|
213
|
+
Guards.object(AuditableItemGraphService.CLASS_NAME, "vertex", vertex);
|
|
214
|
+
Guards.stringValue(AuditableItemGraphService.CLASS_NAME, "vertex.id", vertex.id);
|
|
215
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
216
|
+
const urnParsed = Urn.fromValidString(vertex.id);
|
|
217
|
+
if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.NAMESPACE) {
|
|
218
|
+
throw new GeneralError(AuditableItemGraphService.CLASS_NAME, "namespaceMismatch", {
|
|
219
|
+
namespace: AuditableItemGraphService.NAMESPACE,
|
|
220
|
+
id: vertex.id
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
const vertexId = urnParsed.namespaceSpecific(0);
|
|
225
|
+
const vertexEntity = await this._vertexStorage.get(vertexId);
|
|
226
|
+
if (Is.empty(vertexEntity)) {
|
|
227
|
+
throw new NotFoundError(AuditableItemGraphService.CLASS_NAME, "vertexNotFound", vertex.id);
|
|
228
|
+
}
|
|
229
|
+
if (Is.object(vertex.annotationObject)) {
|
|
230
|
+
const validationFailures = [];
|
|
231
|
+
await JsonLdHelper.validate(vertex.annotationObject, validationFailures);
|
|
232
|
+
Validation.asValidationError(AuditableItemGraphService.CLASS_NAME, "vertex.annotationObject", validationFailures);
|
|
233
|
+
}
|
|
234
|
+
const context = {
|
|
235
|
+
now: new Date(Date.now()).toISOString(),
|
|
236
|
+
contextIds
|
|
237
|
+
};
|
|
238
|
+
delete vertexEntity.aliasIndex;
|
|
239
|
+
const originalEntity = ObjectHelper.clone(vertexEntity);
|
|
240
|
+
const newEntity = ObjectHelper.clone(vertexEntity);
|
|
241
|
+
newEntity.annotationObject = vertex.annotationObject;
|
|
242
|
+
await this.updateAliasList(context, newEntity, vertex.aliases);
|
|
243
|
+
await this.updateResourceList(context, newEntity, vertex.resources);
|
|
244
|
+
await this.updateEdgeList(context, newEntity, vertex.edges);
|
|
245
|
+
const patches = await this.addChangeset(context, originalEntity, newEntity, false);
|
|
246
|
+
if (patches.length > 0) {
|
|
247
|
+
newEntity.dateModified = context.now;
|
|
248
|
+
const indexes = this.buildIndexes(newEntity);
|
|
249
|
+
await this._vertexStorage.set({
|
|
250
|
+
...newEntity,
|
|
251
|
+
...indexes
|
|
252
|
+
});
|
|
253
|
+
await this._eventBusComponent?.publish(AuditableItemGraphTopics.VertexUpdated, { id: vertex.id, patches });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
throw new GeneralError(AuditableItemGraphService.CLASS_NAME, "updatingFailed", undefined, error);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Remove the verifiable storage for an item.
|
|
262
|
+
* @param id The id of the vertex to get.
|
|
263
|
+
* @returns Nothing.
|
|
264
|
+
* @throws NotFoundError if the vertex is not found.
|
|
265
|
+
*/
|
|
266
|
+
async removeVerifiable(id) {
|
|
267
|
+
Guards.stringValue(AuditableItemGraphService.CLASS_NAME, "id", id);
|
|
268
|
+
const urnParsed = Urn.fromValidString(id);
|
|
269
|
+
if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.NAMESPACE) {
|
|
270
|
+
throw new GeneralError(AuditableItemGraphService.CLASS_NAME, "namespaceMismatch", {
|
|
271
|
+
namespace: AuditableItemGraphService.NAMESPACE,
|
|
272
|
+
id
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
const vertexId = urnParsed.namespaceSpecific(0);
|
|
277
|
+
const vertexEntity = await this._vertexStorage.get(vertexId);
|
|
278
|
+
if (Is.empty(vertexEntity)) {
|
|
279
|
+
throw new NotFoundError(AuditableItemGraphService.CLASS_NAME, "vertexNotFound", id);
|
|
280
|
+
}
|
|
281
|
+
let changesetsResult;
|
|
282
|
+
do {
|
|
283
|
+
changesetsResult = await this._changesetStorage.query({
|
|
284
|
+
property: "vertexId",
|
|
285
|
+
value: vertexId,
|
|
286
|
+
comparison: ComparisonOperator.Equals
|
|
287
|
+
}, [
|
|
288
|
+
{
|
|
289
|
+
property: "dateCreated",
|
|
290
|
+
sortDirection: SortDirection.Ascending
|
|
291
|
+
}
|
|
292
|
+
], undefined, changesetsResult?.cursor);
|
|
293
|
+
for (const changeset of changesetsResult.entities) {
|
|
294
|
+
if (Is.stringValue(changeset.proofId)) {
|
|
295
|
+
await this._immutableProofComponent.removeVerifiable(changeset.proofId);
|
|
296
|
+
delete changeset.proofId;
|
|
297
|
+
await this._changesetStorage.set(changeset);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
} while (Is.stringValue(changesetsResult.cursor));
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
throw new GeneralError(AuditableItemGraphService.CLASS_NAME, "removeVerifiableFailed", undefined, error);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Query the graph for vertices.
|
|
308
|
+
* @param options The query options.
|
|
309
|
+
* @param options.id The optional id to look for.
|
|
310
|
+
* @param options.idMode Look in id, alias or both, defaults to both.
|
|
311
|
+
* @param options.idExact Find only exact matches, default to false meaning partial matching.
|
|
312
|
+
* @param options.includesResourceTypes Include vertices with specific resource types.
|
|
313
|
+
* @param conditions Conditions to use in the query.
|
|
314
|
+
* @param orderBy The order for the results, defaults to created.
|
|
315
|
+
* @param orderByDirection The direction for the order, defaults to desc.
|
|
316
|
+
* @param properties The properties to return, if not provided defaults to id, created, aliases and object.
|
|
317
|
+
* @param cursor The cursor to request the next chunk of entities.
|
|
318
|
+
* @param limit Limit the number of entities to return.
|
|
319
|
+
* @returns The entities, which can be partial if a limited keys list was provided.
|
|
320
|
+
*/
|
|
321
|
+
async query(options, conditions, orderBy, orderByDirection, properties, cursor, limit) {
|
|
322
|
+
try {
|
|
323
|
+
const propertiesToReturn = properties ?? [
|
|
324
|
+
"id",
|
|
325
|
+
"dateCreated",
|
|
326
|
+
"dateModified",
|
|
327
|
+
"aliases",
|
|
328
|
+
"annotationObject"
|
|
329
|
+
];
|
|
330
|
+
const combinedConditions = conditions ?? [];
|
|
331
|
+
const orderProperty = orderBy ?? "dateCreated";
|
|
332
|
+
const orderDirection = orderByDirection ?? SortDirection.Descending;
|
|
333
|
+
const idExact = options?.idExact ?? false;
|
|
334
|
+
const idOrAlias = options?.id;
|
|
335
|
+
if (Is.stringValue(idOrAlias)) {
|
|
336
|
+
const idMode = options?.idMode ?? "both";
|
|
337
|
+
if (idMode === "id" || idMode === "both") {
|
|
338
|
+
combinedConditions.push({
|
|
339
|
+
property: "id",
|
|
340
|
+
comparison: idExact ? ComparisonOperator.Equals : ComparisonOperator.Includes,
|
|
341
|
+
value: idOrAlias
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
if (idMode === "alias" || idMode === "both") {
|
|
345
|
+
combinedConditions.push({
|
|
346
|
+
property: "aliasIndex",
|
|
347
|
+
comparison: ComparisonOperator.Includes,
|
|
348
|
+
value: idExact ? `||${idOrAlias.toLowerCase()}||` : idOrAlias.toLowerCase()
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (Is.arrayValue(options?.includesResourceTypes)) {
|
|
353
|
+
for (const resourceType of options.includesResourceTypes) {
|
|
354
|
+
combinedConditions.push({
|
|
355
|
+
property: "resourceTypeIndex",
|
|
356
|
+
comparison: ComparisonOperator.Includes,
|
|
357
|
+
value: `||${resourceType.toLowerCase()}||`
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (!propertiesToReturn.includes("id")) {
|
|
362
|
+
propertiesToReturn.unshift("id");
|
|
363
|
+
}
|
|
364
|
+
const results = await this._vertexStorage.query(combinedConditions.length > 0
|
|
365
|
+
? {
|
|
366
|
+
conditions: combinedConditions,
|
|
367
|
+
logicalOperator: LogicalOperator.Or
|
|
368
|
+
}
|
|
369
|
+
: undefined, [
|
|
370
|
+
{
|
|
371
|
+
property: orderProperty,
|
|
372
|
+
sortDirection: orderDirection
|
|
373
|
+
}
|
|
374
|
+
], propertiesToReturn, cursor, limit);
|
|
375
|
+
const models = results.entities.map(e => this.vertexEntityToJsonLd(e));
|
|
376
|
+
const vertexList = {
|
|
377
|
+
"@context": [
|
|
378
|
+
SchemaOrgContexts.ContextRoot,
|
|
379
|
+
AuditableItemGraphContexts.ContextRoot,
|
|
380
|
+
AuditableItemGraphContexts.ContextRootCommon
|
|
381
|
+
],
|
|
382
|
+
type: [SchemaOrgTypes.ItemList, AuditableItemGraphTypes.VertexList],
|
|
383
|
+
[SchemaOrgTypes.ItemListElement]: models,
|
|
384
|
+
[SchemaOrgTypes.NextItem]: results.cursor
|
|
385
|
+
};
|
|
386
|
+
const result = await JsonLdProcessor.compact(vertexList, vertexList["@context"]);
|
|
387
|
+
return result;
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
throw new GeneralError(AuditableItemGraphService.CLASS_NAME, "queryingFailed", undefined, error);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Map the vertex entity to JSON-LD.
|
|
395
|
+
* @param vertexEntity The vertex entity.
|
|
396
|
+
* @returns The model.
|
|
397
|
+
* @internal
|
|
398
|
+
*/
|
|
399
|
+
vertexEntityToJsonLd(vertexEntity) {
|
|
400
|
+
const model = {
|
|
401
|
+
"@context": [
|
|
402
|
+
AuditableItemGraphContexts.ContextRoot,
|
|
403
|
+
AuditableItemGraphContexts.ContextRootCommon,
|
|
404
|
+
SchemaOrgContexts.ContextRoot
|
|
405
|
+
],
|
|
406
|
+
type: AuditableItemGraphTypes.Vertex,
|
|
407
|
+
id: new Urn(AuditableItemGraphService.NAMESPACE, vertexEntity.id).toString(),
|
|
408
|
+
dateCreated: vertexEntity.dateCreated,
|
|
409
|
+
dateModified: vertexEntity.dateModified,
|
|
410
|
+
organizationIdentity: vertexEntity.organizationIdentity,
|
|
411
|
+
annotationObject: vertexEntity.annotationObject
|
|
412
|
+
};
|
|
413
|
+
if (Is.arrayValue(vertexEntity.aliases)) {
|
|
414
|
+
model.aliases ??= [];
|
|
415
|
+
for (const aliasEntity of vertexEntity.aliases) {
|
|
416
|
+
const aliasModel = {
|
|
417
|
+
"@context": [
|
|
418
|
+
AuditableItemGraphContexts.ContextRoot,
|
|
419
|
+
AuditableItemGraphContexts.ContextRootCommon,
|
|
420
|
+
SchemaOrgContexts.ContextRoot
|
|
421
|
+
],
|
|
422
|
+
type: AuditableItemGraphTypes.Alias,
|
|
423
|
+
id: aliasEntity.id,
|
|
424
|
+
aliasFormat: aliasEntity.aliasFormat,
|
|
425
|
+
dateCreated: aliasEntity.dateCreated,
|
|
426
|
+
dateModified: aliasEntity.dateModified,
|
|
427
|
+
dateDeleted: aliasEntity.dateDeleted,
|
|
428
|
+
annotationObject: aliasEntity.annotationObject
|
|
429
|
+
};
|
|
430
|
+
model.aliases.push(aliasModel);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (Is.arrayValue(vertexEntity.resources)) {
|
|
434
|
+
model.resources ??= [];
|
|
435
|
+
for (const resourceEntity of vertexEntity.resources) {
|
|
436
|
+
const resourceModel = {
|
|
437
|
+
"@context": [
|
|
438
|
+
AuditableItemGraphContexts.ContextRoot,
|
|
439
|
+
AuditableItemGraphContexts.ContextRootCommon,
|
|
440
|
+
SchemaOrgContexts.ContextRoot
|
|
441
|
+
],
|
|
442
|
+
type: AuditableItemGraphTypes.Resource,
|
|
443
|
+
id: resourceEntity.id,
|
|
444
|
+
dateCreated: resourceEntity.dateCreated,
|
|
445
|
+
dateModified: resourceEntity.dateModified,
|
|
446
|
+
dateDeleted: resourceEntity.dateDeleted,
|
|
447
|
+
resourceObject: resourceEntity.resourceObject
|
|
448
|
+
};
|
|
449
|
+
model.resources.push(resourceModel);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (Is.arrayValue(vertexEntity.edges)) {
|
|
453
|
+
model.edges ??= [];
|
|
454
|
+
for (const edgeEntity of vertexEntity.edges) {
|
|
455
|
+
const edgeModel = {
|
|
456
|
+
"@context": [
|
|
457
|
+
AuditableItemGraphContexts.ContextRoot,
|
|
458
|
+
AuditableItemGraphContexts.ContextRootCommon,
|
|
459
|
+
SchemaOrgContexts.ContextRoot
|
|
460
|
+
],
|
|
461
|
+
type: AuditableItemGraphTypes.Edge,
|
|
462
|
+
id: this.fullEdgeId(vertexEntity.id, edgeEntity.id),
|
|
463
|
+
targetId: edgeEntity.targetId,
|
|
464
|
+
dateCreated: edgeEntity.dateCreated,
|
|
465
|
+
dateModified: edgeEntity.dateModified,
|
|
466
|
+
dateDeleted: edgeEntity.dateDeleted,
|
|
467
|
+
edgeRelationships: edgeEntity.edgeRelationships,
|
|
468
|
+
annotationObject: edgeEntity.annotationObject
|
|
469
|
+
};
|
|
470
|
+
model.edges.push(edgeModel);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return model;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Map the changeset entity to a JSON-LD.
|
|
477
|
+
* @param vertexId The id of the vertex the changeset belongs to.
|
|
478
|
+
* @param changesetEntity The changeset entity.
|
|
479
|
+
* @returns The model.
|
|
480
|
+
* @internal
|
|
481
|
+
*/
|
|
482
|
+
changesetEntityToJsonLd(vertexId, changesetEntity) {
|
|
483
|
+
const model = {
|
|
484
|
+
"@context": [
|
|
485
|
+
AuditableItemGraphContexts.ContextRoot,
|
|
486
|
+
AuditableItemGraphContexts.ContextRootCommon,
|
|
487
|
+
SchemaOrgContexts.ContextRoot
|
|
488
|
+
],
|
|
489
|
+
type: AuditableItemGraphTypes.Changeset,
|
|
490
|
+
id: new Urn(AuditableItemGraphService.NAMESPACE, [
|
|
491
|
+
vertexId,
|
|
492
|
+
AuditableItemGraphService.NAMESPACE_CHANGESET,
|
|
493
|
+
changesetEntity.id
|
|
494
|
+
]).toString(),
|
|
495
|
+
dateCreated: changesetEntity.dateCreated,
|
|
496
|
+
userIdentity: changesetEntity.userIdentity,
|
|
497
|
+
patches: changesetEntity.patches.map(p => ({
|
|
498
|
+
"@context": [
|
|
499
|
+
AuditableItemGraphContexts.ContextRoot,
|
|
500
|
+
AuditableItemGraphContexts.ContextRootCommon,
|
|
501
|
+
SchemaOrgContexts.ContextRoot
|
|
502
|
+
],
|
|
503
|
+
type: AuditableItemGraphTypes.PatchOperation,
|
|
504
|
+
patchOperation: p.op,
|
|
505
|
+
patchPath: p.path,
|
|
506
|
+
patchFrom: p.from,
|
|
507
|
+
patchValue: p.value
|
|
508
|
+
})),
|
|
509
|
+
proofId: changesetEntity.proofId
|
|
510
|
+
};
|
|
511
|
+
return model;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Update the aliases of a vertex model.
|
|
515
|
+
* @param context The context for the operation.
|
|
516
|
+
* @param vertex The vertex.
|
|
517
|
+
* @param aliases The aliases to update.
|
|
518
|
+
* @internal
|
|
519
|
+
*/
|
|
520
|
+
async updateAliasList(context, vertex, aliases) {
|
|
521
|
+
const active = vertex.aliases?.filter(a => Is.empty(a.dateDeleted)) ?? [];
|
|
522
|
+
// The active aliases that are not in the update list should be marked as deleted.
|
|
523
|
+
if (Is.arrayValue(active)) {
|
|
524
|
+
for (const alias of active) {
|
|
525
|
+
if (!aliases?.find(a => a.id === alias.id)) {
|
|
526
|
+
alias.dateDeleted = context.now;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (Is.arrayValue(aliases)) {
|
|
531
|
+
for (const alias of aliases) {
|
|
532
|
+
await this.updateAlias(context, vertex, alias);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Update an alias in the vertex.
|
|
538
|
+
* @param context The context for the operation.
|
|
539
|
+
* @param vertex The vertex.
|
|
540
|
+
* @param alias The alias.
|
|
541
|
+
* @internal
|
|
542
|
+
*/
|
|
543
|
+
async updateAlias(context, vertex, alias) {
|
|
544
|
+
Guards.object(AuditableItemGraphService.CLASS_NAME, "alias", alias);
|
|
545
|
+
Guards.stringValue(AuditableItemGraphService.CLASS_NAME, "alias.id", alias.id);
|
|
546
|
+
if (alias.unique ?? false) {
|
|
547
|
+
const existingVertices = await this.findMatchingVertices(context, vertex.id, alias.id);
|
|
548
|
+
if (existingVertices) {
|
|
549
|
+
throw new GeneralError(AuditableItemGraphService.CLASS_NAME, "aliasNotUnique", {
|
|
550
|
+
aliasId: alias.id
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (Is.object(alias.annotationObject)) {
|
|
555
|
+
const validationFailures = [];
|
|
556
|
+
await JsonLdHelper.validate(alias.annotationObject, validationFailures);
|
|
557
|
+
Validation.asValidationError(AuditableItemGraphService.CLASS_NAME, "alias.annotationObject", validationFailures);
|
|
558
|
+
}
|
|
559
|
+
// Try to find an existing alias with the same id.
|
|
560
|
+
const existing = vertex.aliases?.find(a => a.id === alias.id);
|
|
561
|
+
if (Is.empty(existing) || !Is.empty(existing?.dateDeleted)) {
|
|
562
|
+
// Did not find a matching item, or found one which is deleted.
|
|
563
|
+
vertex.aliases ??= [];
|
|
564
|
+
const model = {
|
|
565
|
+
id: alias.id,
|
|
566
|
+
aliasFormat: alias.aliasFormat,
|
|
567
|
+
dateCreated: context.now,
|
|
568
|
+
annotationObject: alias.annotationObject
|
|
569
|
+
};
|
|
570
|
+
vertex.aliases.push(model);
|
|
571
|
+
}
|
|
572
|
+
else if (existing.aliasFormat !== alias.aliasFormat ||
|
|
573
|
+
!ObjectHelper.equal(existing.annotationObject, alias.annotationObject, false)) {
|
|
574
|
+
// Existing alias found, update the annotationObject.
|
|
575
|
+
existing.dateModified = context.now;
|
|
576
|
+
existing.aliasFormat = alias.aliasFormat;
|
|
577
|
+
existing.annotationObject = alias.annotationObject;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Update the resources of a vertex.
|
|
582
|
+
* @param context The context for the operation.
|
|
583
|
+
* @param vertex The vertex.
|
|
584
|
+
* @param resources The resources to update.
|
|
585
|
+
* @internal
|
|
586
|
+
*/
|
|
587
|
+
async updateResourceList(context, vertex, resources) {
|
|
588
|
+
if (Is.arrayValue(resources)) {
|
|
589
|
+
for (let i = 0; i < resources.length; i++) {
|
|
590
|
+
const id = this.getResourceId(resources[i]);
|
|
591
|
+
if (Is.empty(id)) {
|
|
592
|
+
throw new GeneralError(AuditableItemGraphService.CLASS_NAME, "resourceIdMissing", {
|
|
593
|
+
index: i
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
const active = vertex.resources?.filter(r => Is.empty(r.dateDeleted)) ?? [];
|
|
599
|
+
// The active resources that are not in the update list should be marked as deleted.
|
|
600
|
+
if (Is.arrayValue(active)) {
|
|
601
|
+
for (const resource of active) {
|
|
602
|
+
if (!resources?.find(a => this.getResourceId(a) === this.getResourceId(resource))) {
|
|
603
|
+
resource.dateDeleted = context.now;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
if (Is.arrayValue(resources)) {
|
|
608
|
+
for (const resource of resources) {
|
|
609
|
+
await this.updateResource(context, vertex, resource);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Add a resource to the vertex.
|
|
615
|
+
* @param context The context for the operation.
|
|
616
|
+
* @param vertex The vertex.
|
|
617
|
+
* @param resource The resource.
|
|
618
|
+
* @internal
|
|
619
|
+
*/
|
|
620
|
+
async updateResource(context, vertex, resource) {
|
|
621
|
+
Guards.object(AuditableItemGraphService.CLASS_NAME, "resource", resource);
|
|
622
|
+
if (Is.object(resource.resourceObject)) {
|
|
623
|
+
const validationFailures = [];
|
|
624
|
+
await JsonLdHelper.validate(resource.resourceObject, validationFailures);
|
|
625
|
+
Validation.asValidationError(AuditableItemGraphService.CLASS_NAME, "resource.resourceObject", validationFailures);
|
|
626
|
+
}
|
|
627
|
+
// Try to find an existing resource with the same id.
|
|
628
|
+
const existing = vertex.resources?.find(r => this.getResourceId(r) === this.getResourceId(resource));
|
|
629
|
+
if (Is.empty(existing) || !Is.empty(existing?.dateDeleted)) {
|
|
630
|
+
// Did not find a matching item, or found one which is deleted.
|
|
631
|
+
vertex.resources ??= [];
|
|
632
|
+
const model = {
|
|
633
|
+
id: resource.id,
|
|
634
|
+
dateCreated: context.now,
|
|
635
|
+
resourceObject: resource.resourceObject
|
|
636
|
+
};
|
|
637
|
+
vertex.resources.push(model);
|
|
638
|
+
}
|
|
639
|
+
else if (!ObjectHelper.equal(existing.resourceObject, resource.resourceObject, false)) {
|
|
640
|
+
// Existing resource found, update the resourceObject.
|
|
641
|
+
existing.dateModified = context.now;
|
|
642
|
+
existing.resourceObject = resource.resourceObject;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Update the edges of a vertex.
|
|
647
|
+
* @param context The context for the operation.
|
|
648
|
+
* @param vertex The vertex.
|
|
649
|
+
* @param edges The edges to update.
|
|
650
|
+
* @internal
|
|
651
|
+
*/
|
|
652
|
+
async updateEdgeList(context, vertex, edges) {
|
|
653
|
+
const active = vertex.edges?.filter(e => Is.empty(e.dateDeleted)) ?? [];
|
|
654
|
+
// The active edges that are not in the update list should be marked as deleted.
|
|
655
|
+
if (Is.arrayValue(active)) {
|
|
656
|
+
for (const edge of active) {
|
|
657
|
+
if (!edges?.find(e => Is.stringValue(e.id) && this.reduceEdgeId(e.id) === edge.id)) {
|
|
658
|
+
edge.dateDeleted = context.now;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (Is.arrayValue(edges)) {
|
|
663
|
+
for (const edge of edges) {
|
|
664
|
+
await this.updateEdge(context, vertex, edge);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Add an edge to the vertex.
|
|
670
|
+
* @param context The context for the operation.
|
|
671
|
+
* @param vertex The vertex.
|
|
672
|
+
* @param edge The edge.
|
|
673
|
+
* @internal
|
|
674
|
+
*/
|
|
675
|
+
async updateEdge(context, vertex, edge) {
|
|
676
|
+
Guards.object(AuditableItemGraphService.CLASS_NAME, "edge", edge);
|
|
677
|
+
Guards.stringValue(AuditableItemGraphService.CLASS_NAME, "edge.targetId", edge.targetId);
|
|
678
|
+
Guards.arrayValue(AuditableItemGraphService.CLASS_NAME, "edge.edgeRelationships", edge.edgeRelationships);
|
|
679
|
+
const validationFailures = [];
|
|
680
|
+
if (edge.targetId === vertex.id) {
|
|
681
|
+
validationFailures.push({
|
|
682
|
+
property: "id",
|
|
683
|
+
reason: `validation.${StringHelper.camelCase(AuditableItemGraphService.CLASS_NAME)}.edgeIdSameAsVertexId`,
|
|
684
|
+
properties: {
|
|
685
|
+
targetId: edge.targetId
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
if (Is.object(edge.annotationObject)) {
|
|
690
|
+
await JsonLdHelper.validate(edge.annotationObject, validationFailures);
|
|
691
|
+
}
|
|
692
|
+
Validation.asValidationError(AuditableItemGraphService.CLASS_NAME, "edge.annotationObject", validationFailures);
|
|
693
|
+
let findId = Is.stringValue(edge.id) ? this.reduceEdgeId(edge.id) : undefined;
|
|
694
|
+
if (Is.empty(findId)) {
|
|
695
|
+
findId = Converter.bytesToHex(RandomHelper.generate(32), false);
|
|
696
|
+
}
|
|
697
|
+
// Try to find an existing edge with the same id.
|
|
698
|
+
const existing = vertex.edges?.find(r => r.id === findId);
|
|
699
|
+
if (Is.empty(existing) || !Is.empty(existing?.dateDeleted)) {
|
|
700
|
+
// Did not find a matching item, or found one which is deleted.
|
|
701
|
+
vertex.edges ??= [];
|
|
702
|
+
const model = {
|
|
703
|
+
id: findId,
|
|
704
|
+
targetId: edge.targetId,
|
|
705
|
+
dateCreated: context.now,
|
|
706
|
+
annotationObject: edge.annotationObject,
|
|
707
|
+
edgeRelationships: edge.edgeRelationships
|
|
708
|
+
};
|
|
709
|
+
vertex.edges.push(model);
|
|
710
|
+
}
|
|
711
|
+
else if (existing.targetId !== edge.targetId ||
|
|
712
|
+
!ArrayHelper.matches(existing.edgeRelationships, edge.edgeRelationships) ||
|
|
713
|
+
!ObjectHelper.equal(existing.annotationObject, edge.annotationObject, false)) {
|
|
714
|
+
// Existing edge found, update the properties.
|
|
715
|
+
existing.targetId = edge.targetId;
|
|
716
|
+
existing.dateModified = context.now;
|
|
717
|
+
existing.edgeRelationships = edge.edgeRelationships;
|
|
718
|
+
existing.annotationObject = edge.annotationObject;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Add a changeset to the vertex and generate the associated verifications.
|
|
723
|
+
* @param context The context for the operation.
|
|
724
|
+
* @param original The original vertex.
|
|
725
|
+
* @param updated The updated vertex.
|
|
726
|
+
* @param isNew Whether this is a new item.
|
|
727
|
+
* @returns True if there were changes.
|
|
728
|
+
* @internal
|
|
729
|
+
*/
|
|
730
|
+
async addChangeset(context, original, updated, isNew) {
|
|
731
|
+
const patches = JsonHelper.diff(original, updated);
|
|
732
|
+
// If there is a diff set or this is the first time the item is created.
|
|
733
|
+
if (patches.length > 0 || isNew) {
|
|
734
|
+
const changesetEntity = {
|
|
735
|
+
id: Converter.bytesToHex(RandomHelper.generate(32), false),
|
|
736
|
+
vertexId: updated.id,
|
|
737
|
+
dateCreated: context.now,
|
|
738
|
+
userIdentity: context.contextIds?.[ContextIdKeys.User],
|
|
739
|
+
patches
|
|
740
|
+
};
|
|
741
|
+
// Create the JSON-LD object we want to use for the proof
|
|
742
|
+
// this is a subset of fixed properties from the changeset object.
|
|
743
|
+
const reducedChangesetJsonLd = this.changesetEntityToJsonLd(original.id, ObjectHelper.pick(changesetEntity, AuditableItemGraphService._PROOF_KEYS_CHANGESET));
|
|
744
|
+
// Create the proof for the changeset object
|
|
745
|
+
changesetEntity.proofId = await this._immutableProofComponent.create(reducedChangesetJsonLd);
|
|
746
|
+
// Link the verifiable storage id to the changeset
|
|
747
|
+
await this._changesetStorage.set(changesetEntity);
|
|
748
|
+
return patches;
|
|
749
|
+
}
|
|
750
|
+
return [];
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Verify the changesets of a vertex.
|
|
754
|
+
* @param vertex The vertex to verify.
|
|
755
|
+
* @param verifySignatureDepth How many signatures to verify.
|
|
756
|
+
* @param contextIds The context ids to perform the operation with.
|
|
757
|
+
* @internal
|
|
758
|
+
*/
|
|
759
|
+
async verifyChangesets(vertex, verifySignatureDepth, partitionKey) {
|
|
760
|
+
const changesets = [];
|
|
761
|
+
let changesetsResult;
|
|
762
|
+
let verified = true;
|
|
763
|
+
const vertexId = Urn.fromValidString(vertex.id);
|
|
764
|
+
do {
|
|
765
|
+
changesetsResult = await this._changesetStorage.query({
|
|
766
|
+
property: "vertexId",
|
|
767
|
+
value: vertexId.namespaceSpecific(),
|
|
768
|
+
comparison: ComparisonOperator.Equals
|
|
769
|
+
}, [
|
|
770
|
+
{
|
|
771
|
+
property: "dateCreated",
|
|
772
|
+
sortDirection: SortDirection.Ascending
|
|
773
|
+
}
|
|
774
|
+
], undefined, changesetsResult?.cursor);
|
|
775
|
+
const storedChangesets = changesetsResult.entities;
|
|
776
|
+
if (Is.arrayValue(storedChangesets)) {
|
|
777
|
+
for (let i = 0; i < storedChangesets.length; i++) {
|
|
778
|
+
const storedChangeset = storedChangesets[i];
|
|
779
|
+
const storedChangesetJsonLd = this.changesetEntityToJsonLd(vertexId.namespaceSpecific(), storedChangeset);
|
|
780
|
+
changesets.push(storedChangesetJsonLd);
|
|
781
|
+
// If we are verifying all signatures
|
|
782
|
+
// or this is the last changeset (cursor is empty)
|
|
783
|
+
// and the changeset has a proofId, then verify the proof.
|
|
784
|
+
if (verifySignatureDepth === VerifyDepth.All ||
|
|
785
|
+
(verifySignatureDepth === VerifyDepth.Current &&
|
|
786
|
+
!Is.stringValue(changesetsResult.cursor) &&
|
|
787
|
+
i === storedChangesets.length - 1)) {
|
|
788
|
+
if (!Is.stringValue(storedChangeset.proofId)) {
|
|
789
|
+
verified = false;
|
|
790
|
+
storedChangesetJsonLd.verification = {
|
|
791
|
+
"@context": ImmutableProofContexts.ContextRoot,
|
|
792
|
+
type: ImmutableProofTypes.ImmutableProofVerification,
|
|
793
|
+
verified: false,
|
|
794
|
+
failure: ImmutableProofFailure.ProofMissing
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
// Verify the proof for the changeset object
|
|
799
|
+
storedChangesetJsonLd.verification = await this._immutableProofComponent.verify(storedChangeset.proofId);
|
|
800
|
+
if (!storedChangesetJsonLd.verification.verified) {
|
|
801
|
+
verified = false;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
} while (Is.stringValue(changesetsResult.cursor));
|
|
808
|
+
return {
|
|
809
|
+
verified,
|
|
810
|
+
changesets
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Get the resource id from a resource object.
|
|
815
|
+
* @param resource The resource.
|
|
816
|
+
* @param resource.id The id of the resource.
|
|
817
|
+
* @param resource.resourceObject The resource object.
|
|
818
|
+
* @returns The resource id if it can find one.
|
|
819
|
+
*/
|
|
820
|
+
getResourceId(resource) {
|
|
821
|
+
return (resource.id ??
|
|
822
|
+
ObjectHelper.extractProperty(resource.resourceObject, ["id", "@id"], false));
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Build the indexes for the vertex.
|
|
826
|
+
* @param vertex The vertex to build the indexes for.
|
|
827
|
+
* @returns The indexes.
|
|
828
|
+
* @internal
|
|
829
|
+
*/
|
|
830
|
+
buildIndexes(vertex) {
|
|
831
|
+
const aliasIndex = vertex.aliases
|
|
832
|
+
?.filter(a => Is.empty(a.dateDeleted))
|
|
833
|
+
.map(a => a.id)
|
|
834
|
+
.join("||")
|
|
835
|
+
.toLowerCase();
|
|
836
|
+
const resourceTypes = [];
|
|
837
|
+
if (Is.arrayValue(vertex.resources)) {
|
|
838
|
+
for (const resource of vertex.resources) {
|
|
839
|
+
const resourceType = ObjectHelper.extractProperty(resource.resourceObject, ["@type", "type"], false);
|
|
840
|
+
if (Is.stringValue(resourceType) && !resourceTypes.includes(resourceType)) {
|
|
841
|
+
resourceTypes.push(resourceType);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
const resourceTypeIndex = resourceTypes.join("||").toLowerCase();
|
|
846
|
+
return {
|
|
847
|
+
aliasIndex: Is.stringValue(aliasIndex) ? `||${aliasIndex}||` : undefined,
|
|
848
|
+
resourceTypeIndex: Is.stringValue(resourceTypeIndex) ? `||${resourceTypeIndex}||` : undefined
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Find vertices with matching aliases.
|
|
853
|
+
* @param context The context for the operation.
|
|
854
|
+
* @param vertexId The id of the vertex to exclude from the search.
|
|
855
|
+
* @param aliasId The alias id to try and find.
|
|
856
|
+
* @returns True if any other vertices have matching aliases.
|
|
857
|
+
* @internal
|
|
858
|
+
*/
|
|
859
|
+
async findMatchingVertices(context, vertexId, aliasId) {
|
|
860
|
+
const results = await this._vertexStorage.query({
|
|
861
|
+
conditions: [
|
|
862
|
+
{
|
|
863
|
+
property: "aliasIndex",
|
|
864
|
+
comparison: ComparisonOperator.Includes,
|
|
865
|
+
value: `||${aliasId.toLowerCase()}||`
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
property: "id",
|
|
869
|
+
value: vertexId,
|
|
870
|
+
comparison: ComparisonOperator.NotEquals
|
|
871
|
+
}
|
|
872
|
+
],
|
|
873
|
+
logicalOperator: LogicalOperator.And
|
|
874
|
+
});
|
|
875
|
+
return results.entities.length > 0;
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Reduce the edge ID from a URN.
|
|
879
|
+
* @param urn The URN to reduce.
|
|
880
|
+
* @returns The edge ID.
|
|
881
|
+
* @throws GeneralError if the URN is not valid or not an edge URN.
|
|
882
|
+
* @internal
|
|
883
|
+
*/
|
|
884
|
+
reduceEdgeId(urn) {
|
|
885
|
+
const urnParsed = Urn.fromValidString(urn);
|
|
886
|
+
if (urnParsed.namespaceIdentifier() !== AuditableItemGraphService.NAMESPACE) {
|
|
887
|
+
throw new GeneralError(AuditableItemGraphService.CLASS_NAME, "namespaceMismatch", {
|
|
888
|
+
namespace: AuditableItemGraphService.NAMESPACE,
|
|
889
|
+
id: urn
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
if (urnParsed.namespaceSpecificParts().length !== 3) {
|
|
893
|
+
throw new GeneralError(AuditableItemGraphService.CLASS_NAME, "invalidEdgeId", {
|
|
894
|
+
edgeId: urn
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
if (urnParsed.namespaceSpecificParts()[1] !== AuditableItemGraphService.NAMESPACE_EDGE) {
|
|
898
|
+
throw new GeneralError(AuditableItemGraphService.CLASS_NAME, "invalidEdgeId", {
|
|
899
|
+
edgeId: urn
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
return urnParsed.namespaceSpecificParts()[2];
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Create a full edge ID URN from a vertex ID and edge ID.
|
|
906
|
+
* @param vertexId The vertex id the edge belongs to.
|
|
907
|
+
* @param edgeId The edge id.
|
|
908
|
+
* @returns The full edge ID URN.
|
|
909
|
+
* @internal
|
|
910
|
+
*/
|
|
911
|
+
fullEdgeId(vertexId, edgeId) {
|
|
912
|
+
return new Urn(AuditableItemGraphService.NAMESPACE, [
|
|
913
|
+
vertexId,
|
|
914
|
+
AuditableItemGraphService.NAMESPACE_EDGE,
|
|
915
|
+
edgeId
|
|
916
|
+
]).toString();
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
//# sourceMappingURL=auditableItemGraphService.js.map
|