mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Create pod manager for generating dynamic pods
This commit is contained in:
parent
38afd72098
commit
88d008e36f
@ -109,8 +109,10 @@ export * from './logging/VoidLoggerFactory';
|
|||||||
export * from './logging/WinstonLoggerFactory';
|
export * from './logging/WinstonLoggerFactory';
|
||||||
|
|
||||||
// Pods/Generate
|
// Pods/Generate
|
||||||
|
export * from './pods/generate/GenerateUtil';
|
||||||
export * from './pods/generate/HandlebarsTemplateEngine';
|
export * from './pods/generate/HandlebarsTemplateEngine';
|
||||||
export * from './pods/generate/IdentifierGenerator';
|
export * from './pods/generate/IdentifierGenerator';
|
||||||
|
export * from './pods/generate/PodGenerator';
|
||||||
export * from './pods/generate/ResourcesGenerator';
|
export * from './pods/generate/ResourcesGenerator';
|
||||||
export * from './pods/generate/SubdomainIdentifierGenerator';
|
export * from './pods/generate/SubdomainIdentifierGenerator';
|
||||||
export * from './pods/generate/SuffixIdentifierGenerator';
|
export * from './pods/generate/SuffixIdentifierGenerator';
|
||||||
@ -123,6 +125,7 @@ export * from './pods/settings/PodSettingsJsonParser';
|
|||||||
export * from './pods/settings/PodSettingsParser';
|
export * from './pods/settings/PodSettingsParser';
|
||||||
|
|
||||||
// Pods
|
// Pods
|
||||||
|
export * from './pods/ConfigPodManager';
|
||||||
export * from './pods/GeneratedPodManager';
|
export * from './pods/GeneratedPodManager';
|
||||||
export * from './pods/PodManager';
|
export * from './pods/PodManager';
|
||||||
export * from './pods/PodManagerHttpHandler';
|
export * from './pods/PodManagerHttpHandler';
|
||||||
|
58
src/pods/ConfigPodManager.ts
Normal file
58
src/pods/ConfigPodManager.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
||||||
|
import { getLoggerFor } from '../logging/LogUtil';
|
||||||
|
import type { KeyValueStorage } from '../storage/keyvalue/KeyValueStorage';
|
||||||
|
import type { ResourceStore } from '../storage/ResourceStore';
|
||||||
|
import { addGeneratedResources } from './generate/GenerateUtil';
|
||||||
|
import type { IdentifierGenerator } from './generate/IdentifierGenerator';
|
||||||
|
import type { PodGenerator } from './generate/PodGenerator';
|
||||||
|
import type { ResourcesGenerator } from './generate/ResourcesGenerator';
|
||||||
|
import type { PodManager } from './PodManager';
|
||||||
|
import type { PodSettings } from './settings/PodSettings';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pod manager that creates a store for the pod with a {@link PodGenerator}
|
||||||
|
* and fills it with resources from a {@link ResourcesGenerator}.
|
||||||
|
*
|
||||||
|
* Part of the dynamic pod creation.
|
||||||
|
* 1. Calls a PodGenerator to instantiate a new resource store for the pod.
|
||||||
|
* 2. Generates the pod resources based on the templates as usual.
|
||||||
|
* 3. Adds the created pod to the routing storage, which is used for linking pod identifiers to their resource stores.
|
||||||
|
*
|
||||||
|
* @see {@link TemplatedPodGenerator}, {@link ConfigPodInitializer}, {@link BaseUrlRouterRule}
|
||||||
|
*/
|
||||||
|
export class ConfigPodManager implements PodManager {
|
||||||
|
protected readonly logger = getLoggerFor(this);
|
||||||
|
private readonly idGenerator: IdentifierGenerator;
|
||||||
|
private readonly podGenerator: PodGenerator;
|
||||||
|
private readonly routingStorage: KeyValueStorage<ResourceIdentifier, ResourceStore>;
|
||||||
|
private readonly resourcesGenerator: ResourcesGenerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param idGenerator - Generator for the pod identifiers.
|
||||||
|
* @param podGenerator - Generator for the pod stores.
|
||||||
|
* @param resourcesGenerator - Generator for the pod resources.
|
||||||
|
* @param routingStorage - Where to store the generated pods so they can be routed to.
|
||||||
|
*/
|
||||||
|
public constructor(idGenerator: IdentifierGenerator, podGenerator: PodGenerator,
|
||||||
|
resourcesGenerator: ResourcesGenerator, routingStorage: KeyValueStorage<ResourceIdentifier, ResourceStore>) {
|
||||||
|
this.idGenerator = idGenerator;
|
||||||
|
this.podGenerator = podGenerator;
|
||||||
|
this.routingStorage = routingStorage;
|
||||||
|
this.resourcesGenerator = resourcesGenerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createPod(settings: PodSettings): Promise<ResourceIdentifier> {
|
||||||
|
const identifier = this.idGenerator.generate(settings.login);
|
||||||
|
this.logger.info(`Creating pod ${identifier.path}`);
|
||||||
|
|
||||||
|
// Will error in case there already is a store for the given identifier
|
||||||
|
const store = await this.podGenerator.generate(identifier, settings);
|
||||||
|
|
||||||
|
const count = await addGeneratedResources(identifier, settings, this.resourcesGenerator, store);
|
||||||
|
this.logger.info(`Added ${count} resources to ${identifier.path}`);
|
||||||
|
|
||||||
|
await this.routingStorage.set(identifier, store);
|
||||||
|
|
||||||
|
return identifier;
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifie
|
|||||||
import { getLoggerFor } from '../logging/LogUtil';
|
import { getLoggerFor } from '../logging/LogUtil';
|
||||||
import type { ResourceStore } from '../storage/ResourceStore';
|
import type { ResourceStore } from '../storage/ResourceStore';
|
||||||
import { ConflictHttpError } from '../util/errors/ConflictHttpError';
|
import { ConflictHttpError } from '../util/errors/ConflictHttpError';
|
||||||
|
import { addGeneratedResources } from './generate/GenerateUtil';
|
||||||
import type { IdentifierGenerator } from './generate/IdentifierGenerator';
|
import type { IdentifierGenerator } from './generate/IdentifierGenerator';
|
||||||
import type { ResourcesGenerator } from './generate/ResourcesGenerator';
|
import type { ResourcesGenerator } from './generate/ResourcesGenerator';
|
||||||
import type { PodManager } from './PodManager';
|
import type { PodManager } from './PodManager';
|
||||||
@ -36,12 +37,7 @@ export class GeneratedPodManager implements PodManager {
|
|||||||
throw new ConflictHttpError(`There already is a resource at ${podIdentifier.path}`);
|
throw new ConflictHttpError(`There already is a resource at ${podIdentifier.path}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const resources = this.resourcesGenerator.generate(podIdentifier, settings);
|
const count = await addGeneratedResources(podIdentifier, settings, this.resourcesGenerator, this.store);
|
||||||
let count = 0;
|
|
||||||
for await (const { identifier, representation } of resources) {
|
|
||||||
await this.store.setRepresentation(identifier, representation);
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
this.logger.info(`Added ${count} resources to ${podIdentifier.path}`);
|
this.logger.info(`Added ${count} resources to ${podIdentifier.path}`);
|
||||||
return podIdentifier;
|
return podIdentifier;
|
||||||
}
|
}
|
||||||
|
24
src/pods/generate/GenerateUtil.ts
Normal file
24
src/pods/generate/GenerateUtil.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
|
||||||
|
import type { ResourceStore } from '../../storage/ResourceStore';
|
||||||
|
import type { PodSettings } from '../settings/PodSettings';
|
||||||
|
import type { ResourcesGenerator } from './ResourcesGenerator';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates resources with the given generator and adds them to the given store.
|
||||||
|
* @param identifier - Identifier of the pod.
|
||||||
|
* @param settings - Settings from which the pod is being created.
|
||||||
|
* @param generator - Generator to be used.
|
||||||
|
* @param store - Store to be updated.
|
||||||
|
*
|
||||||
|
* @returns The amount of resources that were added.
|
||||||
|
*/
|
||||||
|
export async function addGeneratedResources(identifier: ResourceIdentifier, settings: PodSettings,
|
||||||
|
generator: ResourcesGenerator, store: ResourceStore): Promise<number> {
|
||||||
|
const resources = generator.generate(identifier, settings);
|
||||||
|
let count = 0;
|
||||||
|
for await (const { identifier: resourceId, representation } of resources) {
|
||||||
|
await store.setRepresentation(resourceId, representation);
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
21
src/pods/generate/PodGenerator.ts
Normal file
21
src/pods/generate/PodGenerator.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
|
||||||
|
import type { ResourceStore } from '../../storage/ResourceStore';
|
||||||
|
import type { PodSettings } from '../settings/PodSettings';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an empty resource store to be used as a new pod.
|
||||||
|
* It is also responsible for storing any relevant variables needed to instantiate this resource store.
|
||||||
|
* These can then be used when the server is restarted to re-instantiate those stores.
|
||||||
|
*/
|
||||||
|
export interface PodGenerator {
|
||||||
|
/**
|
||||||
|
* Creates a ResourceStore based on the given input.
|
||||||
|
* Should error if there already is a store for the given identifier.
|
||||||
|
*
|
||||||
|
* @param identifier - Identifier of the new pod.
|
||||||
|
* @param settings - Parameters to be used for the new pod.
|
||||||
|
*
|
||||||
|
* @returns A new ResourceStore to be used for the new pod.
|
||||||
|
*/
|
||||||
|
generate: (identifier: ResourceIdentifier, settings: PodSettings) => Promise<ResourceStore>;
|
||||||
|
}
|
67
test/unit/pods/ConfigPodManager.test.ts
Normal file
67
test/unit/pods/ConfigPodManager.test.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import type { ResourceIdentifier } from '../../../src/ldp/representation/ResourceIdentifier';
|
||||||
|
import { ConfigPodManager } from '../../../src/pods/ConfigPodManager';
|
||||||
|
import type { IdentifierGenerator } from '../../../src/pods/generate/IdentifierGenerator';
|
||||||
|
import type { PodGenerator } from '../../../src/pods/generate/PodGenerator';
|
||||||
|
import type { Resource, ResourcesGenerator } from '../../../src/pods/generate/ResourcesGenerator';
|
||||||
|
import type { PodSettings } from '../../../src/pods/settings/PodSettings';
|
||||||
|
import type { KeyValueStorage } from '../../../src/storage/keyvalue/KeyValueStorage';
|
||||||
|
import type { ResourceStore } from '../../../src/storage/ResourceStore';
|
||||||
|
describe('A ConfigPodManager', (): void => {
|
||||||
|
let settings: PodSettings;
|
||||||
|
const base = 'http://test.com/';
|
||||||
|
const idGenerator: IdentifierGenerator = {
|
||||||
|
generate: (slug: string): ResourceIdentifier => ({ path: `${base}${slug}/` }),
|
||||||
|
};
|
||||||
|
let store: ResourceStore;
|
||||||
|
let podGenerator: PodGenerator;
|
||||||
|
let routingStorage: KeyValueStorage<ResourceIdentifier, ResourceStore>;
|
||||||
|
let generatorData: Resource[];
|
||||||
|
let resourcesGenerator: ResourcesGenerator;
|
||||||
|
let manager: ConfigPodManager;
|
||||||
|
|
||||||
|
beforeEach(async(): Promise<void> => {
|
||||||
|
settings = {
|
||||||
|
login: 'alice',
|
||||||
|
template: 'config-template.json',
|
||||||
|
webId: 'webId',
|
||||||
|
};
|
||||||
|
|
||||||
|
store = {
|
||||||
|
setRepresentation: jest.fn(),
|
||||||
|
} as any;
|
||||||
|
podGenerator = {
|
||||||
|
generate: jest.fn().mockResolvedValue(store),
|
||||||
|
};
|
||||||
|
|
||||||
|
generatorData = [
|
||||||
|
{ identifier: { path: '/path/' }, representation: '/' as any },
|
||||||
|
{ identifier: { path: '/path/foo' }, representation: '/foo' as any },
|
||||||
|
];
|
||||||
|
resourcesGenerator = {
|
||||||
|
generate: jest.fn(async function* (): any {
|
||||||
|
yield* generatorData;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const map = new Map();
|
||||||
|
routingStorage = {
|
||||||
|
get: async(identifier: ResourceIdentifier): Promise<ResourceStore | undefined> => map.get(identifier.path),
|
||||||
|
set: async(identifier: ResourceIdentifier, value: ResourceStore): Promise<any> => map.set(identifier.path, value),
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
manager = new ConfigPodManager(idGenerator, podGenerator, resourcesGenerator, routingStorage);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a pod and returns the newly generated identifier.', async(): Promise<void> => {
|
||||||
|
const identifier = { path: `${base}alice/` };
|
||||||
|
await expect(manager.createPod(settings)).resolves.toEqual(identifier);
|
||||||
|
expect(podGenerator.generate).toHaveBeenCalledTimes(1);
|
||||||
|
expect(podGenerator.generate).toHaveBeenLastCalledWith(identifier, settings);
|
||||||
|
expect(resourcesGenerator.generate).toHaveBeenCalledTimes(1);
|
||||||
|
expect(resourcesGenerator.generate).toHaveBeenLastCalledWith(identifier, settings);
|
||||||
|
expect(store.setRepresentation).toHaveBeenCalledTimes(2);
|
||||||
|
expect(store.setRepresentation).toHaveBeenCalledWith({ path: '/path/' }, '/');
|
||||||
|
expect(store.setRepresentation).toHaveBeenLastCalledWith({ path: '/path/foo' }, '/foo');
|
||||||
|
await expect(routingStorage.get(identifier)).resolves.toBe(store);
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user