mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
fix: Prevent generated metadata from being stored
This commit is contained in:
parent
35a7cf988c
commit
12e501844f
@ -1,4 +1,5 @@
|
|||||||
import { namedNode } from '@rdfjs/data-model';
|
import { namedNode } from '@rdfjs/data-model';
|
||||||
|
import { SOLID_META } from '../../util/Vocabularies';
|
||||||
import type { RepresentationMetadata } from '../representation/RepresentationMetadata';
|
import type { RepresentationMetadata } from '../representation/RepresentationMetadata';
|
||||||
import type { AuxiliaryIdentifierStrategy } from './AuxiliaryIdentifierStrategy';
|
import type { AuxiliaryIdentifierStrategy } from './AuxiliaryIdentifierStrategy';
|
||||||
import { MetadataGenerator } from './MetadataGenerator';
|
import { MetadataGenerator } from './MetadataGenerator';
|
||||||
@ -22,7 +23,9 @@ export class LinkMetadataGenerator extends MetadataGenerator {
|
|||||||
public async handle(metadata: RepresentationMetadata): Promise<void> {
|
public async handle(metadata: RepresentationMetadata): Promise<void> {
|
||||||
const identifier = { path: metadata.identifier.value };
|
const identifier = { path: metadata.identifier.value };
|
||||||
if (!this.identifierStrategy.isAuxiliaryIdentifier(identifier)) {
|
if (!this.identifierStrategy.isAuxiliaryIdentifier(identifier)) {
|
||||||
metadata.add(this.link, namedNode(this.identifierStrategy.getAuxiliaryIdentifier(identifier).path));
|
metadata.add(this.link,
|
||||||
|
namedNode(this.identifierStrategy.getAuxiliaryIdentifier(identifier).path),
|
||||||
|
SOLID_META.terms.ResponseMetadata);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,18 @@ import {
|
|||||||
} from '../util/PathUtil';
|
} from '../util/PathUtil';
|
||||||
import { parseQuads } from '../util/QuadUtil';
|
import { parseQuads } from '../util/QuadUtil';
|
||||||
import { addResourceMetadata } from '../util/ResourceUtil';
|
import { addResourceMetadata } from '../util/ResourceUtil';
|
||||||
import { CONTENT_TYPE, DC, SOLID_HTTP, LDP, POSIX, PIM, RDF, VANN, XSD } from '../util/Vocabularies';
|
import {
|
||||||
|
CONTENT_TYPE,
|
||||||
|
DC,
|
||||||
|
SOLID_HTTP,
|
||||||
|
LDP,
|
||||||
|
POSIX,
|
||||||
|
PIM,
|
||||||
|
RDF,
|
||||||
|
XSD,
|
||||||
|
SOLID_META,
|
||||||
|
PREFERRED_PREFIX_TERM,
|
||||||
|
} from '../util/Vocabularies';
|
||||||
import type { DataAccessor } from './accessors/DataAccessor';
|
import type { DataAccessor } from './accessors/DataAccessor';
|
||||||
import type { ResourceStore } from './ResourceStore';
|
import type { ResourceStore } from './ResourceStore';
|
||||||
|
|
||||||
@ -98,16 +109,23 @@ export class DataAccessorBasedStore implements ResourceStore {
|
|||||||
for await (const child of this.accessor.getChildren(identifier)) {
|
for await (const child of this.accessor.getChildren(identifier)) {
|
||||||
if (!this.auxiliaryStrategy.isAuxiliaryIdentifier({ path: child.identifier.value })) {
|
if (!this.auxiliaryStrategy.isAuxiliaryIdentifier({ path: child.identifier.value })) {
|
||||||
metadata.addQuads(child.quads());
|
metadata.addQuads(child.quads());
|
||||||
metadata.add(LDP.terms.contains, child.identifier as NamedNode);
|
metadata.add(LDP.terms.contains, child.identifier as NamedNode, SOLID_META.terms.ResponseMetadata);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a container representation from the metadata
|
// Generate a container representation from the metadata
|
||||||
const data = metadata.quads();
|
// All triples should be in the same graph for the data representation
|
||||||
metadata.addQuad(DC.terms.namespace, VANN.terms.preferredNamespacePrefix, 'dc');
|
const data = metadata.quads().map((triple): Quad => {
|
||||||
metadata.addQuad(LDP.terms.namespace, VANN.terms.preferredNamespacePrefix, 'ldp');
|
if (triple.graph.termType === 'DefaultGraph') {
|
||||||
metadata.addQuad(POSIX.terms.namespace, VANN.terms.preferredNamespacePrefix, 'posix');
|
return triple;
|
||||||
metadata.addQuad(XSD.terms.namespace, VANN.terms.preferredNamespacePrefix, 'xsd');
|
}
|
||||||
|
return DataFactory.quad(triple.subject, triple.predicate, triple.object);
|
||||||
|
});
|
||||||
|
|
||||||
|
metadata.addQuad(DC.terms.namespace, PREFERRED_PREFIX_TERM, 'dc', SOLID_META.terms.ResponseMetadata);
|
||||||
|
metadata.addQuad(LDP.terms.namespace, PREFERRED_PREFIX_TERM, 'ldp', SOLID_META.terms.ResponseMetadata);
|
||||||
|
metadata.addQuad(POSIX.terms.namespace, PREFERRED_PREFIX_TERM, 'posix', SOLID_META.terms.ResponseMetadata);
|
||||||
|
metadata.addQuad(XSD.terms.namespace, PREFERRED_PREFIX_TERM, 'xsd', SOLID_META.terms.ResponseMetadata);
|
||||||
representation = new BasicRepresentation(data, metadata, INTERNAL_QUADS);
|
representation = new BasicRepresentation(data, metadata, INTERNAL_QUADS);
|
||||||
} else {
|
} else {
|
||||||
// Retrieve a document representation from the accessor
|
// Retrieve a document representation from the accessor
|
||||||
@ -317,6 +335,11 @@ export class DataAccessorBasedStore implements ResourceStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove all generated metadata to prevent it from being stored permanently
|
||||||
|
representation.metadata.removeQuads(
|
||||||
|
representation.metadata.quads(null, null, null, SOLID_META.terms.ResponseMetadata),
|
||||||
|
);
|
||||||
|
|
||||||
await (isContainer ?
|
await (isContainer ?
|
||||||
this.accessor.writeContainer(identifier, representation.metadata) :
|
this.accessor.writeContainer(identifier, representation.metadata) :
|
||||||
this.accessor.writeDocument(identifier, representation.data, representation.metadata));
|
this.accessor.writeDocument(identifier, representation.data, representation.metadata));
|
||||||
|
@ -15,7 +15,7 @@ import { joinFilePath, isContainerIdentifier } from '../../util/PathUtil';
|
|||||||
import { parseQuads, serializeQuads } from '../../util/QuadUtil';
|
import { parseQuads, serializeQuads } from '../../util/QuadUtil';
|
||||||
import { addResourceMetadata } from '../../util/ResourceUtil';
|
import { addResourceMetadata } from '../../util/ResourceUtil';
|
||||||
import { toLiteral } from '../../util/TermUtil';
|
import { toLiteral } from '../../util/TermUtil';
|
||||||
import { CONTENT_TYPE, DC, LDP, POSIX, RDF, XSD } from '../../util/Vocabularies';
|
import { CONTENT_TYPE, DC, LDP, POSIX, RDF, SOLID_META, XSD } from '../../util/Vocabularies';
|
||||||
import type { FileIdentifierMapper, ResourceLink } from '../mapping/FileIdentifierMapper';
|
import type { FileIdentifierMapper, ResourceLink } from '../mapping/FileIdentifierMapper';
|
||||||
import type { DataAccessor } from './DataAccessor';
|
import type { DataAccessor } from './DataAccessor';
|
||||||
|
|
||||||
@ -319,10 +319,14 @@ export class FileDataAccessor implements DataAccessor {
|
|||||||
* @param stats - Stats of the file/directory corresponding to the resource.
|
* @param stats - Stats of the file/directory corresponding to the resource.
|
||||||
*/
|
*/
|
||||||
private addPosixMetadata(metadata: RepresentationMetadata, stats: Stats): void {
|
private addPosixMetadata(metadata: RepresentationMetadata, stats: Stats): void {
|
||||||
metadata.add(DC.terms.modified, toLiteral(stats.mtime.toISOString(), XSD.terms.dateTime));
|
metadata.add(DC.terms.modified,
|
||||||
metadata.add(POSIX.terms.mtime, toLiteral(Math.floor(stats.mtime.getTime() / 1000), XSD.terms.integer));
|
toLiteral(stats.mtime.toISOString(), XSD.terms.dateTime),
|
||||||
|
SOLID_META.terms.ResponseMetadata);
|
||||||
|
metadata.add(POSIX.terms.mtime,
|
||||||
|
toLiteral(Math.floor(stats.mtime.getTime() / 1000), XSD.terms.integer),
|
||||||
|
SOLID_META.terms.ResponseMetadata);
|
||||||
if (!stats.isDirectory()) {
|
if (!stats.isDirectory()) {
|
||||||
metadata.add(POSIX.terms.size, toLiteral(stats.size, XSD.terms.integer));
|
metadata.add(POSIX.terms.size, toLiteral(stats.size, XSD.terms.integer), SOLID_META.terms.ResponseMetadata);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,6 +128,11 @@ export const SOLID_HTTP = createUriAndTermNamespace('urn:npm:solid:community-ser
|
|||||||
'slug',
|
'slug',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const SOLID_META = createUriAndTermNamespace('urn:npm:solid:community-server:meta:',
|
||||||
|
// This identifier is used as graph for all metadata that is generated on the fly and should not be stored
|
||||||
|
'ResponseMetadata',
|
||||||
|
);
|
||||||
|
|
||||||
export const VANN = createUriAndTermNamespace('http://purl.org/vocab/vann/',
|
export const VANN = createUriAndTermNamespace('http://purl.org/vocab/vann/',
|
||||||
'preferredNamespacePrefix',
|
'preferredNamespacePrefix',
|
||||||
);
|
);
|
||||||
|
@ -2,6 +2,7 @@ import type { AuxiliaryIdentifierStrategy } from '../../../../src/ldp/auxiliary/
|
|||||||
import { LinkMetadataGenerator } from '../../../../src/ldp/auxiliary/LinkMetadataGenerator';
|
import { LinkMetadataGenerator } from '../../../../src/ldp/auxiliary/LinkMetadataGenerator';
|
||||||
import { RepresentationMetadata } from '../../../../src/ldp/representation/RepresentationMetadata';
|
import { RepresentationMetadata } from '../../../../src/ldp/representation/RepresentationMetadata';
|
||||||
import type { ResourceIdentifier } from '../../../../src/ldp/representation/ResourceIdentifier';
|
import type { ResourceIdentifier } from '../../../../src/ldp/representation/ResourceIdentifier';
|
||||||
|
import { SOLID_META } from '../../../../src/util/Vocabularies';
|
||||||
|
|
||||||
describe('A LinkMetadataGenerator', (): void => {
|
describe('A LinkMetadataGenerator', (): void => {
|
||||||
const link = 'link';
|
const link = 'link';
|
||||||
@ -35,5 +36,6 @@ describe('A LinkMetadataGenerator', (): void => {
|
|||||||
await expect(generator.handle(metadata)).resolves.toBeUndefined();
|
await expect(generator.handle(metadata)).resolves.toBeUndefined();
|
||||||
expect(metadata.quads()).toHaveLength(1);
|
expect(metadata.quads()).toHaveLength(1);
|
||||||
expect(metadata.get(link)?.value).toBe(auxiliaryId.path);
|
expect(metadata.get(link)?.value).toBe(auxiliaryId.path);
|
||||||
|
expect(metadata.getAll(link, SOLID_META.terms.ResponseMetadata)).toHaveLength(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -150,13 +150,13 @@ describe('A RepresentationMetadata', (): void => {
|
|||||||
it('can add a quad.', async(): Promise<void> => {
|
it('can add a quad.', async(): Promise<void> => {
|
||||||
const newQuad = quad(namedNode('random'), namedNode('new'), literal('triple'));
|
const newQuad = quad(namedNode('random'), namedNode('new'), literal('triple'));
|
||||||
metadata.addQuad('random', 'new', 'triple');
|
metadata.addQuad('random', 'new', 'triple');
|
||||||
expect(metadata.quads()).toBeRdfIsomorphic(inputQuads.concat([ newQuad ]));
|
expect(metadata.quads()).toBeRdfIsomorphic([ ...inputQuads, newQuad ]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can add a quad with a graph.', async(): Promise<void> => {
|
it('can add a quad with a graph.', async(): Promise<void> => {
|
||||||
const newQuad = quad(namedNode('random'), namedNode('new'), literal('triple'), namedNode('graph'));
|
const newQuad = quad(namedNode('random'), namedNode('new'), literal('triple'), namedNode('graph'));
|
||||||
metadata.addQuad('random', 'new', 'triple', 'graph');
|
metadata.addQuad('random', 'new', 'triple', 'graph');
|
||||||
expect(metadata.quads()).toBeRdfIsomorphic(inputQuads.concat([ newQuad ]));
|
expect(metadata.quads()).toBeRdfIsomorphic([ ...inputQuads, newQuad ]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can add quads.', async(): Promise<void> => {
|
it('can add quads.', async(): Promise<void> => {
|
||||||
|
@ -21,9 +21,8 @@ import type { Guarded } from '../../../src/util/GuardedStream';
|
|||||||
import { SingleRootIdentifierStrategy } from '../../../src/util/identifiers/SingleRootIdentifierStrategy';
|
import { SingleRootIdentifierStrategy } from '../../../src/util/identifiers/SingleRootIdentifierStrategy';
|
||||||
import { trimTrailingSlashes } from '../../../src/util/PathUtil';
|
import { trimTrailingSlashes } from '../../../src/util/PathUtil';
|
||||||
import { guardedStreamFrom } from '../../../src/util/StreamUtil';
|
import { guardedStreamFrom } from '../../../src/util/StreamUtil';
|
||||||
import { CONTENT_TYPE, SOLID_HTTP, LDP, PIM, RDF } from '../../../src/util/Vocabularies';
|
import { CONTENT_TYPE, SOLID_HTTP, LDP, PIM, RDF, SOLID_META } from '../../../src/util/Vocabularies';
|
||||||
import quad = DataFactory.quad;
|
const { namedNode, quad } = DataFactory;
|
||||||
import namedNode = DataFactory.namedNode;
|
|
||||||
|
|
||||||
class SimpleDataAccessor implements DataAccessor {
|
class SimpleDataAccessor implements DataAccessor {
|
||||||
public readonly data: Record<string, Representation> = {};
|
public readonly data: Record<string, Representation> = {};
|
||||||
@ -379,8 +378,8 @@ describe('A DataAccessorBasedStore', (): void => {
|
|||||||
it('can write resources.', async(): Promise<void> => {
|
it('can write resources.', async(): Promise<void> => {
|
||||||
const resourceID = { path: `${root}resource` };
|
const resourceID = { path: `${root}resource` };
|
||||||
await expect(store.setRepresentation(resourceID, representation)).resolves.toEqual([
|
await expect(store.setRepresentation(resourceID, representation)).resolves.toEqual([
|
||||||
{ path: 'http://test.com/' },
|
{ path: root },
|
||||||
{ path: 'http://test.com/resource' },
|
{ path: `${root}resource` },
|
||||||
]);
|
]);
|
||||||
await expect(arrayifyStream(accessor.data[resourceID.path].data)).resolves.toEqual([ resourceData ]);
|
await expect(arrayifyStream(accessor.data[resourceID.path].data)).resolves.toEqual([ resourceData ]);
|
||||||
});
|
});
|
||||||
@ -393,13 +392,26 @@ describe('A DataAccessorBasedStore', (): void => {
|
|||||||
representation.metadata.contentType = 'text/turtle';
|
representation.metadata.contentType = 'text/turtle';
|
||||||
representation.data = guardedStreamFrom([ `<${`${root}resource/`}> a <coolContainer>.` ]);
|
representation.data = guardedStreamFrom([ `<${`${root}resource/`}> a <coolContainer>.` ]);
|
||||||
await expect(store.setRepresentation(resourceID, representation)).resolves.toEqual([
|
await expect(store.setRepresentation(resourceID, representation)).resolves.toEqual([
|
||||||
{ path: `${root}` },
|
{ path: root },
|
||||||
{ path: `${root}container/` },
|
{ path: `${root}container/` },
|
||||||
]);
|
]);
|
||||||
expect(accessor.data[resourceID.path]).toBeTruthy();
|
expect(accessor.data[resourceID.path]).toBeTruthy();
|
||||||
expect(accessor.data[resourceID.path].metadata.contentType).toBeUndefined();
|
expect(accessor.data[resourceID.path].metadata.contentType).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not write generated metadata.', async(): Promise<void> => {
|
||||||
|
const resourceID = { path: `${root}resource` };
|
||||||
|
representation.metadata.add('notGen', 'value');
|
||||||
|
representation.metadata.add('gen', 'value', SOLID_META.terms.ResponseMetadata);
|
||||||
|
await expect(store.setRepresentation(resourceID, representation)).resolves.toEqual([
|
||||||
|
{ path: root },
|
||||||
|
{ path: `${root}resource` },
|
||||||
|
]);
|
||||||
|
await expect(arrayifyStream(accessor.data[resourceID.path].data)).resolves.toEqual([ resourceData ]);
|
||||||
|
expect(accessor.data[resourceID.path].metadata.get('notGen')?.value).toBe('value');
|
||||||
|
expect(accessor.data[resourceID.path].metadata.get('gen')).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
it('can write resources even if root does not exist.', async(): Promise<void> => {
|
it('can write resources even if root does not exist.', async(): Promise<void> => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||||
delete accessor.data[root];
|
delete accessor.data[root];
|
||||||
|
@ -11,9 +11,10 @@ import { NotFoundHttpError } from '../../../../src/util/errors/NotFoundHttpError
|
|||||||
import type { SystemError } from '../../../../src/util/errors/SystemError';
|
import type { SystemError } from '../../../../src/util/errors/SystemError';
|
||||||
import { UnsupportedMediaTypeHttpError } from '../../../../src/util/errors/UnsupportedMediaTypeHttpError';
|
import { UnsupportedMediaTypeHttpError } from '../../../../src/util/errors/UnsupportedMediaTypeHttpError';
|
||||||
import type { Guarded } from '../../../../src/util/GuardedStream';
|
import type { Guarded } from '../../../../src/util/GuardedStream';
|
||||||
|
import { isContainerPath } from '../../../../src/util/PathUtil';
|
||||||
import { guardedStreamFrom, readableToString } from '../../../../src/util/StreamUtil';
|
import { guardedStreamFrom, readableToString } from '../../../../src/util/StreamUtil';
|
||||||
import { toLiteral } from '../../../../src/util/TermUtil';
|
import { toLiteral } from '../../../../src/util/TermUtil';
|
||||||
import { CONTENT_TYPE, DC, LDP, POSIX, RDF, XSD } from '../../../../src/util/Vocabularies';
|
import { CONTENT_TYPE, DC, LDP, POSIX, RDF, SOLID_META, XSD } from '../../../../src/util/Vocabularies';
|
||||||
import { mockFs } from '../../../util/Util';
|
import { mockFs } from '../../../util/Util';
|
||||||
|
|
||||||
jest.mock('fs');
|
jest.mock('fs');
|
||||||
@ -102,6 +103,7 @@ describe('A FileDataAccessor', (): void => {
|
|||||||
expect(metadata.get(POSIX.size)).toEqualRdfTerm(toLiteral('data'.length, XSD.terms.integer));
|
expect(metadata.get(POSIX.size)).toEqualRdfTerm(toLiteral('data'.length, XSD.terms.integer));
|
||||||
expect(metadata.get(DC.modified)).toEqualRdfTerm(toLiteral(now.toISOString(), XSD.terms.dateTime));
|
expect(metadata.get(DC.modified)).toEqualRdfTerm(toLiteral(now.toISOString(), XSD.terms.dateTime));
|
||||||
expect(metadata.get(POSIX.mtime)).toEqualRdfTerm(toLiteral(Math.floor(now.getTime() / 1000), XSD.terms.integer));
|
expect(metadata.get(POSIX.mtime)).toEqualRdfTerm(toLiteral(Math.floor(now.getTime() / 1000), XSD.terms.integer));
|
||||||
|
expect(metadata.quads(null, null, null, SOLID_META.terms.ResponseMetadata)).toHaveLength(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not generate size metadata for a container.', async(): Promise<void> => {
|
it('does not generate size metadata for a container.', async(): Promise<void> => {
|
||||||
@ -121,6 +123,7 @@ describe('A FileDataAccessor', (): void => {
|
|||||||
expect(metadata.get(POSIX.size)).toBeUndefined();
|
expect(metadata.get(POSIX.size)).toBeUndefined();
|
||||||
expect(metadata.get(DC.modified)).toEqualRdfTerm(toLiteral(now.toISOString(), XSD.terms.dateTime));
|
expect(metadata.get(DC.modified)).toEqualRdfTerm(toLiteral(now.toISOString(), XSD.terms.dateTime));
|
||||||
expect(metadata.get(POSIX.mtime)).toEqualRdfTerm(toLiteral(Math.floor(now.getTime() / 1000), XSD.terms.integer));
|
expect(metadata.get(POSIX.mtime)).toEqualRdfTerm(toLiteral(Math.floor(now.getTime() / 1000), XSD.terms.integer));
|
||||||
|
expect(metadata.quads(null, null, null, SOLID_META.terms.ResponseMetadata)).toHaveLength(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('generates metadata for container child resources.', async(): Promise<void> => {
|
it('generates metadata for container child resources.', async(): Promise<void> => {
|
||||||
@ -136,6 +139,8 @@ describe('A FileDataAccessor', (): void => {
|
|||||||
expect(child.get(DC.modified)).toEqualRdfTerm(toLiteral(now.toISOString(), XSD.terms.dateTime));
|
expect(child.get(DC.modified)).toEqualRdfTerm(toLiteral(now.toISOString(), XSD.terms.dateTime));
|
||||||
expect(child.get(POSIX.mtime)).toEqualRdfTerm(toLiteral(Math.floor(now.getTime() / 1000),
|
expect(child.get(POSIX.mtime)).toEqualRdfTerm(toLiteral(Math.floor(now.getTime() / 1000),
|
||||||
XSD.terms.integer));
|
XSD.terms.integer));
|
||||||
|
expect(child.quads(null, null, null, SOLID_META.terms.ResponseMetadata))
|
||||||
|
.toHaveLength(isContainerPath(child.identifier.value) ? 2 : 3);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user