Skip to content

Commit

Permalink
Personalized RelationshipTemplates (#270)
Browse files Browse the repository at this point in the history
* feat: add personalized templates

* fix: template controller

* chore: remove reference changes

* test: improve tests

* chore: bump backbone

* fix: some fixes

* refactor/feat: integrate changed reference

* fix: remove transport properly

* chore: review comments

* feat: personalization inheritance

* feat: take value instead of validating

* Revert "feat: take value instead of validating"

This reverts commit 2299b55.

* refactor: test naming

* fix: tokenController test

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
Magnus-Kuhn and mergify[bot] authored Oct 11, 2024
1 parent eaf4c1f commit 2da60aa
Show file tree
Hide file tree
Showing 23 changed files with 287 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface PeerRelationshipTemplateDVO extends DataViewObject {
createdAt: string;
expiresAt?: string;
maxNumberOfAllocations?: number;
forIdentity?: string;

/**
* Is optional, as there can be RelationshipTemplates without actual requests in it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface RelationshipTemplateDVO extends DataViewObject {
createdAt: string;
expiresAt?: string;
maxNumberOfAllocations?: number;
forIdentity?: string;

/**
* Is optional, as there can be RelationshipTemplates without actual requests in it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface RelationshipTemplateDTO {
createdBy: string;
createdByDevice: string;
createdAt: string;
forIdentity?: string;
content: RelationshipTemplateContentDerivation;
expiresAt?: string;
maxNumberOfAllocations?: number;
Expand Down
7 changes: 7 additions & 0 deletions packages/runtime/src/useCases/common/RuntimeErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ class Files {
}

class RelationshipTemplates {
public personalizationMustBeInherited(): ApplicationError {
return new ApplicationError(
"error.runtime.relationshipTemplates.personalizationMustBeInherited",
"If a RelationshipTemplate is personalized, Tokens created from it must have the same personalization."
);
}

public cannotCreateTokenForPeerTemplate(): ApplicationError {
return new ApplicationError("error.runtime.relationshipTemplates.cannotCreateTokenForPeerTemplate", "You cannot create a Token for a peer RelationshipTemplate.");
}
Expand Down
7 changes: 7 additions & 0 deletions packages/runtime/src/useCases/common/Schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21960,6 +21960,9 @@ export const CreateOwnRelationshipTemplateRequest: any = {
"maxNumberOfAllocations": {
"type": "number",
"minimum": 1
},
"forIdentity": {
"$ref": "#/definitions/AddressString"
}
},
"required": [
Expand All @@ -21972,6 +21975,10 @@ export const CreateOwnRelationshipTemplateRequest: any = {
"type": "string",
"errorMessage": "must match ISO8601 datetime format",
"pattern": "^([+-]?\\d{4}(?!\\d{2}\\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)[0-5]\\d)?|24:?00)([.,]\\d+(?!:))?)?(\\17[0-5]\\d([.,]\\d+)?)?([zZ]|([+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)?$"
},
"AddressString": {
"type": "string",
"pattern": "did:e:[a-zA-Z0-9.-]+:dids:[0-9a-f]{22}"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export class LoadItemFromTruncatedReferenceUseCase extends UseCase<LoadItemFromT
const tokenContent = token.cache.content;

if (tokenContent instanceof TokenContentRelationshipTemplate) {
const template = await this.templateController.loadPeerRelationshipTemplate(tokenContent.templateId, tokenContent.secretKey);
const template = await this.templateController.loadPeerRelationshipTemplate(tokenContent.templateId, tokenContent.secretKey, tokenContent.forIdentity?.toString());
return Result.ok({
type: "RelationshipTemplate",
value: RelationshipTemplateMapper.toRelationshipTemplateDTO(template)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { Serializable } from "@js-soft/ts-serval";
import { Result } from "@js-soft/ts-utils";
import { OutgoingRequestsController } from "@nmshd/consumption";
import { ArbitraryRelationshipTemplateContent, RelationshipTemplateContent } from "@nmshd/content";
import { CoreDate } from "@nmshd/core-types";
import { CoreAddress, CoreDate } from "@nmshd/core-types";
import { AccountController, RelationshipTemplateController } from "@nmshd/transport";
import { DateTime } from "luxon";
import { nameof } from "ts-simple-nameof";
import { Inject } from "typescript-ioc";
import { RelationshipTemplateDTO } from "../../../types";
import { ISO8601DateTimeString, RuntimeErrors, SchemaRepository, SchemaValidator, UseCase, ValidationFailure, ValidationResult } from "../../common";
import { AddressString, ISO8601DateTimeString, RuntimeErrors, SchemaRepository, SchemaValidator, UseCase, ValidationFailure, ValidationResult } from "../../common";
import { RelationshipTemplateMapper } from "./RelationshipTemplateMapper";

export interface CreateOwnRelationshipTemplateRequest {
Expand All @@ -18,6 +18,7 @@ export interface CreateOwnRelationshipTemplateRequest {
* @minimum 1
*/
maxNumberOfAllocations?: number;
forIdentity?: AddressString;
}

class Validator extends SchemaValidator<CreateOwnRelationshipTemplateRequest> {
Expand Down Expand Up @@ -59,7 +60,8 @@ export class CreateOwnRelationshipTemplateUseCase extends UseCase<CreateOwnRelat
const relationshipTemplate = await this.templateController.sendRelationshipTemplate({
content: request.content,
expiresAt: CoreDate.from(request.expiresAt),
maxNumberOfAllocations: request.maxNumberOfAllocations
maxNumberOfAllocations: request.maxNumberOfAllocations,
forIdentity: request.forIdentity ? CoreAddress.from(request.forIdentity) : undefined
});

await this.accountController.syncDatawallet();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,14 @@ export class CreateTokenForOwnTemplateUseCase extends UseCase<CreateTokenForOwnT
return Result.fail(RuntimeErrors.relationshipTemplates.cannotCreateTokenForPeerTemplate());
}

if (template.cache?.forIdentity && request.forIdentity !== template.cache.forIdentity.toString()) {
return Result.fail(RuntimeErrors.relationshipTemplates.personalizationMustBeInherited());
}

const tokenContent = TokenContentRelationshipTemplate.from({
templateId: template.id,
secretKey: template.secretKey
secretKey: template.secretKey,
forIdentity: template.cache?.forIdentity
});

const ephemeral = request.ephemeral ?? true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,14 @@ export class CreateTokenQRCodeForOwnTemplateUseCase extends UseCase<CreateTokenQ
return Result.fail(RuntimeErrors.relationshipTemplates.cannotCreateTokenForPeerTemplate());
}

