diff --git a/API.paw b/API.paw index 23c6fc5900c70514dd3964d73d3551ee600b8be3..18697cc4589c23693eb11a1af634fb2b34a16d3a 100644 Binary files a/API.paw and b/API.paw differ diff --git a/src/controller/ComponentController.ts b/src/controller/ComponentController.ts index 2d77b2a8d320e1fc6649d96cea901f3bbd407a1e..587993f56c2560bc03c361057f311f95db6d6bd5 100644 --- a/src/controller/ComponentController.ts +++ b/src/controller/ComponentController.ts @@ -107,6 +107,9 @@ export default class ComponentController extends BaseController { const topic = request.body.topic; const parentComponentId: string = request.body.parentComponentId || Component.rootId; const measurementTargetIds: string[] = request.body.measurementTargets || []; + const componentLicense: string = request.body.componentLicense; + const informationLicense: string = request.body.informationLicense; + const measurementLicense: string = request.body.measurementLicense; this.componentRepository.findOne(parentComponentId, { relations: ["isComponentOf"] }).then(async (parentComponent) => { if(!parentComponent) { @@ -157,7 +160,7 @@ export default class ComponentController extends BaseController { let childComponent: Component; try { - childComponent = Component.create(informationName, comment, topic, measurementTargets, type, metadata); + childComponent = Component.create(informationName, comment, topic, measurementTargets, type, metadata, componentLicense, informationLicense, measurementLicense); } catch(error) { // Verification of information schema failed. logger.error(`information verification failed: ${error}`); @@ -165,9 +168,31 @@ export default class ComponentController extends BaseController { response.send(); return; } + + let relationLicense: string; + + if(request.body.parentComponentId) { + if(request.body.relationLicense) { + relationLicense = request.body.relationLicense; + } else { + response.status(400); + this.setJSONLDResponseType(response); + response.send({ + "@context": { + "message": "https://schema.org/error" + }, + "message": "License URL for parent relation not set." + }); + return; + } + } else { + // This relation is a relation to the root component which is + // internal only and thus does not need a license. + relationLicense = ""; + } await this.componentRepository.save(childComponent); - const relation = Component.addSubComponent(parentComponent, childComponent); + const relation = Component.addSubComponent(parentComponent, childComponent, relationLicense); await this.componentRelationRepository.save(relation); const childComponentJsonLd = await childComponent.toJSONLD(); this.setJSONLDResponseType(response); diff --git a/src/controller/ComponentInformationController.ts b/src/controller/ComponentInformationController.ts index 333c3a50862d277c1267fc27561af3f6ee3fd2be..a2274e36155671c6a89a80bb406d58fc26b39516 100644 --- a/src/controller/ComponentInformationController.ts +++ b/src/controller/ComponentInformationController.ts @@ -75,6 +75,7 @@ export default class ComponentInformationController extends BaseController { createNewVersion = async (request: Request, response: Response) => { const oldVersionId = request.params.oldVersionId; + this.componentInformationRepository.findOne(oldVersionId, { relations: ["component", "nextVersion"] }).then(async (oldVersionInformation) => { if (!oldVersionInformation) { logger.error("Cannot find information with id: ", request.params.oldVersionId); @@ -98,6 +99,8 @@ export default class ComponentInformationController extends BaseController { const metadata = request.body.metadata; const topic = request.body.topic; const measurementTargetIds: string[] = request.body.measurementTargets || []; + const informationLicense: string = request.body.informationLicense; + const measurementLicense: string = request.body.measurementLicense; // First check if the topic is already taken. const currentInformationWithTopic: ComponentInformation[] = await ComponentInformation.findCurrentVersionForTopic(topic, this.componentInformationRepository); @@ -148,7 +151,7 @@ export default class ComponentInformationController extends BaseController { } } - const newInformation = new ComponentInformation(informationName, measurementTargets, comment, topic, type, metadata); + const newInformation = new ComponentInformation(informationName, measurementTargets, comment, topic, informationLicense, measurementLicense, type, metadata); ComponentInformation.linkNewVersion(oldVersionInformation, newInformation); await this.componentInformationRepository.save(oldVersionInformation); const newInformationJsonLd = newInformation.toJSONLD(); diff --git a/src/controller/ComponentRelationController.ts b/src/controller/ComponentRelationController.ts index 4e9d8b3dd18aff8ebf1eabc4feb87100488a4880..5362763da1b3bbe7d3737e11113dab95e9b54a87 100644 --- a/src/controller/ComponentRelationController.ts +++ b/src/controller/ComponentRelationController.ts @@ -113,7 +113,9 @@ export default class ComponentRelationController extends BaseController { let newRelation: ComponentRelation | undefined; if(newParentComponent) { - newRelation = Component.addSubComponent(newParentComponent, component); + const relationLicense: string = request.body.relationLicense; + + newRelation = Component.addSubComponent(newParentComponent, component, relationLicense); newRelation.from = creationDate; } diff --git a/src/controller/TypeDefinitionController.ts b/src/controller/TypeDefinitionController.ts index f4a26761cd47a83adc3a4364c29c1558c331f562..dede4f2512de8956df442c2c17d896becfb2a96e 100644 --- a/src/controller/TypeDefinitionController.ts +++ b/src/controller/TypeDefinitionController.ts @@ -69,7 +69,8 @@ export default class TypeDefinitionController extends BaseController { name: request.body.name, comment: comment, context: request.body.context, - schema: schema + schema: schema, + license: request.body.license }).then(() => { return this.repository.findOneOrFail({ name: request.body.name }); }).then((newDefinition) => { diff --git a/src/entity/Component.ts b/src/entity/Component.ts index 53c68824b54e765c605c7218832bb6c58a4a2ef4..90aab338c0c55393e75e2c9ab8cb651c6efe9670 100644 --- a/src/entity/Component.ts +++ b/src/entity/Component.ts @@ -1,5 +1,6 @@ import { PrimaryGeneratedColumn, + Column, CreateDateColumn, Entity, Repository, @@ -39,6 +40,9 @@ export default class Component { }) dateCreated!: Date; + @Column() + license!: string; + // Sub/Parent Component Relation @OneToMany(() => ComponentRelation, relation => relation.child, { @@ -154,7 +158,7 @@ export default class Component { .getOne(); } - static create(name: string, comment: string, topic: string, measurementTargets: Component[], type: TypeDefinition, data: Record<string, unknown>): Component { + static create(name: string, comment: string, topic: string, measurementTargets: Component[], type: TypeDefinition, data: Record<string, unknown>, componentLicense: string, informationLicense: string, measurementLicense: string): Component { // Create a component with an initial information object attached to it. if (!type.validateData(data)) { throw new Error("Information does not fulfill type definition schema requirements."); @@ -171,13 +175,14 @@ export default class Component { targets = measurementTargets; } - const information = new ComponentInformation(name, targets, comment, topic, type, data); + const information = new ComponentInformation(name, targets, comment, topic,informationLicense, measurementLicense , type, data); component.information = [information]; + component.license = componentLicense; return component; } - static addSubComponent(parent: Component, child: Component): ComponentRelation { - return new ComponentRelation(parent, child); + static addSubComponent(parent: Component, child: Component, relationLicense: string): ComponentRelation { + return new ComponentRelation(parent, child, relationLicense); } toJSONLD(): NodeObject { @@ -185,7 +190,8 @@ export default class Component { "@context": ContextDefinitions.component, "@type": "Component", "identifier": { "@id": `${config.baseURL}/component/${this.id.toString()}`}, - "dateCreated": this.dateCreated.toISOString() + "dateCreated": this.dateCreated.toISOString(), + "license": this.license }; if (this.isComponentOf) { diff --git a/src/entity/ComponentInformation.ts b/src/entity/ComponentInformation.ts index 8aec7d2f8ae849ef1e17ccd2ee3bd4aff1622118..26d3641e06688434b1d9960eacd6033a2f6b862f 100644 --- a/src/entity/ComponentInformation.ts +++ b/src/entity/ComponentInformation.ts @@ -47,6 +47,12 @@ export default class ComponentInformation { @Column() topic: string; + @Column() + informationLicense!: string; + + @Column() + measurementLicense!: string; + // Versioning Relation @OneToOne(() => ComponentInformation, component => component.previousVersion, { @@ -80,13 +86,15 @@ export default class ComponentInformation { // Constructor - constructor(name: string, measurementTargets: Component[], comment: string, topic: string, type: TypeDefinition, metadata: Record<string, unknown>) { + constructor(name: string, measurementTargets: Component[], comment: string, topic: string, informationLicense: string, measurementLicense: string, type: TypeDefinition, metadata: Record<string, unknown>) { this.name = name; this.measurementTargets = measurementTargets; this.comment = comment; this.metadata = metadata; this.type = type; this.topic = topic; + this.informationLicense = informationLicense; + this.measurementLicense = measurementLicense; } // Database Queries @@ -166,7 +174,9 @@ export default class ComponentInformation { "dateCreated": this.dateCreated.toISOString(), "name": this.name, "comment": this.comment, - "metadata": this.type.toJSONLD(this.metadata) + "metadata": this.type.toJSONLD(this.metadata), + "informationLicense": this.informationLicense, + "measurementLicense": this.measurementLicense }; return document; diff --git a/src/entity/ComponentRelation.ts b/src/entity/ComponentRelation.ts index 6b67b541b0e4a1a7d1a6d60012d39848f6a29870..76540f93362ab7011f5f851d8e1ea265b66a65df 100644 --- a/src/entity/ComponentRelation.ts +++ b/src/entity/ComponentRelation.ts @@ -30,6 +30,9 @@ export default class ComponentRelation { }) to?: Date; + @Column() + license!: string; + // Relations @ManyToOne(() => Component, component => component.subComponents) @@ -40,9 +43,10 @@ export default class ComponentRelation { // Initializer - constructor(parent: Component, child: Component) { + constructor(parent: Component, child: Component, license: string) { this.parent = parent; this.child = child; + this.license = license; } static find(filter: string | undefined, pageSize: number, page: number, repository: Repository<ComponentRelation>): Promise<ComponentRelation[]> { @@ -80,6 +84,7 @@ export default class ComponentRelation { const document: NodeObject = { "@context": ContextDefinitions.componentRelation, "@type": "ComponentRelation", + "license": this.license, "identifier": { "@id": `${config.baseURL}/relation/${this.id.toString()}`}, "from": this.from.toISOString(), "to": null, diff --git a/src/entity/Measurement.ts b/src/entity/Measurement.ts index 22a01bd69687be0b2568cfa8a7473da458a67d31..277471fc47d64f5383cfdeae1f5b7e1efacc28fe 100644 --- a/src/entity/Measurement.ts +++ b/src/entity/Measurement.ts @@ -28,6 +28,9 @@ export default class Measurement { @Column({ type: "jsonb", nullable: true }) metadata?: unknown; + @Column() + license!: string; + // Component Relation @ManyToOne(() => ComponentInformation, information => information.measurements, { nullable: false }) @@ -48,8 +51,9 @@ export default class Measurement { // Constructor - constructor(timestamp: Date, value: unknown, valueType: TypeDefinition, metadata: unknown | undefined, metadataType: TypeDefinition | undefined, componentInformation: ComponentInformation) { + constructor(timestamp: Date, license: string, value: unknown, valueType: TypeDefinition, metadata: unknown | undefined, metadataType: TypeDefinition | undefined, componentInformation: ComponentInformation) { this.dateCreated = timestamp; + this.license = license; this.value = value; this.valueType = valueType; this.metadata = metadata; @@ -117,6 +121,7 @@ export default class Measurement { "@context": ContextDefinitions.measurement(this.valueType.context, this.metadataType?.context), "identifier": { "@id": `${config.baseURL}/measurement/${this.id.toString()}` }, "dateCreated": this.dateCreated.toISOString(), + "license": this.license, "value": this.value }; diff --git a/src/entity/TypeDefinition.ts b/src/entity/TypeDefinition.ts index b7112bfa7aefa629e5b67368b7ae00a413c9012d..c458db8948f4f7de6937da2e27d2abfef4492a17 100644 --- a/src/entity/TypeDefinition.ts +++ b/src/entity/TypeDefinition.ts @@ -36,6 +36,9 @@ export default class TypeDefinition { }) context: Record<string, unknown>; + @Column() + license!: string; + // Relations @OneToMany(() => ComponentInformation, information => information.type) @@ -47,11 +50,12 @@ export default class TypeDefinition { @OneToMany(() => Measurement, measurement => measurement.metadataType) isTypeOfMeasurementMetadata?: Measurement; - constructor(name: string, comment: string, schema: Record<string, unknown>, context: Record<string, unknown>) { + constructor(name: string, comment: string, schema: Record<string, unknown>, context: Record<string, unknown>, license: string) { this.name = name; this.comment = comment; this.schema = schema; this.context = context; + this.license = license; } static findByName(name: string, repository: Repository<TypeDefinition>): Promise<TypeDefinition | undefined> { diff --git a/src/middleware/ValidationErrorMiddleware.ts b/src/middleware/ValidationErrorMiddleware.ts index c64b9d64dcb12f43a3888580a4833d08bba4d39c..f43008615153f229ddfbe8ef0c1cb3f52194aa51 100644 --- a/src/middleware/ValidationErrorMiddleware.ts +++ b/src/middleware/ValidationErrorMiddleware.ts @@ -8,6 +8,17 @@ export default (request: Request, response: Response, next: NextFunction): void return; } - response.status(400).send({ errors: result.array() }); + response.status(400); + response.type("application/ld+json"); + response.send( + result.array().map((error) => { + return { + "@context": { + "message": "https://schema.org/error" + }, + "message": error.msg + }; + }) + ); return; }; diff --git a/src/migration/1645518654632-SeedRootComponent.ts b/src/migration/1645518654632-SeedRootComponent.ts index cb7efd1a7ec7a5a681bf1589aed0a9923800e59a..8d6020dadce73fafa2c2fccd9a20056da54ff36c 100644 --- a/src/migration/1645518654632-SeedRootComponent.ts +++ b/src/migration/1645518654632-SeedRootComponent.ts @@ -11,6 +11,7 @@ export class SeedRootComponent1645518654632 implements MigrationInterface { const repository = await queryRunner.connection.getRepository(Component); const rootComponent = new Component(); rootComponent.id = Component.rootId; + rootComponent.license = ""; await repository.save(rootComponent); } diff --git a/src/services/IngestionService.ts b/src/services/IngestionService.ts index 339ef3f901bed02b9232df9318e31aed60323bab..555541de3f5c2984da2b321fbdc13718322b66b8 100644 --- a/src/services/IngestionService.ts +++ b/src/services/IngestionService.ts @@ -128,7 +128,7 @@ class IngestionService { } const information: ComponentInformation = linkedInformation[0]; - const measurement = new Measurement(new Date(messageContent.timestamp), messageContent.value, valueType, metadata, metadataType, information); + const measurement = new Measurement(new Date(messageContent.timestamp), information.measurementLicense, messageContent.value, valueType, metadata, metadataType, information); measurement.targets = information.measurementTargets; this.measurementRepository.save(measurement); diff --git a/src/util/ContextDefinitions.ts b/src/util/ContextDefinitions.ts index 9009e76adeba8b9cbda710be68f6ed27933dad70..c2bc4d941a2efd3d48217f40d847d620b141aed6 100644 --- a/src/util/ContextDefinitions.ts +++ b/src/util/ContextDefinitions.ts @@ -1,25 +1,30 @@ const component = { "id": { "@id": "https://schema.org/url", "@type": "@id"}, - "dateCreated": "https://schema.org/DateTime" + "dateCreated": "https://schema.org/DateTime", + "license": "https://schema.org/license" }; const componentRelation = { "id": { "@id": "https://schema.org/url", "@type": "@id"}, "from": "https://schema.org/DateTime", - "to": "https://schema.org/DateTime" + "to": "https://schema.org/DateTime", + "license": "https://schema.org/license" }; const information = { "id": { "@id": "https://schema.org/url", "@type": "@id"}, "dateCreated": "https://schema.org/DateTime", "name": "https://schema.org/name", - "comment": "http://schema.org/comment" + "comment": "http://schema.org/comment", + "informationLicense": "https://schema.org/license", + "measurementLicense": "https://schema.org/license" }; const measurement = function(valueContext: unknown, metadataContext: unknown | undefined): Record<string, unknown> { const result: Record<string, unknown> = { "id": { "@id": "https://schema.org/url", "@type": "@id"}, "dateCreated": "https://schema.org/DateTime", + "license": "https://schema.org/license", "value": valueContext }; diff --git a/src/util/SchemaDefinitions.ts b/src/util/SchemaDefinitions.ts index f0bbdcd12271b9fae45631373f5a3f2fc177a0c3..11db23e442a9670c8fd54130ac13278a5d139b5f 100644 --- a/src/util/SchemaDefinitions.ts +++ b/src/util/SchemaDefinitions.ts @@ -1,4 +1,4 @@ -import { Schema } from "express-validator"; +import { Schema, ParamSchema } from "express-validator"; const paginationSchema: Schema = { page: { @@ -15,6 +15,12 @@ const paginationSchema: Schema = { } }; +const licenseSchema: ParamSchema = { + in: ["body"], + isURL: true, + errorMessage: "No valid license URL set." +}; + const filterQuerySchema: Schema = { filter: { in: ["query"], @@ -87,7 +93,8 @@ const createRelationSchema: Schema = { in: ["body"], errorMessage: "New Parent ID is not set.", isUUID: true - } + }, + relationLicense: licenseSchema }; const getInformationSchema: Schema = { @@ -143,7 +150,9 @@ const createInformationSchema: Schema = { isString: true, errorMessage: "Measurement targets must be UUIDs.", optional: true - } + }, + "informationLicense": licenseSchema, + "measurementLicense": licenseSchema }; const createInformationVersionSchema: Schema = { @@ -181,6 +190,11 @@ const createComponentSchema: Schema = { errorMessage: "Parent ID not set.", isUUID: true, optional: true + }, + componentLicense: licenseSchema, + relationLicense: { + ...licenseSchema, + optional: true } }; @@ -218,7 +232,8 @@ const createTypeDefinitionBaseSchema: Schema = { in: ["body"], errorMessage: "No schema definition set.", isObject: true - } + }, + license: licenseSchema }; const createTypeDefinitionURLContextSchema: Schema = {