From e465587f6283fc7ed6f9d029fa536ae2b74c82b7 Mon Sep 17 00:00:00 2001 From: maartenvandenbrande Date: Wed, 18 Sep 2024 21:38:24 +0200 Subject: [PATCH] Initial renaming and refactor (removing the pods) --- config/default.json | 10 +-- lib/core/AsyncConstructor.ts | 29 --------- lib/endpoint/Endpoint.ts | 32 +++++++++- lib/endpoint/test/Endpoint-test.ts | 4 +- .../EndpointHandlerServiceDescription-test.ts | 4 +- lib/index.ts | 5 -- lib/pod/IPod.ts | 7 -- lib/pod/PodCss.ts | 64 ------------------- lib/pod/assets/.gitignore | 1 - lib/pod/assets/css-config.json | 37 ----------- lib/pod/assets/seed.json | 9 --- lib/service-registry/IServiceRegistry.ts | 5 +- .../ServiceRegistryHardcoded.ts | 52 +++++++++++++++ .../ServiceRegistryHardcodedTestOnly.ts | 17 +---- lib/service/IService.ts | 7 +- lib/service/ServiceAggregation.ts | 46 ++++++------- lib/service/ServiceEmpty.ts | 35 ++++------ 17 files changed, 126 insertions(+), 238 deletions(-) delete mode 100644 lib/core/AsyncConstructor.ts delete mode 100644 lib/pod/IPod.ts delete mode 100644 lib/pod/PodCss.ts delete mode 100644 lib/pod/assets/.gitignore delete mode 100644 lib/pod/assets/css-config.json delete mode 100644 lib/pod/assets/seed.json create mode 100644 lib/service-registry/ServiceRegistryHardcoded.ts diff --git a/config/default.json b/config/default.json index 046906f..5023105 100644 --- a/config/default.json +++ b/config/default.json @@ -16,21 +16,17 @@ "@type": "NativeFetch" }, { - "@id": "urn:solid-aggregator:pod", - "@type": "PodCss" - }, - { - "@id": "urn:solid-aggregator:aggregator-service-aggregation", + "@id": "urn:solid-aggregator:service-aggregation", "@type": "ServiceAggregation", "fetch": { "@id": "urn:solid-aggregator:fetch" }, - "pod": { "@id": "urn:solid-aggregator:pod" } + "endpoint": { "@id": "urn:solid-aggregator:endpoint" } }, { "@id": "urn:solid-aggregator:service-registry", "@type": "ServiceRegistryHardcodedTestOnly", "costQueueFactory": { "@id": "urn:solid-aggregator:cost-queue-factory" }, "aggregatorServices": [ - { "@id": "urn:solid-aggregator:aggregator-service-aggregation" } + { "@id": "urn:solid-aggregator:service-aggregation" } ] } ] diff --git a/lib/core/AsyncConstructor.ts b/lib/core/AsyncConstructor.ts deleted file mode 100644 index 056108f..0000000 --- a/lib/core/AsyncConstructor.ts +++ /dev/null @@ -1,29 +0,0 @@ -export interface IAsyncConstructor { - subscribeInitialized: (callback: () => void) => void; -} - -export abstract class AsyncConstructor implements IAsyncConstructor { - public initialized = false; - private readonly callbacks: (() => void)[] = []; - - protected constructor(args: AsyncConstructorArgs) { - this.initialize(args) - .then((): void => { - this.initialized = true; - for (const callback of this.callbacks) { - callback(); - } - }) - .catch((reason): void => { - throw new Error(`Error during async initialization of class '${this.constructor.name}', reason: "${reason}".`); - }); - } - - protected abstract initialize(args: AsyncConstructorArgs): Promise; - - public subscribeInitialized(callback: () => void): void { - this.callbacks.push(callback); - } -} - -export type AsyncConstructorArgs = any; diff --git a/lib/endpoint/Endpoint.ts b/lib/endpoint/Endpoint.ts index cce6368..9f62040 100644 --- a/lib/endpoint/Endpoint.ts +++ b/lib/endpoint/Endpoint.ts @@ -1,11 +1,14 @@ import type { IncomingMessage, Server, ServerResponse } from 'node:http'; import { createServer } from 'node:http'; +import { v4 as uuidv4 } from 'uuid'; import type { IServiceRegistry } from '../service-registry/IServiceRegistry'; export class Endpoint { public readonly serviceRegistry: IServiceRegistry; public readonly endpointHandlers: IEndpointHandler[]; private readonly httpServer: Server; + private readonly endpoints = + new Map void>(); public constructor(serviceRegistry: IServiceRegistry, endpointHandlers: IEndpointHandler[]) { this.serviceRegistry = serviceRegistry; @@ -14,17 +17,34 @@ export class Endpoint { } public async start(): Promise { - await this.serviceRegistry.initializeServices(); - + // TODO [2024-10-01]: Make port configurable this.httpServer.listen(1612); this.httpServer.on('request', (request: IncomingMessage, response: ServerResponse): void => { this.handleRequest(request, response) .catch((error): void => { - // TODO [2024-03-01]: implement proper logging + // TODO [2024-10-01]: implement proper logging // eslint-disable-next-line no-console console.error(error); }); }); + // TODO [2024-10-01]: Make port configurable + this.httpServer.listen(1613); + this.httpServer.on('request', (request: IncomingMessage, response: ServerResponse): void => { + const endpointLocation = request.url?.split('/')[1]; + if (endpointLocation === undefined) { + response.statusCode = 404; + response.end(); + return; + } + const endpoint = this.endpoints.get(endpointLocation); + if (endpoint === undefined) { + response.statusCode = 404; + response.end(); + return; + } + endpoint(request, response); + response.end(); + }); } private async handleRequest(request: IncomingMessage, response: ServerResponse): Promise { @@ -54,6 +74,12 @@ export class Endpoint { }); }); } + + public newServiceEndpoint(handleFunction: (request: IncomingMessage, response: ServerResponse) => void): string { + const endpointLocation = uuidv4(); + this.endpoints.set(endpointLocation, handleFunction); + return endpointLocation; + } } export interface IEndpointHandler { diff --git a/lib/endpoint/test/Endpoint-test.ts b/lib/endpoint/test/Endpoint-test.ts index 152caa9..72de9dc 100644 --- a/lib/endpoint/test/Endpoint-test.ts +++ b/lib/endpoint/test/Endpoint-test.ts @@ -22,7 +22,7 @@ describe('Endpoint', (): void => { 'testDescription', ]) as any, run: jest.fn( - async(): Promise => undefined, + async(): Promise => {}, ), }; }); @@ -191,7 +191,7 @@ describe('Endpoint', (): void => { 'testDescription', ]) as any, run: jest.fn( - async(): Promise => undefined, + async(): Promise => {}, ), }; diff --git a/lib/endpoint/test/EndpointHandlerServiceDescription-test.ts b/lib/endpoint/test/EndpointHandlerServiceDescription-test.ts index 7ec356a..5000799 100644 --- a/lib/endpoint/test/EndpointHandlerServiceDescription-test.ts +++ b/lib/endpoint/test/EndpointHandlerServiceDescription-test.ts @@ -17,7 +17,7 @@ describe('EndpointHandlerServiceDescription', (): void => { initializeServices: jest.fn().mockResolvedValue(undefined), descriptions, run: jest.fn( - async(): Promise => undefined, + async(): Promise => {}, ), }; @@ -39,7 +39,7 @@ describe('EndpointHandlerServiceDescription', (): void => { initializeServices: jest.fn().mockResolvedValue(undefined), descriptions, run: jest.fn( - async(): Promise => undefined, + async(): Promise => {}, ), }; diff --git a/lib/index.ts b/lib/index.ts index 4b7936d..16069a8 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,5 +1,3 @@ -export * from './core/AsyncConstructor'; - export * from './cost-queue/ICostQueue'; export * from './cost-queue/CostQueueTime'; @@ -11,9 +9,6 @@ export * from './fetch/NativeFetch'; export * from './init/AppRunner'; -export * from './pod/IPod'; -export * from './pod/PodCss'; - export * from './service/IService'; export * from './service/ServiceEmpty'; export * from './service/ServiceAggregation'; diff --git a/lib/pod/IPod.ts b/lib/pod/IPod.ts deleted file mode 100644 index 6a600dd..0000000 --- a/lib/pod/IPod.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { IServiceDescription } from '../service/IService'; - -export interface IPod { - newServiceLocation: (description: IServiceDescription) => Promise; -} - -export type PodServiceLocation = string; diff --git a/lib/pod/PodCss.ts b/lib/pod/PodCss.ts deleted file mode 100644 index 85ce4d2..0000000 --- a/lib/pod/PodCss.ts +++ /dev/null @@ -1,64 +0,0 @@ -import * as path from 'node:path'; -import * as fs from 'fs-extra'; -import { AppRunner } from '@solid/community-server'; -import { v4 } from 'uuid'; -import { AsyncConstructor } from '../core/AsyncConstructor'; -import type { IServiceDescription } from '../service/IService'; -import type { IPod, PodServiceLocation } from './IPod'; - -export class PodCss extends AsyncConstructor implements IPod { - public podURL = 'http://localhost:3000/aggregator'; - - public constructor() { - super({}); - } - - protected async initialize(): Promise { - // TODO [2024-03-01]: make sure the file for the server is selected => not sure actually - - const loaderProperties = { - mainModulePath: 'node_modules/@solid/community-server/', - dumpErrorState: true, - typeChecking: false, - }; - - const config = path.join(__dirname, './assets/css-config.json'); - - const shorthand: Record = { - rootFilePath: path.join(__dirname, './assets/podData/'), - }; - if (!(await fs.pathExists(path.join(__dirname, './assets/podData/')))) { - shorthand.seedConfig = path.join(__dirname, './assets/seed.json'); - } - - await (new AppRunner()).run({ - loaderProperties, - config, - shorthand, - }); - // TODO [2024-03-01]: Edit profile card - } - - public async newServiceLocation(description: IServiceDescription): Promise { - if (!this.initialized) { - await new Promise((resolve): void => { - this.subscribeInitialized((): void => { - resolve(); - }); - }); - } - - // Create service folder with uuid - const location = `${this.podURL}/${v4()}`; - const response = await fetch(`${location}/description`, { - method: 'PUT', - body: description.toString(), - }); - - if (response.ok) { - return location; - } - // TODO [2024-03-01]: redo maybe? - throw new Error(`Can't create location on the Solid Server: ${response.statusText}`); - } -} diff --git a/lib/pod/assets/.gitignore b/lib/pod/assets/.gitignore deleted file mode 100644 index f67ee08..0000000 --- a/lib/pod/assets/.gitignore +++ /dev/null @@ -1 +0,0 @@ -./podData diff --git a/lib/pod/assets/css-config.json b/lib/pod/assets/css-config.json deleted file mode 100644 index f1c8a3b..0000000 --- a/lib/pod/assets/css-config.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld", - "import": [ - "css:config/app/init/default.json", - "css:config/app/main/default.json", - "css:config/app/variables/default.json", - "css:config/http/handler/default.json", - "css:config/http/middleware/default.json", - "css:config/http/notifications/all.json", - "css:config/http/server-factory/http.json", - "css:config/http/static/default.json", - "css:config/identity/access/public.json", - "css:config/identity/email/default.json", - "css:config/identity/handler/default.json", - "css:config/identity/oidc/default.json", - "css:config/identity/ownership/token.json", - "css:config/identity/pod/static.json", - "css:config/ldp/authentication/dpop-bearer.json", - "css:config/ldp/authorization/allow-all.json", - "css:config/ldp/handler/default.json", - "css:config/ldp/metadata-parser/default.json", - "css:config/ldp/metadata-writer/default.json", - "css:config/ldp/modes/default.json", - "css:config/storage/backend/memory.json", - "css:config/storage/key-value/resource-store.json", - "css:config/storage/location/pod.json", - "css:config/storage/middleware/default.json", - "css:config/util/auxiliary/empty.json", - "css:config/util/identifiers/suffix.json", - "css:config/util/index/default.json", - "css:config/util/logging/winston.json", - "css:config/util/representation-conversion/default.json", - "css:config/util/resource-locker/file.json", - "css:config/util/variables/default.json" - ], - "@graph": [] -} diff --git a/lib/pod/assets/seed.json b/lib/pod/assets/seed.json deleted file mode 100644 index 955af89..0000000 --- a/lib/pod/assets/seed.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - { - "email": "aggregator@example.com", - "password": "aggregator", - "pods": [ - { "name": "aggregator" } - ] - } -] diff --git a/lib/service-registry/IServiceRegistry.ts b/lib/service-registry/IServiceRegistry.ts index 5779897..00a1d40 100644 --- a/lib/service-registry/IServiceRegistry.ts +++ b/lib/service-registry/IServiceRegistry.ts @@ -1,7 +1,6 @@ -import type { IOperation, IOperationResult } from '../service/IService'; +import type { IOperation } from '../service/IService'; export interface IServiceRegistry { get descriptions(): string[]; - initializeServices: () => Promise; - run: (operation: IOperation) => Promise; + run: (operation: IOperation) => Promise; } diff --git a/lib/service-registry/ServiceRegistryHardcoded.ts b/lib/service-registry/ServiceRegistryHardcoded.ts new file mode 100644 index 0000000..80324b2 --- /dev/null +++ b/lib/service-registry/ServiceRegistryHardcoded.ts @@ -0,0 +1,52 @@ +import type { IOperation, IService } from '../service/IService'; +import type { ICostQueueFactory } from '../cost-queue/ICostQueue'; +import type { IServiceRegistry } from './IServiceRegistry'; + +export class ServiceRegistryHardcodedTestOnly implements IServiceRegistry { + public readonly costQueueFactory: ICostQueueFactory; + public readonly services: IService[]; + + public constructor( + aggregatorServices: { service: IService; operations: IOperation[] }[], + costQueueFactory: ICostQueueFactory, + ) { + this.services = aggregatorServices.map((aggregatorService): IService => aggregatorService.service); + this.costQueueFactory = costQueueFactory; + for (const aggregatorService of aggregatorServices) { + for (const operation of aggregatorService.operations) { + aggregatorService.service.test(operation).then( + (testResult): void => { + if (!testResult.runnable) { + throw new Error('Operation not runnable'); + } + aggregatorService.service.run(operation).catch( + (error): void => { + throw new Error( + `Aggregator Service:\n${aggregatorService.service.description.toString()}\n` + + `failed to 'run' on operation:\n${JSON.stringify(operation)}\nwith error:\n${error}`, + ); + }, + ); + }, + ).catch((error): void => { + throw new Error( + `Aggregator Service:\n${aggregatorService.service.description.toString()}\n` + + `failed to 'test' on operation:\n${JSON.stringify(operation)}\nwith error:\n${error}`, + ); + }); + } + } + } + + public async run(operation: IOperation): Promise { + throw new Error(`Service registry not changeable ${JSON.stringify(operation)}.`); + } + + public get descriptions(): string[] { + const result = []; + for (const service of this.services) { + result.push(service.description.toString()); + } + return result; + } +} diff --git a/lib/service-registry/ServiceRegistryHardcodedTestOnly.ts b/lib/service-registry/ServiceRegistryHardcodedTestOnly.ts index bcb5d36..88e9f4d 100644 --- a/lib/service-registry/ServiceRegistryHardcodedTestOnly.ts +++ b/lib/service-registry/ServiceRegistryHardcodedTestOnly.ts @@ -1,4 +1,4 @@ -import type { IOperation, IOperationResult, IService } from '../service/IService'; +import type { IOperation, IService } from '../service/IService'; import type { ICostQueueFactory } from '../cost-queue/ICostQueue'; import type { IServiceRegistry } from './IServiceRegistry'; @@ -11,16 +11,7 @@ export class ServiceRegistryHardcodedTestOnly implements IServiceRegistry { this.costQueueFactory = costQueueFactory; } - public async initializeServices(): Promise { - await Promise.all( - this.services.map( - async(aggregatorService): Promise => new Promise((resolve): void => - aggregatorService.subscribeInitialized(resolve)), - ), - ); - } - - public async run(operation: IOperation): Promise { + public async run(operation: IOperation): Promise { const costQueue = this.costQueueFactory.create(); await Promise.all(this.services.map( @@ -36,12 +27,10 @@ export class ServiceRegistryHardcodedTestOnly implements IServiceRegistry { let operationResult = costQueue.pop(); while (operationResult === undefined || operationResult.operationResult === undefined) { if (costQueue.length === 0) { - return undefined; + throw new Error('No aggregator services can handel operation.'); } operationResult = costQueue.pop(); } - - return operationResult.operationResult; } public get descriptions(): string[] { diff --git a/lib/service/IService.ts b/lib/service/IService.ts index b1e0b14..76bab3e 100644 --- a/lib/service/IService.ts +++ b/lib/service/IService.ts @@ -1,10 +1,9 @@ import type { CostParameters } from '../cost-queue/ICostQueue'; -import type { IAsyncConstructor } from '../core/AsyncConstructor'; -export interface IService extends IAsyncConstructor { +export interface IService { get description(): IServiceDescription; test: (operation: IOperation) => Promise; - run: (operation: IOperation) => Promise; + run: (operation: IOperation) => Promise; } export interface IServiceDescription { @@ -22,7 +21,7 @@ export interface IOperationTestResult { export interface IOperationResult { aggregatorService: IService; operation: IOperation; - resultLocation: string; + serviceEndpoint: string; } export interface IOperation { diff --git a/lib/service/ServiceAggregation.ts b/lib/service/ServiceAggregation.ts index 5431cae..1193062 100644 --- a/lib/service/ServiceAggregation.ts +++ b/lib/service/ServiceAggregation.ts @@ -1,23 +1,15 @@ import { StreamParser, StreamWriter } from 'n3'; -import { v4 } from 'uuid'; import type { IFetch } from '../fetch/IFetch'; -import type { IPod } from '../pod/IPod'; -import { AsyncConstructor } from '../core/AsyncConstructor'; -import type { IOperation, IOperationResult, IOperationTestResult, IService, IServiceDescription } from './IService'; +import type { Endpoint } from '../endpoint/Endpoint'; +import type { IOperation, IOperationTestResult, IService, IServiceDescription } from './IService'; -export class ServiceAggregation extends AsyncConstructor implements IService { +export class ServiceAggregation implements IService { public fetch: IFetch; - public pod: IPod; - private podLocation: string | undefined; + public endpoint: Endpoint; public constructor(args: ServiceAggregationArgs) { - super(args); this.fetch = args.fetch; - this.pod = args.pod; - } - - protected async initialize(args: ServiceAggregationArgs): Promise { - this.podLocation = await args.pod.newServiceLocation(this.description); + this.endpoint = args.endpoint; } public async test(operation: IOperation): Promise { @@ -31,9 +23,7 @@ export class ServiceAggregation extends AsyncConstructor implements IService { }; } - public async run(operation: IOperation): Promise { - const resultLocation = `${this.podLocation}/${v4()}.ttl`; - + public async run(operation: IOperation): Promise { const streamWriter = new StreamWriter(); for (const source of operation.sources) { const streamParser = new StreamParser(); @@ -42,19 +32,19 @@ export class ServiceAggregation extends AsyncConstructor implements IService { streamParser.pipe(streamWriter); } - await this.fetch.fetch(resultLocation, { - method: 'PUT', - body: streamWriter, - headers: { - 'Content-Type': 'text/turtle', // eslint-disable-line ts/naming-convention - }, + const chunks = []; + for await (const chunk of streamWriter) { + chunks.push(Buffer.from(chunk)); // eslint-disable-line ts/no-unsafe-argument + } + const result = Buffer.concat(chunks).toString('utf-8'); + + const serviceEndpoint = this.endpoint.newServiceEndpoint((request, response): void => { + response.writeHead(200); + response.setHeader('Content-Type', 'text/turtle'); + response.write(result); }); - return { - aggregatorService: this, - operation, - resultLocation, - }; + return serviceEndpoint; } public get description(): IServiceDescription { @@ -66,5 +56,5 @@ export class ServiceAggregation extends AsyncConstructor implements IService { export type ServiceAggregationArgs = { fetch: IFetch; - pod: IPod; + endpoint: Endpoint; }; diff --git a/lib/service/ServiceEmpty.ts b/lib/service/ServiceEmpty.ts index a068b8d..6a09915 100644 --- a/lib/service/ServiceEmpty.ts +++ b/lib/service/ServiceEmpty.ts @@ -1,21 +1,11 @@ -import { AsyncConstructor } from '../core/AsyncConstructor'; -import type { IPod } from '../pod/IPod'; -import type { IFetch } from '../fetch/IFetch'; -import type { IOperation, IOperationResult, IOperationTestResult, IService, IServiceDescription } from './IService'; +import type { Endpoint } from '../endpoint/Endpoint'; +import type { IOperation, IOperationTestResult, IService, IServiceDescription } from './IService'; -export class ServiceEmpty extends AsyncConstructor implements IService { - private podLocation: string | undefined; - public fetch: IFetch; - public pod: IPod; +export class ServiceEmpty implements IService { + public endpoint: Endpoint; public constructor(args: ServiceEmptyArgs) { - super(args); - this.fetch = args.fetch; - this.pod = args.pod; - } - - protected async initialize(args: ServiceEmptyArgs): Promise { - this.podLocation = await args.pod.newServiceLocation(this.description); + this.endpoint = args.endpoint; } public async test(operation: IOperation): Promise { @@ -29,12 +19,12 @@ export class ServiceEmpty extends AsyncConstructor implements IService { }; } - public async run(operation: IOperation): Promise { - return { - aggregatorService: this, - operation, - resultLocation: '', - }; + public async run(): Promise { + return this.endpoint.newServiceEndpoint((request, response): void => { + response.writeHead(200); + response.setHeader('Content-Type', 'text/turtle'); + response.write(''); + }); } public get description(): IServiceDescription { @@ -45,6 +35,5 @@ export class ServiceEmpty extends AsyncConstructor implements IService { } export type ServiceEmptyArgs = { - fetch: IFetch; - pod: IPod; + endpoint: Endpoint; };