if (template.cache?.forIdentity && request.forIdentity !== template.cache.forIdentity.toString()) {
return Result.fail(RuntimeErrors.relationshipTemplates.personalizationMustBeInherited());
}

const tokenContent = TokenContentRelationshipTemplate.from({
templateId: template.id,
secretKey: template.secretKey
secretKey: template.secretKey,
forIdentity: template.cache!.forIdentity
});

const defaultTokenExpiry = template.cache?.expiresAt ?? CoreDate.utc().add({ days: 12 });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { Result } from "@js-soft/ts-utils";
import { CoreId } from "@nmshd/core-types";
import { CryptoSecretKey } from "@nmshd/crypto";
import { AccountController, RelationshipTemplateController, Token, TokenContentRelationshipTemplate, TokenController } from "@nmshd/transport";
import { Inject } from "typescript-ioc";
import { RelationshipTemplateDTO } from "../../../types";
Expand Down Expand Up @@ -67,11 +65,7 @@ export class LoadPeerRelationshipTemplateUseCase extends UseCase<LoadPeerRelatio
}

const content = token.cache.content;
return await this.loadTemplate(content.templateId, content.secretKey);
}

private async loadTemplate(id: CoreId, key: CryptoSecretKey) {
const template = await this.templateController.loadPeerRelationshipTemplate(id, key);
const template = await this.templateController.loadPeerRelationshipTemplate(content.templateId, content.secretKey, content.forIdentity?.toString());
return Result.ok(RelationshipTemplateMapper.toRelationshipTemplateDTO(template));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class RelationshipTemplateMapper {
createdBy: template.cache.createdBy.toString(),
createdByDevice: template.cache.createdByDevice.toString(),
createdAt: template.cache.createdAt.toString(),
forIdentity: template.cache.forIdentity?.toString(),
content: this.toTemplateContent(template.cache.content),
expiresAt: template.cache.expiresAt?.toString(),
maxNumberOfAllocations: template.cache.maxNumberOfAllocations,
Expand Down
14 changes: 12 additions & 2 deletions packages/runtime/test/dataViews/RelationshipTemplateDVO.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
Surname
} from "@nmshd/content";
import { CoreAddress } from "@nmshd/core-types";
import { DateTime } from "luxon";
import {
IncomingRequestStatusChangedEvent,
LocalRequestStatus,
Expand All @@ -22,7 +23,7 @@ import {
RelationshipTemplateDTO,
RequestItemGroupDVO
} from "../../src";
import { RuntimeServiceProvider, TestRuntimeServices, createTemplate, syncUntilHasRelationships } from "../lib";
import { RuntimeServiceProvider, TestRuntimeServices, syncUntilHasRelationships } from "../lib";

const serviceProvider = new RuntimeServiceProvider();
let templator: TestRuntimeServices;
Expand Down Expand Up @@ -142,7 +143,14 @@ describe("RelationshipTemplateDVO", () => {
]
}
];
templatorTemplate = (await createTemplate(templator.transport, templateContent)) as RelationshipTemplateDTO & { content: RelationshipTemplateContentJSON };
templatorTemplate = (
await templator.transport.relationshipTemplates.createOwnRelationshipTemplate({
maxNumberOfAllocations: 1,
expiresAt: DateTime.utc().plus({ minutes: 10 }).toString(),
content: templateContent,
forIdentity: requestor.address
})
).value as RelationshipTemplateDTO & { content: RelationshipTemplateContentJSON };
templateId = templatorTemplate.id;
});

Expand All @@ -159,6 +167,7 @@ describe("RelationshipTemplateDVO", () => {
expect(dvo.name).toStrictEqual(dto.content.title ? dto.content.title : "i18n://dvo.template.outgoing.name");
expect(dvo.isOwn).toBe(true);
expect(dvo.maxNumberOfAllocations).toBe(1);
expect(dvo.forIdentity).toBe(requestor.address);

expect(dvo.onNewRelationship!.type).toBe("RequestDVO");
expect(dvo.onNewRelationship!.items).toHaveLength(2);
Expand Down Expand Up @@ -193,6 +202,7 @@ describe("RelationshipTemplateDVO", () => {
expect(dvo.name).toStrictEqual(dto.content.title ? dto.content.title : "i18n://dvo.template.incoming.name");
expect(dvo.isOwn).toBe(false);
expect(dvo.maxNumberOfAllocations).toBe(1);
expect(dvo.forIdentity).toBe(requestor.address);

expect(dvo.onNewRelationship!.type).toBe("RequestDVO");
expect(dvo.onNewRelationship!.items).toHaveLength(2);
Expand Down
Loading

0 comments on commit 2da60aa

Please sign in to comment.