mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
refactor: Restructure source code folder
This way the location of certain classes should make more sense
This commit is contained in:
30
src/http/ldp/DeleteOperationHandler.ts
Normal file
30
src/http/ldp/DeleteOperationHandler.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { ResourceStore } from '../../storage/ResourceStore';
|
||||
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
|
||||
import { ResetResponseDescription } from '../output/response/ResetResponseDescription';
|
||||
import type { ResponseDescription } from '../output/response/ResponseDescription';
|
||||
import type { OperationHandlerInput } from './OperationHandler';
|
||||
import { OperationHandler } from './OperationHandler';
|
||||
|
||||
/**
|
||||
* Handles DELETE {@link Operation}s.
|
||||
* Calls the deleteResource function from a {@link ResourceStore}.
|
||||
*/
|
||||
export class DeleteOperationHandler extends OperationHandler {
|
||||
private readonly store: ResourceStore;
|
||||
|
||||
public constructor(store: ResourceStore) {
|
||||
super();
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
public async canHandle({ operation }: OperationHandlerInput): Promise<void> {
|
||||
if (operation.method !== 'DELETE') {
|
||||
throw new NotImplementedHttpError('This handler only supports DELETE operations');
|
||||
}
|
||||
}
|
||||
|
||||
public async handle({ operation }: OperationHandlerInput): Promise<ResponseDescription> {
|
||||
await this.store.deleteResource(operation.target, operation.conditions);
|
||||
return new ResetResponseDescription();
|
||||
}
|
||||
}
|
||||
31
src/http/ldp/GetOperationHandler.ts
Normal file
31
src/http/ldp/GetOperationHandler.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { ResourceStore } from '../../storage/ResourceStore';
|
||||
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
|
||||
import { OkResponseDescription } from '../output/response/OkResponseDescription';
|
||||
import type { ResponseDescription } from '../output/response/ResponseDescription';
|
||||
import type { OperationHandlerInput } from './OperationHandler';
|
||||
import { OperationHandler } from './OperationHandler';
|
||||
|
||||
/**
|
||||
* Handles GET {@link Operation}s.
|
||||
* Calls the getRepresentation function from a {@link ResourceStore}.
|
||||
*/
|
||||
export class GetOperationHandler extends OperationHandler {
|
||||
private readonly store: ResourceStore;
|
||||
|
||||
public constructor(store: ResourceStore) {
|
||||
super();
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
public async canHandle({ operation }: OperationHandlerInput): Promise<void> {
|
||||
if (operation.method !== 'GET') {
|
||||
throw new NotImplementedHttpError('This handler only supports GET operations');
|
||||
}
|
||||
}
|
||||
|
||||
public async handle({ operation }: OperationHandlerInput): Promise<ResponseDescription> {
|
||||
const body = await this.store.getRepresentation(operation.target, operation.preferences, operation.conditions);
|
||||
|
||||
return new OkResponseDescription(body.metadata, body.data);
|
||||
}
|
||||
}
|
||||
34
src/http/ldp/HeadOperationHandler.ts
Normal file
34
src/http/ldp/HeadOperationHandler.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { ResourceStore } from '../../storage/ResourceStore';
|
||||
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
|
||||
import { OkResponseDescription } from '../output/response/OkResponseDescription';
|
||||
import type { ResponseDescription } from '../output/response/ResponseDescription';
|
||||
import type { OperationHandlerInput } from './OperationHandler';
|
||||
import { OperationHandler } from './OperationHandler';
|
||||
|
||||
/**
|
||||
* Handles HEAD {@link Operation}s.
|
||||
* Calls the getRepresentation function from a {@link ResourceStore}.
|
||||
*/
|
||||
export class HeadOperationHandler extends OperationHandler {
|
||||
private readonly store: ResourceStore;
|
||||
|
||||
public constructor(store: ResourceStore) {
|
||||
super();
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
public async canHandle({ operation }: OperationHandlerInput): Promise<void> {
|
||||
if (operation.method !== 'HEAD') {
|
||||
throw new NotImplementedHttpError('This handler only supports HEAD operations');
|
||||
}
|
||||
}
|
||||
|
||||
public async handle({ operation }: OperationHandlerInput): Promise<ResponseDescription> {
|
||||
const body = await this.store.getRepresentation(operation.target, operation.preferences, operation.conditions);
|
||||
|
||||
// Close the Readable as we will not return it.
|
||||
body.data.destroy();
|
||||
|
||||
return new OkResponseDescription(body.metadata);
|
||||
}
|
||||
}
|
||||
12
src/http/ldp/OperationHandler.ts
Normal file
12
src/http/ldp/OperationHandler.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { AsyncHandler } from '../../util/handlers/AsyncHandler';
|
||||
import type { Operation } from '../Operation';
|
||||
import type { ResponseDescription } from '../output/response/ResponseDescription';
|
||||
|
||||
export interface OperationHandlerInput {
|
||||
operation: Operation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for a specific operation type.
|
||||
*/
|
||||
export abstract class OperationHandler extends AsyncHandler<OperationHandlerInput, ResponseDescription> {}
|
||||
42
src/http/ldp/PatchOperationHandler.ts
Normal file
42
src/http/ldp/PatchOperationHandler.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { getLoggerFor } from '../../logging/LogUtil';
|
||||
import type { ResourceStore } from '../../storage/ResourceStore';
|
||||
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
|
||||
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
|
||||
import { ResetResponseDescription } from '../output/response/ResetResponseDescription';
|
||||
import type { ResponseDescription } from '../output/response/ResponseDescription';
|
||||
import type { Patch } from '../representation/Patch';
|
||||
import type { OperationHandlerInput } from './OperationHandler';
|
||||
import { OperationHandler } from './OperationHandler';
|
||||
|
||||
/**
|
||||
* Handles PATCH {@link Operation}s.
|
||||
* Calls the modifyResource function from a {@link ResourceStore}.
|
||||
*/
|
||||
export class PatchOperationHandler extends OperationHandler {
|
||||
protected readonly logger = getLoggerFor(this);
|
||||
|
||||
private readonly store: ResourceStore;
|
||||
|
||||
public constructor(store: ResourceStore) {
|
||||
super();
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
public async canHandle({ operation }: OperationHandlerInput): Promise<void> {
|
||||
if (operation.method !== 'PATCH') {
|
||||
throw new NotImplementedHttpError('This handler only supports PATCH operations.');
|
||||
}
|
||||
}
|
||||
|
||||
public async handle({ operation }: OperationHandlerInput): Promise<ResponseDescription> {
|
||||
// Solid, §2.1: "A Solid server MUST reject PUT, POST and PATCH requests
|
||||
// without the Content-Type header with a status code of 400."
|
||||
// https://solid.github.io/specification/protocol#http-server
|
||||
if (!operation.body?.metadata.contentType) {
|
||||
this.logger.warn('No Content-Type header specified on PATCH request');
|
||||
throw new BadRequestHttpError('No Content-Type header specified on PATCH request');
|
||||
}
|
||||
await this.store.modifyResource(operation.target, operation.body as Patch, operation.conditions);
|
||||
return new ResetResponseDescription();
|
||||
}
|
||||
}
|
||||
41
src/http/ldp/PostOperationHandler.ts
Normal file
41
src/http/ldp/PostOperationHandler.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { getLoggerFor } from '../../logging/LogUtil';
|
||||
import type { ResourceStore } from '../../storage/ResourceStore';
|
||||
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
|
||||
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
|
||||
import { CreatedResponseDescription } from '../output/response/CreatedResponseDescription';
|
||||
import type { ResponseDescription } from '../output/response/ResponseDescription';
|
||||
import type { OperationHandlerInput } from './OperationHandler';
|
||||
import { OperationHandler } from './OperationHandler';
|
||||
|
||||
/**
|
||||
* Handles POST {@link Operation}s.
|
||||
* Calls the addResource function from a {@link ResourceStore}.
|
||||
*/
|
||||
export class PostOperationHandler extends OperationHandler {
|
||||
protected readonly logger = getLoggerFor(this);
|
||||
|
||||
private readonly store: ResourceStore;
|
||||
|
||||
public constructor(store: ResourceStore) {
|
||||
super();
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
public async canHandle({ operation }: OperationHandlerInput): Promise<void> {
|
||||
if (operation.method !== 'POST') {
|
||||
throw new NotImplementedHttpError('This handler only supports POST operations');
|
||||
}
|
||||
}
|
||||
|
||||
public async handle({ operation }: OperationHandlerInput): Promise<ResponseDescription> {
|
||||
// Solid, §2.1: "A Solid server MUST reject PUT, POST and PATCH requests
|
||||
// without the Content-Type header with a status code of 400."
|
||||
// https://solid.github.io/specification/protocol#http-server
|
||||
if (!operation.body?.metadata.contentType) {
|
||||
this.logger.warn('No Content-Type header specified on POST request');
|
||||
throw new BadRequestHttpError('No Content-Type header specified on POST request');
|
||||
}
|
||||
const identifier = await this.store.addResource(operation.target, operation.body, operation.conditions);
|
||||
return new CreatedResponseDescription(identifier);
|
||||
}
|
||||
}
|
||||
41
src/http/ldp/PutOperationHandler.ts
Normal file
41
src/http/ldp/PutOperationHandler.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { getLoggerFor } from '../../logging/LogUtil';
|
||||
import type { ResourceStore } from '../../storage/ResourceStore';
|
||||
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
|
||||
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
|
||||
import { ResetResponseDescription } from '../output/response/ResetResponseDescription';
|
||||
import type { ResponseDescription } from '../output/response/ResponseDescription';
|
||||
import type { OperationHandlerInput } from './OperationHandler';
|
||||
import { OperationHandler } from './OperationHandler';
|
||||
|
||||
/**
|
||||
* Handles PUT {@link Operation}s.
|
||||
* Calls the setRepresentation function from a {@link ResourceStore}.
|
||||
*/
|
||||
export class PutOperationHandler extends OperationHandler {
|
||||
protected readonly logger = getLoggerFor(this);
|
||||
|
||||
private readonly store: ResourceStore;
|
||||
|
||||
public constructor(store: ResourceStore) {
|
||||
super();
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
public async canHandle({ operation }: OperationHandlerInput): Promise<void> {
|
||||
if (operation.method !== 'PUT') {
|
||||
throw new NotImplementedHttpError('This handler only supports PUT operations');
|
||||
}
|
||||
}
|
||||
|
||||
public async handle({ operation }: OperationHandlerInput): Promise<ResponseDescription> {
|
||||
// Solid, §2.1: "A Solid server MUST reject PUT, POST and PATCH requests
|
||||
// without the Content-Type header with a status code of 400."
|
||||
// https://solid.github.io/specification/protocol#http-server
|
||||
if (!operation.body?.metadata.contentType) {
|
||||
this.logger.warn('No Content-Type header specified on PUT request');
|
||||
throw new BadRequestHttpError('No Content-Type header specified on PUT request');
|
||||
}
|
||||
await this.store.setRepresentation(operation.target, operation.body, operation.conditions);
|
||||
return new ResetResponseDescription();
|
||||
}
|
||||
}
|
||||
19
src/http/ldp/metadata/OperationMetadataCollector.ts
Normal file
19
src/http/ldp/metadata/OperationMetadataCollector.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { AsyncHandler } from '../../../util/handlers/AsyncHandler';
|
||||
import type { Operation } from '../../Operation';
|
||||
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
|
||||
|
||||
export interface OperationMetadataCollectorInput {
|
||||
/**
|
||||
* Metadata to update with permission knowledge.
|
||||
*/
|
||||
metadata: RepresentationMetadata;
|
||||
/**
|
||||
* Operation corresponding to the request.
|
||||
*/
|
||||
operation: Operation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds metadata about the operation to the provided metadata object.
|
||||
*/
|
||||
export abstract class OperationMetadataCollector extends AsyncHandler<OperationMetadataCollectorInput> {}
|
||||
38
src/http/ldp/metadata/WebAclMetadataCollector.ts
Normal file
38
src/http/ldp/metadata/WebAclMetadataCollector.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { AclMode } from '../../../authorization/permissions/AclPermission';
|
||||
import type { AclPermission } from '../../../authorization/permissions/AclPermission';
|
||||
import { AccessMode } from '../../../authorization/permissions/Permissions';
|
||||
import { ACL, AUTH } from '../../../util/Vocabularies';
|
||||
|
||||
import type { OperationMetadataCollectorInput } from './OperationMetadataCollector';
|
||||
import { OperationMetadataCollector } from './OperationMetadataCollector';
|
||||
|
||||
const VALID_METHODS = new Set([ 'HEAD', 'GET' ]);
|
||||
const VALID_ACL_MODES = new Set([ AccessMode.read, AccessMode.write, AccessMode.append, AclMode.control ]);
|
||||
|
||||
/**
|
||||
* Indicates which acl permissions are available on the requested resource.
|
||||
* Only adds public and agent permissions for HEAD/GET requests.
|
||||
*/
|
||||
export class WebAclMetadataCollector extends OperationMetadataCollector {
|
||||
public async handle({ metadata, operation }: OperationMetadataCollectorInput): Promise<void> {
|
||||
if (!operation.permissionSet || !VALID_METHODS.has(operation.method)) {
|
||||
return;
|
||||
}
|
||||
const user: AclPermission = operation.permissionSet.agent ?? {};
|
||||
const everyone: AclPermission = operation.permissionSet.public ?? {};
|
||||
|
||||
const modes = new Set<AccessMode>([ ...Object.keys(user), ...Object.keys(everyone) ] as AccessMode[]);
|
||||
|
||||
for (const mode of modes) {
|
||||
if (VALID_ACL_MODES.has(mode)) {
|
||||
const capitalizedMode = mode.charAt(0).toUpperCase() + mode.slice(1) as 'Read' | 'Write' | 'Append' | 'Control';
|
||||
if (everyone[mode]) {
|
||||
metadata.add(AUTH.terms.publicMode, ACL.terms[capitalizedMode]);
|
||||
}
|
||||
if (user[mode]) {
|
||||
metadata.add(AUTH.terms.userMode, ACL.terms[capitalizedMode]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user