mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Initialize root containers with RootContainerInitializer
This commit is contained in:
parent
a08b7e9112
commit
231349b30d
@ -11,6 +11,15 @@
|
|||||||
"@id": "urn:solid-server:default:LoggerFactory"
|
"@id": "urn:solid-server:default:LoggerFactory"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"@type": "RootContainerInitializer",
|
||||||
|
"RootContainerInitializer:_baseUrl": {
|
||||||
|
"@id": "urn:solid-server:default:variable:baseUrl"
|
||||||
|
},
|
||||||
|
"RootContainerInitializer:_store": {
|
||||||
|
"@id": "urn:solid-server:default:ResourceStore"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"@type": "AclInitializer",
|
"@type": "AclInitializer",
|
||||||
"AclInitializer:_baseUrl": {
|
"AclInitializer:_baseUrl": {
|
||||||
|
@ -17,6 +17,7 @@ export * from './init/AclInitializer';
|
|||||||
export * from './init/CliRunner';
|
export * from './init/CliRunner';
|
||||||
export * from './init/Initializer';
|
export * from './init/Initializer';
|
||||||
export * from './init/LoggerInitializer';
|
export * from './init/LoggerInitializer';
|
||||||
|
export * from './init/RootContainerInitializer';
|
||||||
export * from './init/ServerInitializer';
|
export * from './init/ServerInitializer';
|
||||||
|
|
||||||
// LDP/HTTP/Metadata
|
// LDP/HTTP/Metadata
|
||||||
|
72
src/init/RootContainerInitializer.ts
Normal file
72
src/init/RootContainerInitializer.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { DataFactory } from 'n3';
|
||||||
|
import { RepresentationMetadata } from '../ldp/representation/RepresentationMetadata';
|
||||||
|
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
||||||
|
import { getLoggerFor } from '../logging/LogUtil';
|
||||||
|
import type { ResourceStore } from '../storage/ResourceStore';
|
||||||
|
import { TEXT_TURTLE } from '../util/ContentTypes';
|
||||||
|
import { NotFoundHttpError } from '../util/errors/NotFoundHttpError';
|
||||||
|
import { ensureTrailingSlash } from '../util/PathUtil';
|
||||||
|
import { generateResourceQuads } from '../util/ResourceUtil';
|
||||||
|
import { guardedStreamFrom } from '../util/StreamUtil';
|
||||||
|
import { PIM, RDF } from '../util/UriConstants';
|
||||||
|
import { toNamedNode } from '../util/UriUtil';
|
||||||
|
import { Initializer } from './Initializer';
|
||||||
|
import namedNode = DataFactory.namedNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes ResourceStores by creating a root container if it didn't exist yet.
|
||||||
|
*/
|
||||||
|
export class RootContainerInitializer extends Initializer {
|
||||||
|
protected readonly logger = getLoggerFor(this);
|
||||||
|
private readonly baseId: ResourceIdentifier;
|
||||||
|
private readonly store: ResourceStore;
|
||||||
|
|
||||||
|
public constructor(baseUrl: string, store: ResourceStore) {
|
||||||
|
super();
|
||||||
|
this.baseId = { path: ensureTrailingSlash(baseUrl) };
|
||||||
|
this.store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async handle(): Promise<void> {
|
||||||
|
if (!await this.hasRootContainer()) {
|
||||||
|
await this.createRootContainer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify if a root container already exists in a ResourceStore.
|
||||||
|
*/
|
||||||
|
protected async hasRootContainer(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const result = await this.store.getRepresentation(this.baseId, {});
|
||||||
|
this.logger.debug(`Existing root container found at ${this.baseId.path}`);
|
||||||
|
result.data.destroy();
|
||||||
|
return true;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (!(error instanceof NotFoundHttpError)) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a root container in a ResourceStore.
|
||||||
|
*/
|
||||||
|
protected async createRootContainer(): Promise<void> {
|
||||||
|
const metadata = new RepresentationMetadata(this.baseId);
|
||||||
|
metadata.addQuads(generateResourceQuads(namedNode(this.baseId.path), true));
|
||||||
|
|
||||||
|
// Make sure the root container is a pim:Storage
|
||||||
|
// This prevents deletion of the root container as storage root containers can not be deleted
|
||||||
|
metadata.add(RDF.type, toNamedNode(PIM.Storage));
|
||||||
|
|
||||||
|
metadata.contentType = TEXT_TURTLE;
|
||||||
|
|
||||||
|
await this.store.setRepresentation(this.baseId, {
|
||||||
|
binary: true,
|
||||||
|
data: guardedStreamFrom([]),
|
||||||
|
metadata,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -46,6 +46,11 @@ export const MA = {
|
|||||||
format: MA_PREFIX('format'),
|
format: MA_PREFIX('format'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const PIM_PREFIX = createNamespace('http://www.w3.org/ns/pim/space#');
|
||||||
|
export const PIM = {
|
||||||
|
Storage: PIM_PREFIX('Storage'),
|
||||||
|
};
|
||||||
|
|
||||||
const POSIX_PREFIX = createNamespace('http://www.w3.org/ns/posix/stat#');
|
const POSIX_PREFIX = createNamespace('http://www.w3.org/ns/posix/stat#');
|
||||||
export const POSIX = {
|
export const POSIX = {
|
||||||
mtime: POSIX_PREFIX('mtime'),
|
mtime: POSIX_PREFIX('mtime'),
|
||||||
|
41
test/unit/init/RootContainerInitializer.test.ts
Normal file
41
test/unit/init/RootContainerInitializer.test.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { RootContainerInitializer } from '../../../src/init/RootContainerInitializer';
|
||||||
|
import type { ResourceStore } from '../../../src/storage/ResourceStore';
|
||||||
|
import { NotFoundHttpError } from '../../../src/util/errors/NotFoundHttpError';
|
||||||
|
|
||||||
|
describe('A RootContainerInitializer', (): void => {
|
||||||
|
const baseUrl = 'http://test.com/';
|
||||||
|
const store: jest.Mocked<ResourceStore> = {
|
||||||
|
getRepresentation: jest.fn().mockRejectedValue(new NotFoundHttpError()),
|
||||||
|
setRepresentation: jest.fn(),
|
||||||
|
} as any;
|
||||||
|
const initializer = new RootContainerInitializer(baseUrl, store);
|
||||||
|
|
||||||
|
afterEach((): void => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invokes ResourceStore initialization.', async(): Promise<void> => {
|
||||||
|
await initializer.handle();
|
||||||
|
|
||||||
|
expect(store.getRepresentation).toHaveBeenCalledTimes(1);
|
||||||
|
expect(store.getRepresentation).toHaveBeenCalledWith({ path: baseUrl }, {});
|
||||||
|
expect(store.setRepresentation).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not invoke ResourceStore initialization when a root container already exists.', async(): Promise<void> => {
|
||||||
|
store.getRepresentation.mockReturnValueOnce(Promise.resolve({
|
||||||
|
data: { destroy: jest.fn() },
|
||||||
|
} as any));
|
||||||
|
|
||||||
|
await initializer.handle();
|
||||||
|
|
||||||
|
expect(store.getRepresentation).toHaveBeenCalledTimes(1);
|
||||||
|
expect(store.getRepresentation).toHaveBeenCalledWith({ path: 'http://test.com/' }, {});
|
||||||
|
expect(store.setRepresentation).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors when the store errors writing the root container.', async(): Promise<void> => {
|
||||||
|
store.getRepresentation.mockRejectedValueOnce(new Error('Fatal'));
|
||||||
|
await expect(initializer.handle()).rejects.toThrow('Fatal');
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user