From 2ec7873e6b573c153882a8009ac98f32faea19bc Mon Sep 17 00:00:00 2001 From: Milena Czierlinski <146972016+Milena-Czierlinski@users.noreply.github.com> Date: Tue, 25 Jun 2024 16:10:33 +0200 Subject: [PATCH] Feature/Default expiration date for files (#165) * feat: provide default expiration date for files * refactor: un-nest condition * refactor: change format of date strings * feat: integrate comments * refactor: use CoreDate.utc() --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- package-lock.json | 2 +- packages/runtime/package.json | 2 +- .../runtime/src/useCases/common/Schemas.ts | 437 +++++++++++++++--- .../useCases/transport/files/UploadOwnFile.ts | 10 +- packages/runtime/test/transport/files.test.ts | 15 +- 5 files changed, 383 insertions(+), 83 deletions(-) diff --git a/package-lock.json b/package-lock.json index 11f0bbde4..fb76763cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12604,7 +12604,7 @@ }, "packages/runtime": { "name": "@nmshd/runtime", - "version": "4.12.1", + "version": "4.13.0", "license": "MIT", "dependencies": { "@js-soft/docdb-querytranslator": "^1.1.4", diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 093695a3c..95b9c49e3 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@nmshd/runtime", - "version": "4.12.1", + "version": "4.13.0", "description": "The enmeshed client runtime.", "homepage": "https://enmeshed.eu", "repository": { diff --git a/packages/runtime/src/useCases/common/Schemas.ts b/packages/runtime/src/useCases/common/Schemas.ts index 30cff901d..c502aad1c 100644 --- a/packages/runtime/src/useCases/common/Schemas.ts +++ b/packages/runtime/src/useCases/common/Schemas.ts @@ -273,6 +273,9 @@ export const CanCreateOutgoingRequestRequest: any = { { "$ref": "#/definitions/CreateAttributeRequestItemJSON" }, + { + "$ref": "#/definitions/DeleteAttributeRequestItemJSON" + }, { "$ref": "#/definitions/ShareAttributeRequestItemJSON" }, @@ -2296,6 +2299,50 @@ export const CanCreateOutgoingRequestRequest: any = { ], "additionalProperties": false }, + "DeleteAttributeRequestItemJSON": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "const": "DeleteAttributeRequestItem" + }, + "@context": { + "type": "string" + }, + "@version": { + "type": "string" + }, + "title": { + "type": "string", + "description": "The human-readable title of this item." + }, + "description": { + "type": "string", + "description": "The human-readable description of this item." + }, + "metadata": { + "type": "object", + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + }, + "mustBeAccepted": { + "type": "boolean", + "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + }, + "requireManualDecision": { + "type": "boolean", + "description": "If set to `true`, it advices the recipient of this RequestItem to carefully consider their decision and especially do not decide based on some automation rules." + }, + "attributeId": { + "type": "string" + } + }, + "required": [ + "@type", + "attributeId", + "mustBeAccepted" + ], + "additionalProperties": false + }, "ShareAttributeRequestItemJSON": { "type": "object", "properties": { @@ -3191,6 +3238,12 @@ export const CompleteOutgoingRequestRequest: any = { { "$ref": "#/definitions/AcceptResponseItemJSON" }, + { + "$ref": "#/definitions/AttributeAlreadySharedAcceptResponseItemJSON" + }, + { + "$ref": "#/definitions/AttributeSuccessionAcceptResponseItemJSON" + }, { "$ref": "#/definitions/CreateAttributeAcceptResponseItemJSON" }, @@ -3234,12 +3287,12 @@ export const CompleteOutgoingRequestRequest: any = { ], "additionalProperties": false }, - "CreateAttributeAcceptResponseItemJSON": { + "AttributeAlreadySharedAcceptResponseItemJSON": { "type": "object", "properties": { "@type": { "type": "string", - "const": "CreateAttributeAcceptResponseItem" + "const": "AttributeAlreadySharedAcceptResponseItem" }, "@context": { "type": "string" @@ -3262,12 +3315,12 @@ export const CompleteOutgoingRequestRequest: any = { ], "additionalProperties": false }, - "ShareAttributeAcceptResponseItemJSON": { + "AttributeSuccessionAcceptResponseItemJSON": { "type": "object", "properties": { "@type": { "type": "string", - "const": "ShareAttributeAcceptResponseItem" + "const": "AttributeSuccessionAcceptResponseItem" }, "@context": { "type": "string" @@ -3279,38 +3332,13 @@ export const CompleteOutgoingRequestRequest: any = { "type": "string", "const": "Accepted" }, - "attributeId": { - "type": "string" - } - }, - "required": [ - "@type", - "attributeId", - "result" - ], - "additionalProperties": false - }, - "ProposeAttributeAcceptResponseItemJSON": { - "type": "object", - "properties": { - "@type": { - "type": "string", - "const": "ProposeAttributeAcceptResponseItem" - }, - "@context": { + "predecessorId": { "type": "string" }, - "@version": { + "successorId": { "type": "string" }, - "result": { - "type": "string", - "const": "Accepted" - }, - "attributeId": { - "type": "string" - }, - "attribute": { + "successorContent": { "anyOf": [ { "$ref": "#/definitions/IdentityAttributeJSON" @@ -3323,9 +3351,10 @@ export const CompleteOutgoingRequestRequest: any = { }, "required": [ "@type", - "attribute", - "attributeId", - "result" + "predecessorId", + "result", + "successorContent", + "successorId" ], "additionalProperties": false }, @@ -5239,6 +5268,101 @@ export const CompleteOutgoingRequestRequest: any = { "protected" ] }, + "CreateAttributeAcceptResponseItemJSON": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "const": "CreateAttributeAcceptResponseItem" + }, + "@context": { + "type": "string" + }, + "@version": { + "type": "string" + }, + "result": { + "type": "string", + "const": "Accepted" + }, + "attributeId": { + "type": "string" + } + }, + "required": [ + "@type", + "attributeId", + "result" + ], + "additionalProperties": false + }, + "ShareAttributeAcceptResponseItemJSON": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "const": "ShareAttributeAcceptResponseItem" + }, + "@context": { + "type": "string" + }, + "@version": { + "type": "string" + }, + "result": { + "type": "string", + "const": "Accepted" + }, + "attributeId": { + "type": "string" + } + }, + "required": [ + "@type", + "attributeId", + "result" + ], + "additionalProperties": false + }, + "ProposeAttributeAcceptResponseItemJSON": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "const": "ProposeAttributeAcceptResponseItem" + }, + "@context": { + "type": "string" + }, + "@version": { + "type": "string" + }, + "result": { + "type": "string", + "const": "Accepted" + }, + "attributeId": { + "type": "string" + }, + "attribute": { + "anyOf": [ + { + "$ref": "#/definitions/IdentityAttributeJSON" + }, + { + "$ref": "#/definitions/RelationshipAttributeJSON" + } + ] + } + }, + "required": [ + "@type", + "attribute", + "attributeId", + "result" + ], + "additionalProperties": false + }, "ReadAttributeAcceptResponseItemJSON": { "type": "object", "properties": { @@ -5544,6 +5668,12 @@ export const CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseReq { "$ref": "#/definitions/AcceptResponseItemJSON" }, + { + "$ref": "#/definitions/AttributeAlreadySharedAcceptResponseItemJSON" + }, + { + "$ref": "#/definitions/AttributeSuccessionAcceptResponseItemJSON" + }, { "$ref": "#/definitions/CreateAttributeAcceptResponseItemJSON" }, @@ -5587,12 +5717,12 @@ export const CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseReq ], "additionalProperties": false }, - "CreateAttributeAcceptResponseItemJSON": { + "AttributeAlreadySharedAcceptResponseItemJSON": { "type": "object", "properties": { "@type": { "type": "string", - "const": "CreateAttributeAcceptResponseItem" + "const": "AttributeAlreadySharedAcceptResponseItem" }, "@context": { "type": "string" @@ -5615,12 +5745,12 @@ export const CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseReq ], "additionalProperties": false }, - "ShareAttributeAcceptResponseItemJSON": { + "AttributeSuccessionAcceptResponseItemJSON": { "type": "object", "properties": { "@type": { "type": "string", - "const": "ShareAttributeAcceptResponseItem" + "const": "AttributeSuccessionAcceptResponseItem" }, "@context": { "type": "string" @@ -5632,38 +5762,13 @@ export const CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseReq "type": "string", "const": "Accepted" }, - "attributeId": { - "type": "string" - } - }, - "required": [ - "@type", - "attributeId", - "result" - ], - "additionalProperties": false - }, - "ProposeAttributeAcceptResponseItemJSON": { - "type": "object", - "properties": { - "@type": { - "type": "string", - "const": "ProposeAttributeAcceptResponseItem" - }, - "@context": { - "type": "string" - }, - "@version": { + "predecessorId": { "type": "string" }, - "result": { - "type": "string", - "const": "Accepted" - }, - "attributeId": { + "successorId": { "type": "string" }, - "attribute": { + "successorContent": { "anyOf": [ { "$ref": "#/definitions/IdentityAttributeJSON" @@ -5676,9 +5781,10 @@ export const CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseReq }, "required": [ "@type", - "attribute", - "attributeId", - "result" + "predecessorId", + "result", + "successorContent", + "successorId" ], "additionalProperties": false }, @@ -7592,6 +7698,101 @@ export const CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponseReq "protected" ] }, + "CreateAttributeAcceptResponseItemJSON": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "const": "CreateAttributeAcceptResponseItem" + }, + "@context": { + "type": "string" + }, + "@version": { + "type": "string" + }, + "result": { + "type": "string", + "const": "Accepted" + }, + "attributeId": { + "type": "string" + } + }, + "required": [ + "@type", + "attributeId", + "result" + ], + "additionalProperties": false + }, + "ShareAttributeAcceptResponseItemJSON": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "const": "ShareAttributeAcceptResponseItem" + }, + "@context": { + "type": "string" + }, + "@version": { + "type": "string" + }, + "result": { + "type": "string", + "const": "Accepted" + }, + "attributeId": { + "type": "string" + } + }, + "required": [ + "@type", + "attributeId", + "result" + ], + "additionalProperties": false + }, + "ProposeAttributeAcceptResponseItemJSON": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "const": "ProposeAttributeAcceptResponseItem" + }, + "@context": { + "type": "string" + }, + "@version": { + "type": "string" + }, + "result": { + "type": "string", + "const": "Accepted" + }, + "attributeId": { + "type": "string" + }, + "attribute": { + "anyOf": [ + { + "$ref": "#/definitions/IdentityAttributeJSON" + }, + { + "$ref": "#/definitions/RelationshipAttributeJSON" + } + ] + } + }, + "required": [ + "@type", + "attribute", + "attributeId", + "result" + ], + "additionalProperties": false + }, "ReadAttributeAcceptResponseItemJSON": { "type": "object", "properties": { @@ -7871,6 +8072,9 @@ export const CreateOutgoingRequestRequest: any = { { "$ref": "#/definitions/CreateAttributeRequestItemJSON" }, + { + "$ref": "#/definitions/DeleteAttributeRequestItemJSON" + }, { "$ref": "#/definitions/ShareAttributeRequestItemJSON" }, @@ -9894,6 +10098,50 @@ export const CreateOutgoingRequestRequest: any = { ], "additionalProperties": false }, + "DeleteAttributeRequestItemJSON": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "const": "DeleteAttributeRequestItem" + }, + "@context": { + "type": "string" + }, + "@version": { + "type": "string" + }, + "title": { + "type": "string", + "description": "The human-readable title of this item." + }, + "description": { + "type": "string", + "description": "The human-readable description of this item." + }, + "metadata": { + "type": "object", + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + }, + "mustBeAccepted": { + "type": "boolean", + "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + }, + "requireManualDecision": { + "type": "boolean", + "description": "If set to `true`, it advices the recipient of this RequestItem to carefully consider their decision and especially do not decide based on some automation rules." + }, + "attributeId": { + "type": "string" + } + }, + "required": [ + "@type", + "attributeId", + "mustBeAccepted" + ], + "additionalProperties": false + }, "ShareAttributeRequestItemJSON": { "type": "object", "properties": { @@ -11134,6 +11382,9 @@ export const ReceivedIncomingRequestRequest: any = { { "$ref": "#/definitions/CreateAttributeRequestItemJSON" }, + { + "$ref": "#/definitions/DeleteAttributeRequestItemJSON" + }, { "$ref": "#/definitions/ShareAttributeRequestItemJSON" }, @@ -13157,6 +13408,50 @@ export const ReceivedIncomingRequestRequest: any = { ], "additionalProperties": false }, + "DeleteAttributeRequestItemJSON": { + "type": "object", + "properties": { + "@type": { + "type": "string", + "const": "DeleteAttributeRequestItem" + }, + "@context": { + "type": "string" + }, + "@version": { + "type": "string" + }, + "title": { + "type": "string", + "description": "The human-readable title of this item." + }, + "description": { + "type": "string", + "description": "The human-readable description of this item." + }, + "metadata": { + "type": "object", + "description": "This property can be used to add some arbitrary metadata to this item. The content of this property will be copied into the response on the side of the recipient, so the sender can use it to identify the group content as they receive the response." + }, + "mustBeAccepted": { + "type": "boolean", + "description": "If set to `true`, the recipient has to accept this group if he wants to accept the Request. If set to `false`, the recipient can decide whether he wants to accept it or not.\n\nCaution: this setting does not take effect in case it is inside of a {@link RequestItemGroupJSON RequestItemGroup } , which is not accepted by the recipient, since a {@link RequestItemJSON RequestItem } can only be accepted if the parent group is accepted as well." + }, + "requireManualDecision": { + "type": "boolean", + "description": "If set to `true`, it advices the recipient of this RequestItem to carefully consider their decision and especially do not decide based on some automation rules." + }, + "attributeId": { + "type": "string" + } + }, + "required": [ + "@type", + "attributeId", + "mustBeAccepted" + ], + "additionalProperties": false + }, "ShareAttributeRequestItemJSON": { "type": "object", "properties": { @@ -21012,7 +21307,6 @@ export const UploadOwnFileRequest: any = { "content", "filename", "mimetype", - "expiresAt", "title" ], "additionalProperties": false @@ -21053,7 +21347,6 @@ export const UploadOwnFileValidatableRequest: any = { }, "required": [ "content", - "expiresAt", "filename", "mimetype", "title" diff --git a/packages/runtime/src/useCases/transport/files/UploadOwnFile.ts b/packages/runtime/src/useCases/transport/files/UploadOwnFile.ts index 400f801df..e0f661370 100644 --- a/packages/runtime/src/useCases/transport/files/UploadOwnFile.ts +++ b/packages/runtime/src/useCases/transport/files/UploadOwnFile.ts @@ -1,7 +1,6 @@ import { Result } from "@js-soft/ts-utils"; import { CoreBuffer } from "@nmshd/crypto"; import { AccountController, CoreDate, FileController } from "@nmshd/transport"; -import { DateTime } from "luxon"; import { nameof } from "ts-simple-nameof"; import { Inject } from "typescript-ioc"; import { FileDTO } from "../../../types"; @@ -12,7 +11,7 @@ export interface UploadOwnFileRequest { content: Uint8Array; filename: string; mimetype: string; - expiresAt: ISO8601DateTimeString; + expiresAt?: ISO8601DateTimeString; title: string; description?: string; } @@ -53,7 +52,7 @@ class Validator extends SchemaValidator { ); } - if (DateTime.fromISO(input.expiresAt) <= DateTime.utc()) { + if (input.expiresAt && CoreDate.from(input.expiresAt).isSameOrBefore(CoreDate.utc())) { validationResult.addFailure( new ValidationFailure( RuntimeErrors.general.invalidPropertyValue(`'${nameof((r) => r.expiresAt)}' must be in the future`), @@ -77,13 +76,16 @@ export class UploadOwnFileUseCase extends UseCase } protected async executeInternal(request: UploadOwnFileRequest): Promise> { + const maxDate = "9999-12-31T00:00:00.000Z"; + const expiresAt = request.expiresAt ?? maxDate; + const file = await this.fileController.sendFile({ buffer: CoreBuffer.from(request.content), title: request.title, description: request.description ?? "", filename: request.filename, mimetype: request.mimetype, - expiresAt: CoreDate.from(request.expiresAt) + expiresAt: CoreDate.from(expiresAt) }); await this.accountController.syncDatawallet(); diff --git a/packages/runtime/test/transport/files.test.ts b/packages/runtime/test/transport/files.test.ts index 35315d7e2..aa4742a8e 100644 --- a/packages/runtime/test/transport/files.test.ts +++ b/packages/runtime/test/transport/files.test.ts @@ -1,3 +1,4 @@ +import { CoreDate } from "@nmshd/transport"; import fs from "fs"; import { DateTime } from "luxon"; import { FileDTO, GetFilesQuery, OwnerRestriction, TransportServices } from "../../src"; @@ -95,6 +96,15 @@ describe("File upload", () => { expect(response).toBeSuccessful(); }); + test("uploading a file without expiry date will use the default", async () => { + const response = await transportServices1.files.uploadOwnFile(await makeUploadRequest({ expiresAt: undefined as unknown as string })); + expect(response).toBeSuccessful(); + + const file = response.value; + const defaultDate = CoreDate.from("9999-12-31T00:00:00.000Z"); + expect(CoreDate.from(file.expiresAt).isSame(defaultDate)).toBe(true); + }); + test("cannot upload a file with expiry date in the past", async () => { const response = await transportServices1.files.uploadOwnFile(await makeUploadRequest({ expiresAt: "1970" })); expect(response).toBeAnError("'expiresAt' must be in the future", "error.runtime.validation.invalidPropertyValue"); @@ -104,11 +114,6 @@ describe("File upload", () => { const response = await transportServices1.files.uploadOwnFile(await makeUploadRequest({ expiresAt: "" })); expect(response).toBeAnError("expiresAt must match ISO8601 datetime format", "error.runtime.validation.invalidPropertyValue"); }); - - test("cannot upload a file with undefined as expiry date", async () => { - const response = await transportServices1.files.uploadOwnFile(await makeUploadRequest({ expiresAt: undefined as unknown as string })); - expect(response).toBeAnError("must have required property 'expiresAt'", "error.runtime.validation.invalidPropertyValue"); - }); }); describe("Get file", () => {