mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Rework ResourceStore to return extra info
* feat: change return types in ResourceStore.ts
* feat: change return types in BaseResourceStore.ts
* feat: change return types in LockingResourceStore.ts
* feat: change return types in RoutingResourceStore.ts
* feat: change return types in MonitoringStore.ts
* feat: change return types in PassthroughStore.ts
* feat: change return types in ReadOnlyStore.ts
* feat: change return types in PatchHandler.ts
* feat: change return types in PatchingStore.ts
* feat: change return types in RepresentationPatchHandler.ts
* feat: create createResourceIdentifier() function for convenience
* feat: adapt PostOperationHandler.ts to new typing
* feat: change return types in RepresentationConvertingStore.ts
* feat: adapt DataAccessorBasedStore.ts implementation to new typings
* feat: adapt UnsecureWebSocketsProtocol.ts to new typing
* chore: add temporary comments
* fix: return correct Location header on POST request with slug
* fix: npm run lint command needs more packages
* fix: linting errors
* chore: revert ed9952b
* test: adapt PostOperationHandler tests
* test: adapt UnsecureWebSocketsProtocol tests
* test: adapt DataAccessorBasedStore tests
* fix: linting errors
* feat: emit specific created, deleted, updated events in MonitoringStore
* test: adapt RepresentationPatchHandler tests
* fix: revert UnsecureWebSocketsProtocol changes
* feat: emit extra parameter on changed
* test: adapt MonitoringStore tests
* fix: linting errors
* test: add test to MonitorStore.test for coverage
* fix: linting error
* chore: update doc in ResourceStore.ts
* test: improve MonitoringStore tests
* chore: update RELEASE_NOTES.md
* chore: add extra info about the MonitoringStore to documentation/resource-store.md
* chore: Update RELEASE_NOTES.md
Co-authored-by: Anton Wiklund <ixuz07@gmail.com>
* chore: Update documentation/resource-store.md
Co-authored-by: Anton Wiklund <ixuz07@gmail.com>
* chore: very small changes
* chore: simplify metadata creation
* fix: DataAccessorBasedStore improvement and bugfix
* chore: improve resource-store.md
* chore: adapt MonitoringStore event names, update docs and apply code suggestion
* chore: use ResourceStoreResponse type
* fix: typo
* chore: rename ResourceStoreResponse type to ChangeMap
* chore: adapt .gitignore to name change
Co-authored-by: Anton Wiklund <ixuz07@gmail.com>
This commit is contained in:
@@ -2,8 +2,10 @@ import { getLoggerFor } from '../../logging/LogUtil';
|
||||
import type { ResourceStore } from '../../storage/ResourceStore';
|
||||
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
|
||||
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
|
||||
import { AS, SOLID_AS } from '../../util/Vocabularies';
|
||||
import { CreatedResponseDescription } from '../output/response/CreatedResponseDescription';
|
||||
import type { ResponseDescription } from '../output/response/ResponseDescription';
|
||||
import { createResourceIdentifier } from '../representation/ResourceIdentifier';
|
||||
import type { OperationHandlerInput } from './OperationHandler';
|
||||
import { OperationHandler } from './OperationHandler';
|
||||
|
||||
@@ -35,7 +37,10 @@ export class PostOperationHandler extends OperationHandler {
|
||||
this.logger.warn('POST requests require the Content-Type header to be set');
|
||||
throw new BadRequestHttpError('POST requests require the Content-Type header to be set');
|
||||
}
|
||||
const identifier = await this.store.addResource(operation.target, operation.body, operation.conditions);
|
||||
return new CreatedResponseDescription(identifier);
|
||||
const result = await this.store.addResource(operation.target, operation.body, operation.conditions);
|
||||
const createdIdentifier = Object.entries(result).find(
|
||||
([ , value ]): boolean => value.get(SOLID_AS.terms.Activity)?.value === AS.Create,
|
||||
)![0];
|
||||
return new CreatedResponseDescription(createResourceIdentifier(createdIdentifier));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,15 @@ export interface ResourceIdentifier {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the object is a `ResourceIdentifier`.
|
||||
* Determines whether the object is a {@link ResourceIdentifier}.
|
||||
*/
|
||||
export function isResourceIdentifier(object: any): object is ResourceIdentifier {
|
||||
return object && (typeof object.path === 'string');
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory function creating a {@link ResourceIdentifier} for convenience.
|
||||
*/
|
||||
export function createResourceIdentifier(resourcePath: string): ResourceIdentifier {
|
||||
return { path: resourcePath };
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { RepresentationPreferences } from '../http/representation/Represent
|
||||
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
|
||||
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
|
||||
import type { Conditions } from './Conditions';
|
||||
import type { ResourceStore } from './ResourceStore';
|
||||
import type { ResourceStore, ChangeMap } from './ResourceStore';
|
||||
|
||||
/**
|
||||
* Base implementation of ResourceStore for implementers of custom stores.
|
||||
@@ -21,22 +21,22 @@ export class BaseResourceStore implements ResourceStore {
|
||||
}
|
||||
|
||||
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
throw new NotImplementedHttpError();
|
||||
}
|
||||
|
||||
public async addResource(container: ResourceIdentifier, representation: Representation,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
throw new NotImplementedHttpError();
|
||||
}
|
||||
|
||||
public async deleteResource(identifier: ResourceIdentifier,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
throw new NotImplementedHttpError();
|
||||
}
|
||||
|
||||
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
throw new NotImplementedHttpError();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { AuxiliaryStrategy } from '../http/auxiliary/AuxiliaryStrategy';
|
||||
import { BasicRepresentation } from '../http/representation/BasicRepresentation';
|
||||
import type { Patch } from '../http/representation/Patch';
|
||||
import type { Representation } from '../http/representation/Representation';
|
||||
import type { RepresentationMetadata } from '../http/representation/RepresentationMetadata';
|
||||
import { RepresentationMetadata } from '../http/representation/RepresentationMetadata';
|
||||
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
|
||||
import { getLoggerFor } from '../logging/LogUtil';
|
||||
import { INTERNAL_QUADS } from '../util/ContentTypes';
|
||||
@@ -39,10 +39,12 @@ import {
|
||||
SOLID_META,
|
||||
PREFERRED_PREFIX_TERM,
|
||||
CONTENT_TYPE_TERM,
|
||||
SOLID_AS,
|
||||
AS,
|
||||
} from '../util/Vocabularies';
|
||||
import type { DataAccessor } from './accessors/DataAccessor';
|
||||
import type { Conditions } from './Conditions';
|
||||
import type { ResourceStore } from './ResourceStore';
|
||||
import type { ResourceStore, ChangeMap } from './ResourceStore';
|
||||
|
||||
/**
|
||||
* ResourceStore which uses a DataAccessor for backend access.
|
||||
@@ -138,7 +140,7 @@ export class DataAccessorBasedStore implements ResourceStore {
|
||||
}
|
||||
|
||||
public async addResource(container: ResourceIdentifier, representation: Representation, conditions?: Conditions):
|
||||
Promise<ResourceIdentifier> {
|
||||
Promise<ChangeMap> {
|
||||
this.validateIdentifier(container);
|
||||
|
||||
const parentMetadata = await this.getSafeNormalizedMetadata(container);
|
||||
@@ -174,13 +176,11 @@ export class DataAccessorBasedStore implements ResourceStore {
|
||||
}
|
||||
|
||||
// Write the data. New containers should never be made for a POST request.
|
||||
await this.writeData(newID, representation, isContainer, false, false);
|
||||
|
||||
return newID;
|
||||
return this.writeData(newID, representation, isContainer, false, false);
|
||||
}
|
||||
|
||||
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
this.validateIdentifier(identifier);
|
||||
|
||||
// Check if the resource already exists
|
||||
@@ -216,7 +216,7 @@ export class DataAccessorBasedStore implements ResourceStore {
|
||||
}
|
||||
|
||||
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<never> {
|
||||
if (conditions) {
|
||||
let metadata: RepresentationMetadata | undefined;
|
||||
try {
|
||||
@@ -233,7 +233,7 @@ export class DataAccessorBasedStore implements ResourceStore {
|
||||
throw new NotImplementedHttpError('Patches are not supported by the default store.');
|
||||
}
|
||||
|
||||
public async deleteResource(identifier: ResourceIdentifier, conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
public async deleteResource(identifier: ResourceIdentifier, conditions?: Conditions): Promise<ChangeMap> {
|
||||
this.validateIdentifier(identifier);
|
||||
const metadata = await this.accessor.getMetadata(identifier);
|
||||
// Solid, §5.4: "When a DELETE request targets storage’s root container or its associated ACL resource,
|
||||
@@ -266,22 +266,25 @@ export class DataAccessorBasedStore implements ResourceStore {
|
||||
// Solid, §5.4: "When a contained resource is deleted,
|
||||
// the server MUST also delete the associated auxiliary resources"
|
||||
// https://solid.github.io/specification/protocol#deleting-resources
|
||||
const deleted = [ identifier ];
|
||||
const changes: ChangeMap = {};
|
||||
if (!this.auxiliaryStrategy.isAuxiliaryIdentifier(identifier)) {
|
||||
const auxiliaries = this.auxiliaryStrategy.getAuxiliaryIdentifiers(identifier);
|
||||
deleted.push(...await this.safelyDeleteAuxiliaryResources(auxiliaries));
|
||||
for (const deletedId of await this.safelyDeleteAuxiliaryResources(auxiliaries)) {
|
||||
changes[deletedId.path] = this.createActivityMetadata(deletedId, AS.Delete);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.identifierStrategy.isRootContainer(identifier)) {
|
||||
const container = this.identifierStrategy.getParentContainer(identifier);
|
||||
deleted.push(container);
|
||||
changes[container.path] = this.createActivityMetadata(container, AS.Update);
|
||||
|
||||
// Update modified date of parent
|
||||
await this.updateContainerModifiedDate(container);
|
||||
}
|
||||
|
||||
await this.accessor.deleteResource(identifier);
|
||||
return deleted;
|
||||
changes[identifier.path] = this.createActivityMetadata(identifier, AS.Delete);
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -359,7 +362,7 @@ export class DataAccessorBasedStore implements ResourceStore {
|
||||
* @returns Identifiers of resources that were possibly modified.
|
||||
*/
|
||||
protected async writeData(identifier: ResourceIdentifier, representation: Representation, isContainer: boolean,
|
||||
createContainers: boolean, exists: boolean): Promise<ResourceIdentifier[]> {
|
||||
createContainers: boolean, exists: boolean): Promise<ChangeMap> {
|
||||
// Make sure the metadata has the correct identifier and correct type quads
|
||||
// Need to do this before handling container data to have the correct identifier
|
||||
representation.metadata.identifier = DataFactory.namedNode(identifier.path);
|
||||
@@ -382,18 +385,22 @@ export class DataAccessorBasedStore implements ResourceStore {
|
||||
// Solid, §5.3: "Servers MUST create intermediate containers and include corresponding containment triples
|
||||
// in container representations derived from the URI path component of PUT and PATCH requests."
|
||||
// https://solid.github.io/specification/protocol#writing-resources
|
||||
const modified = [];
|
||||
let changes: ChangeMap = {};
|
||||
if (!this.identifierStrategy.isRootContainer(identifier) && !exists) {
|
||||
const container = this.identifierStrategy.getParentContainer(identifier);
|
||||
const parent = this.identifierStrategy.getParentContainer(identifier);
|
||||
if (!createContainers) {
|
||||
modified.push(container);
|
||||
changes[parent.path] = this.createActivityMetadata(parent, AS.Update);
|
||||
} else {
|
||||
const created = await this.createRecursiveContainers(container);
|
||||
modified.push(...created.length === 0 ? [ container ] : created);
|
||||
const createdContainers = await this.createRecursiveContainers(parent);
|
||||
changes = { ...changes, ...createdContainers };
|
||||
|
||||
if (Object.keys(createdContainers).length === 0) {
|
||||
changes[parent.path] = this.createActivityMetadata(parent, AS.Update);
|
||||
}
|
||||
}
|
||||
|
||||
// Parent container is also modified
|
||||
await this.updateContainerModifiedDate(container);
|
||||
await this.updateContainerModifiedDate(parent);
|
||||
}
|
||||
|
||||
// Remove all generated metadata to prevent it from being stored permanently
|
||||
@@ -403,7 +410,8 @@ export class DataAccessorBasedStore implements ResourceStore {
|
||||
this.accessor.writeContainer(identifier, representation.metadata) :
|
||||
this.accessor.writeDocument(identifier, representation.data, representation.metadata));
|
||||
|
||||
return [ ...modified, identifier ];
|
||||
changes[identifier.path] = this.createActivityMetadata(identifier, exists ? AS.Update : AS.Create);
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -597,7 +605,7 @@ export class DataAccessorBasedStore implements ResourceStore {
|
||||
* Will throw errors if the identifier of the last existing "container" corresponds to an existing document.
|
||||
* @param container - Identifier of the container which will need to exist.
|
||||
*/
|
||||
protected async createRecursiveContainers(container: ResourceIdentifier): Promise<ResourceIdentifier[]> {
|
||||
protected async createRecursiveContainers(container: ResourceIdentifier): Promise<ChangeMap> {
|
||||
// Verify whether the container already exists
|
||||
try {
|
||||
const metadata = await this.getNormalizedMetadata(container);
|
||||
@@ -609,7 +617,7 @@ export class DataAccessorBasedStore implements ResourceStore {
|
||||
if (!isContainerPath(metadata.identifier.value)) {
|
||||
throw new ForbiddenHttpError(`Creating container ${container.path} conflicts with an existing resource.`);
|
||||
}
|
||||
return [];
|
||||
return {};
|
||||
} catch (error: unknown) {
|
||||
if (!NotFoundHttpError.isInstance(error)) {
|
||||
throw error;
|
||||
@@ -618,9 +626,14 @@ export class DataAccessorBasedStore implements ResourceStore {
|
||||
|
||||
// Create the container, starting with its parent
|
||||
const ancestors = this.identifierStrategy.isRootContainer(container) ?
|
||||
[] :
|
||||
{} :
|
||||
await this.createRecursiveContainers(this.identifierStrategy.getParentContainer(container));
|
||||
await this.writeData(container, new BasicRepresentation([], container), true, false, false);
|
||||
return [ ...ancestors, container ];
|
||||
const changes = await this.writeData(container, new BasicRepresentation([], container), true, false, false);
|
||||
|
||||
return { ...changes, ...ancestors };
|
||||
}
|
||||
|
||||
private createActivityMetadata(id: ResourceIdentifier, activity: string): RepresentationMetadata {
|
||||
return new RepresentationMetadata(id, { [SOLID_AS.terms.Activity.value]: activity });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { ExpiringReadWriteLocker } from '../util/locking/ExpiringReadWriteL
|
||||
import { endOfStream } from '../util/StreamUtil';
|
||||
import type { AtomicResourceStore } from './AtomicResourceStore';
|
||||
import type { Conditions } from './Conditions';
|
||||
import type { ResourceStore } from './ResourceStore';
|
||||
import type { ResourceStore, ChangeMap } from './ResourceStore';
|
||||
|
||||
/**
|
||||
* Store that for every call acquires a lock before executing it on the requested resource,
|
||||
@@ -46,27 +46,27 @@ export class LockingResourceStore implements AtomicResourceStore {
|
||||
}
|
||||
|
||||
public async addResource(container: ResourceIdentifier, representation: Representation,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
return this.locks.withWriteLock(this.getLockIdentifier(container),
|
||||
async(): Promise<ResourceIdentifier> => this.source.addResource(container, representation, conditions));
|
||||
async(): Promise<ChangeMap> => this.source.addResource(container, representation, conditions));
|
||||
}
|
||||
|
||||
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
return this.locks.withWriteLock(this.getLockIdentifier(identifier),
|
||||
async(): Promise<ResourceIdentifier[]> => this.source.setRepresentation(identifier, representation, conditions));
|
||||
async(): Promise<ChangeMap> => this.source.setRepresentation(identifier, representation, conditions));
|
||||
}
|
||||
|
||||
public async deleteResource(identifier: ResourceIdentifier,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
return this.locks.withWriteLock(this.getLockIdentifier(identifier),
|
||||
async(): Promise<ResourceIdentifier[]> => this.source.deleteResource(identifier, conditions));
|
||||
async(): Promise<ChangeMap> => this.source.deleteResource(identifier, conditions));
|
||||
}
|
||||
|
||||
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
return this.locks.withWriteLock(this.getLockIdentifier(identifier),
|
||||
async(): Promise<ResourceIdentifier[]> => this.source.modifyResource(identifier, patch, conditions));
|
||||
async(): Promise<ChangeMap> => this.source.modifyResource(identifier, patch, conditions));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,8 +3,9 @@ import type { Patch } from '../http/representation/Patch';
|
||||
import type { Representation } from '../http/representation/Representation';
|
||||
import type { RepresentationPreferences } from '../http/representation/RepresentationPreferences';
|
||||
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
|
||||
import { AS, SOLID_AS } from '../util/Vocabularies';
|
||||
import type { Conditions } from './Conditions';
|
||||
import type { ResourceStore } from './ResourceStore';
|
||||
import type { ResourceStore, ChangeMap } from './ResourceStore';
|
||||
|
||||
/**
|
||||
* Store that notifies listeners of changes to its source
|
||||
@@ -29,31 +30,34 @@ export class MonitoringStore<T extends ResourceStore = ResourceStore>
|
||||
}
|
||||
|
||||
public async addResource(container: ResourceIdentifier, representation: Representation,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier> {
|
||||
const identifier = await this.source.addResource(container, representation, conditions);
|
||||
this.emitChanged([ container, identifier ]);
|
||||
return identifier;
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
return this.emitChanged(await this.source.addResource(container, representation, conditions));
|
||||
}
|
||||
|
||||
public async deleteResource(identifier: ResourceIdentifier,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
return this.emitChanged(await this.source.deleteResource(identifier, conditions));
|
||||
}
|
||||
|
||||
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
return this.emitChanged(await this.source.setRepresentation(identifier, representation, conditions));
|
||||
}
|
||||
|
||||
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
return this.emitChanged(await this.source.modifyResource(identifier, patch, conditions));
|
||||
}
|
||||
|
||||
private emitChanged(identifiers: ResourceIdentifier[]): typeof identifiers {
|
||||
for (const identifier of identifiers) {
|
||||
this.emit('changed', identifier);
|
||||
private emitChanged(changes: ChangeMap): ChangeMap {
|
||||
for (const [ key, value ] of Object.entries(changes)) {
|
||||
const activity = value.get(SOLID_AS.terms.Activity)?.value;
|
||||
this.emit('changed', { path: key }, activity);
|
||||
if (activity && [ AS.Create, AS.Delete, AS.Update ].includes(activity)) {
|
||||
this.emit(activity, { path: key });
|
||||
}
|
||||
}
|
||||
return identifiers;
|
||||
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Representation } from '../http/representation/Representation';
|
||||
import type { RepresentationPreferences } from '../http/representation/RepresentationPreferences';
|
||||
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
|
||||
import type { Conditions } from './Conditions';
|
||||
import type { ResourceStore } from './ResourceStore';
|
||||
import type { ResourceStore, ChangeMap } from './ResourceStore';
|
||||
|
||||
/**
|
||||
* Store that calls the corresponding functions of the source Store.
|
||||
@@ -27,22 +27,22 @@ export class PassthroughStore<T extends ResourceStore = ResourceStore> implement
|
||||
}
|
||||
|
||||
public async addResource(container: ResourceIdentifier, representation: Representation,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
return this.source.addResource(container, representation, conditions);
|
||||
}
|
||||
|
||||
public async deleteResource(identifier: ResourceIdentifier,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
return this.source.deleteResource(identifier, conditions);
|
||||
}
|
||||
|
||||
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
return this.source.modifyResource(identifier, patch, conditions);
|
||||
}
|
||||
|
||||
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
return this.source.setRepresentation(identifier, representation, conditions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError'
|
||||
import type { Conditions } from './Conditions';
|
||||
import { PassthroughStore } from './PassthroughStore';
|
||||
import type { PatchHandler } from './patch/PatchHandler';
|
||||
import type { ResourceStore } from './ResourceStore';
|
||||
import type { ResourceStore, ChangeMap } from './ResourceStore';
|
||||
|
||||
/**
|
||||
* {@link ResourceStore} using decorator pattern for the `modifyResource` function.
|
||||
@@ -20,7 +20,7 @@ export class PatchingStore<T extends ResourceStore = ResourceStore> extends Pass
|
||||
}
|
||||
|
||||
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
try {
|
||||
return await this.source.modifyResource(identifier, patch, conditions);
|
||||
} catch (error: unknown) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { ResourceIdentifier } from '../http/representation/ResourceIdentifi
|
||||
import { ForbiddenHttpError } from '../util/errors/ForbiddenHttpError';
|
||||
import type { Conditions } from './Conditions';
|
||||
import { PassthroughStore } from './PassthroughStore';
|
||||
import type { ResourceStore } from './ResourceStore';
|
||||
import type { ResourceStore, ChangeMap } from './ResourceStore';
|
||||
|
||||
/**
|
||||
* Store that only allow read operations on the underlying source.
|
||||
@@ -16,22 +16,22 @@ export class ReadOnlyStore<T extends ResourceStore = ResourceStore> extends Pass
|
||||
}
|
||||
|
||||
public async addResource(container: ResourceIdentifier, representation: Representation,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
throw new ForbiddenHttpError();
|
||||
}
|
||||
|
||||
public async deleteResource(identifier: ResourceIdentifier,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
throw new ForbiddenHttpError();
|
||||
}
|
||||
|
||||
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
throw new ForbiddenHttpError();
|
||||
}
|
||||
|
||||
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
throw new ForbiddenHttpError();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { Conditions } from './Conditions';
|
||||
import { PassthroughConverter } from './conversion/PassthroughConverter';
|
||||
import type { RepresentationConverter } from './conversion/RepresentationConverter';
|
||||
import { PassthroughStore } from './PassthroughStore';
|
||||
import type { ResourceStore } from './ResourceStore';
|
||||
import type { ResourceStore, ChangeMap } from './ResourceStore';
|
||||
|
||||
/**
|
||||
* Store that provides (optional) conversion of incoming and outgoing {@link Representation}s.
|
||||
@@ -40,7 +40,7 @@ export class RepresentationConvertingStore<T extends ResourceStore = ResourceSto
|
||||
}
|
||||
|
||||
public async addResource(identifier: ResourceIdentifier, representation: Representation,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
// We can potentially run into problems here if we convert a turtle document where the base IRI is required,
|
||||
// since we don't know the resource IRI yet at this point.
|
||||
representation = await this.inConverter.handleSafe({ identifier, representation, preferences: this.inPreferences });
|
||||
@@ -48,7 +48,7 @@ export class RepresentationConvertingStore<T extends ResourceStore = ResourceSto
|
||||
}
|
||||
|
||||
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
representation = await this.inConverter.handleSafe({ identifier, representation, preferences: this.inPreferences });
|
||||
return this.source.setRepresentation(identifier, representation, conditions);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import type { Patch } from '../http/representation/Patch';
|
||||
import type { Representation } from '../http/representation/Representation';
|
||||
import type { RepresentationMetadata } from '../http/representation/RepresentationMetadata';
|
||||
import type { RepresentationPreferences } from '../http/representation/RepresentationPreferences';
|
||||
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
|
||||
import type { Conditions } from './Conditions';
|
||||
import type { ResourceSet } from './ResourceSet';
|
||||
|
||||
/**
|
||||
* An object containing one property for each resource that was created, updated or deleted
|
||||
* by this operation. Where the key of the property is the path of the resource (string) and the value is an
|
||||
* instance of RepresentationMetadata containing extra information about the change of the resource.
|
||||
*/
|
||||
export type ChangeMap = Record<string, RepresentationMetadata>;
|
||||
|
||||
/**
|
||||
* A ResourceStore represents a collection of resources.
|
||||
* It has been designed such that each of its methods
|
||||
@@ -38,13 +46,13 @@ export interface ResourceStore extends ResourceSet {
|
||||
* @param representation - New representation of the resource.
|
||||
* @param conditions - Optional conditions under which to proceed.
|
||||
*
|
||||
* @returns Identifiers of resources that were possibly modified.
|
||||
* @returns A {@link ChangeMap}.
|
||||
*/
|
||||
setRepresentation: (
|
||||
identifier: ResourceIdentifier,
|
||||
representation: Representation,
|
||||
conditions?: Conditions,
|
||||
) => Promise<ResourceIdentifier[]>;
|
||||
) => Promise<ChangeMap>;
|
||||
|
||||
/**
|
||||
* Creates a new resource in the container.
|
||||
@@ -52,25 +60,25 @@ export interface ResourceStore extends ResourceSet {
|
||||
* @param representation - Representation of the new resource
|
||||
* @param conditions - Optional conditions under which to proceed.
|
||||
*
|
||||
* @returns The identifier of the newly created resource.
|
||||
* @returns A {@link ChangeMap}.
|
||||
*/
|
||||
addResource: (
|
||||
container: ResourceIdentifier,
|
||||
representation: Representation,
|
||||
conditions?: Conditions,
|
||||
) => Promise<ResourceIdentifier>;
|
||||
) => Promise<ChangeMap>;
|
||||
|
||||
/**
|
||||
* Deletes a resource.
|
||||
* @param identifier - Identifier of resource to delete.
|
||||
* @param conditions - Optional conditions under which to proceed.
|
||||
*
|
||||
* @returns Identifiers of resources that were possibly modified.
|
||||
* @returns A {@link ChangeMap}.
|
||||
*/
|
||||
deleteResource: (
|
||||
identifier: ResourceIdentifier,
|
||||
conditions?: Conditions,
|
||||
) => Promise<ResourceIdentifier[]>;
|
||||
) => Promise<ChangeMap>;
|
||||
|
||||
/**
|
||||
* Sets or updates the representation of a resource,
|
||||
@@ -79,11 +87,11 @@ export interface ResourceStore extends ResourceSet {
|
||||
* @param patch - Description of which parts to update.
|
||||
* @param conditions - Optional conditions under which to proceed.
|
||||
*
|
||||
* @returns Identifiers of resources that were possibly modified.
|
||||
* @returns A {@link ChangeMap}.
|
||||
*/
|
||||
modifyResource: (
|
||||
identifier: ResourceIdentifier,
|
||||
patch: Patch,
|
||||
conditions?: Conditions,
|
||||
) => Promise<ResourceIdentifier[]>;
|
||||
) => Promise<ChangeMap>;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { ResourceIdentifier } from '../http/representation/ResourceIdentifi
|
||||
import { NotFoundHttpError } from '../util/errors/NotFoundHttpError';
|
||||
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
|
||||
import type { Conditions } from './Conditions';
|
||||
import type { ResourceStore } from './ResourceStore';
|
||||
import type { ResourceStore, ChangeMap } from './ResourceStore';
|
||||
import type { RouterRule } from './routing/RouterRule';
|
||||
|
||||
/**
|
||||
@@ -31,22 +31,22 @@ export class RoutingResourceStore implements ResourceStore {
|
||||
}
|
||||
|
||||
public async addResource(container: ResourceIdentifier, representation: Representation,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
return (await this.getStore(container, representation)).addResource(container, representation, conditions);
|
||||
}
|
||||
|
||||
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
return (await this.getStore(identifier, representation)).setRepresentation(identifier, representation, conditions);
|
||||
}
|
||||
|
||||
public async deleteResource(identifier: ResourceIdentifier,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
return (await this.getStore(identifier)).deleteResource(identifier, conditions);
|
||||
}
|
||||
|
||||
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
conditions?: Conditions): Promise<ChangeMap> {
|
||||
return (await this.getStore(identifier)).modifyResource(identifier, patch, conditions);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Patch } from '../../http/representation/Patch';
|
||||
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
|
||||
import { AsyncHandler } from '../../util/handlers/AsyncHandler';
|
||||
import type { ResourceStore } from '../ResourceStore';
|
||||
import type { ResourceStore, ChangeMap } from '../ResourceStore';
|
||||
|
||||
export type PatchHandlerInput<T extends ResourceStore = ResourceStore> = {
|
||||
source: T;
|
||||
@@ -13,4 +13,4 @@ export type PatchHandlerInput<T extends ResourceStore = ResourceStore> = {
|
||||
* Executes the given Patch.
|
||||
*/
|
||||
export abstract class PatchHandler<T extends ResourceStore = ResourceStore>
|
||||
extends AsyncHandler<PatchHandlerInput<T>, ResourceIdentifier[]> {}
|
||||
extends AsyncHandler<PatchHandlerInput<T>, ChangeMap> {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Representation } from '../../http/representation/Representation';
|
||||
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
|
||||
import { getLoggerFor } from '../../logging/LogUtil';
|
||||
import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError';
|
||||
import type { ChangeMap } from '../ResourceStore';
|
||||
import type { PatchHandlerInput } from './PatchHandler';
|
||||
import { PatchHandler } from './PatchHandler';
|
||||
import type { RepresentationPatcher } from './RepresentationPatcher';
|
||||
@@ -23,7 +23,7 @@ export class RepresentationPatchHandler extends PatchHandler {
|
||||
this.patcher = patcher;
|
||||
}
|
||||
|
||||
public async handle({ source, patch, identifier }: PatchHandlerInput): Promise<ResourceIdentifier[]> {
|
||||
public async handle({ source, patch, identifier }: PatchHandlerInput): Promise<ChangeMap> {
|
||||
// Get the representation from the store
|
||||
let representation: Representation | undefined;
|
||||
try {
|
||||
|
||||
@@ -71,6 +71,12 @@ export const ACL = createUriAndTermNamespace('http://www.w3.org/ns/auth/acl#',
|
||||
'Control',
|
||||
);
|
||||
|
||||
export const AS = createUriAndTermNamespace('https://www.w3.org/ns/activitystreams#',
|
||||
'Create',
|
||||
'Delete',
|
||||
'Update',
|
||||
);
|
||||
|
||||
export const AUTH = createUriAndTermNamespace('urn:solid:auth:',
|
||||
'userMode',
|
||||
'publicMode',
|
||||
@@ -144,6 +150,10 @@ export const SOLID = createUriAndTermNamespace('http://www.w3.org/ns/solid/terms
|
||||
'InsertDeletePatch',
|
||||
);
|
||||
|
||||
export const SOLID_AS = createUriAndTermNamespace('http://www.w3.org/ns/solid/activitystreams#',
|
||||
'Activity',
|
||||
);
|
||||
|
||||
export const SOLID_ERROR = createUriAndTermNamespace('urn:npm:solid:community-server:error:',
|
||||
'disallowedMethod',
|
||||
'errorResponse',
|
||||
|
||||
Reference in New Issue
Block a user