Skip to content

Add public key schema to prisma #785

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
Warnings:

- You are about to drop the column `publicKey` on the `BpiSubject` table. All the data in the column will be lost.

*/
-- AlterTable
ALTER TABLE "BpiSubject" DROP COLUMN "publicKey";

-- CreateTable
CREATE TABLE "PublicKey" (
"id" TEXT NOT NULL,
"type" TEXT NOT NULL,
"value" TEXT NOT NULL,
"bpiSubjectId" TEXT NOT NULL,

CONSTRAINT "PublicKey_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "PublicKey_value_key" ON "PublicKey"("value");

-- CreateIndex
CREATE UNIQUE INDEX "PublicKey_type_bpiSubjectId_key" ON "PublicKey"("type", "bpiSubjectId");

-- AddForeignKey
ALTER TABLE "PublicKey" ADD CONSTRAINT "PublicKey_bpiSubjectId_fkey" FOREIGN KEY ("bpiSubjectId") REFERENCES "BpiSubject"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
55 changes: 37 additions & 18 deletions examples/bri-3/prisma/prisma.mapper.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,56 @@
import { Injectable } from '@nestjs/common';
import { BpiSubject as BpiSubjectPrismaModel, BpiSubjectRole as BpiSubjectRolePrismaModel } from '@prisma/client';
import {
BpiSubject as BpiSubjectPrismaModel,
BpiSubjectRole as BpiSubjectRolePrismaModel,
PublicKey as PublicKeyPrismaModel,
} from '@prisma/client';
import { BpiSubject } from '../src/bri/identity/bpiSubjects/models/bpiSubject';
import { BpiSubjectRole } from '../src/bri/identity/bpiSubjects/models/bpiSubjectRole';
import { PublicKey } from '../src/bri/identity/bpiSubjects/models/publicKey';

// We do mapping from prisma models to domain objects using simple Object.assign
// since automapper is not happy working with types, which is how Prisma generates database entities.
// For these mappings to work, the convention is that the domain objects have the same properties as the
// prisma models. In case there is a need to do something custom during mapping, it can be implemented
// in the appropriate method below.
// prisma models. In case there is a need to do something custom during mapping, it can be implemented
// in the appropriate method below.

interface IConstructor<T> {
new (...args: any[]): T;
new (...args: any[]): T;
}

@Injectable()
export class PrismaMapper {
public mapBpiSubjectPrismaModelToDomainObject(source: BpiSubjectPrismaModel): BpiSubject {
const target = this.activator(BpiSubject);
public mapBpiSubjectPrismaModelToDomainObject(
source: BpiSubjectPrismaModel,
): BpiSubject {
const target = this.activator(BpiSubject);

Object.assign(target, source);
Object.assign(target, source);

return target;
}
return target;
}

public mapBpiSubjectRolePrismaModelToDomainObject(source: BpiSubjectRolePrismaModel): BpiSubjectRole {
const target = this.activator(BpiSubjectRole);
public mapBpiSubjectRolePrismaModelToDomainObject(
source: BpiSubjectRolePrismaModel,
): BpiSubjectRole {
const target = this.activator(BpiSubjectRole);

Object.assign(target, source);
Object.assign(target, source);

return target;
}
return target;
}

private activator<T>(type: IConstructor<T>): T {
return new type();
}
}
public mapPublicKeyPrismaModelToDomainObject(
source: PublicKeyPrismaModel,
): PublicKey {
const target = this.activator(PublicKey);

Object.assign(target, source);

return target;
}

private activator<T>(type: IConstructor<T>): T {
return new type();
}
}
11 changes: 10 additions & 1 deletion examples/bri-3/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ model BpiSubject {
id String @id @default(uuid())
name String
description String
publicKey String
publicKey PublicKey[]
loginNonce String @default("")
ownedBpiSubjectAccounts BpiSubjectAccount[] @relation(name: "ownerBpiSubject_fk")
createdBpiSubjectAccounts BpiSubjectAccount[] @relation(name: "creatorBpiSubject_fk")
Expand Down Expand Up @@ -151,6 +151,15 @@ model BpiAccountStateTreeLeafValue {
witness String
}

model PublicKey {
id String @id @default(uuid())
type String
value String @unique
bpiSubjectId String
bpiSubject BpiSubject @relation(fields: [bpiSubjectId], references: [id])
@@unique([type, bpiSubjectId])
}

enum WorkgroupStatus {
ACTIVE
ARCHIVED
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { PrismaService } from '../../../../shared/prisma/prisma.service';
import { NOT_FOUND_ERR_MESSAGE } from '../api/err.messages';
import { BpiSubject } from '../models/bpiSubject';
import { BpiSubjectRole, BpiSubjectRoleName } from '../models/bpiSubjectRole';
import { PublicKey } from '../models/publicKey';

// Repositories are the only places that talk the Prisma language of models.
// They are always mapped to and from domain objects so that the business layer of the application
Expand Down Expand Up @@ -70,8 +71,37 @@ export class BpiSubjectStorageAgent extends PrismaService {
);
}

async storePublicKey(
id: string,
type: string,
value: string,
bpiSubjectId: string,
): Promise<void> {
await this.prisma.publicKey.create({
data: {
id: id,
type: type,
value: value,
bpiSubjectId: bpiSubjectId,
},
});
}

async updatePublicKey(
type: string,
value: string,
bpiSubjectId: string,
): Promise<PublicKey> {
const updatedPublicKey = await this.prisma.publicKey.update({
where: { type_bpiSubjectId: { type: type, bpiSubjectId: bpiSubjectId } },
data: {
value: value,
},
});

return this.mapper.mapPublicKeyPrismaModelToDomainObject(updatedPublicKey);
}
async storeNewBpiSubject(bpiSubject: BpiSubject): Promise<BpiSubject> {
bpiSubject.publicKey = bpiSubject.publicKey.toLowerCase();
const newBpiSubjectModel = await this.prisma.bpiSubject.create({
data: {
...bpiSubject,
Expand All @@ -82,6 +112,13 @@ export class BpiSubjectStorageAgent extends PrismaService {
};
}),
},
publicKey: {
connect: bpiSubject.publicKey.map((pk) => {
return {
id: pk.id,
};
}),
},
},
});

Expand All @@ -102,6 +139,13 @@ export class BpiSubjectStorageAgent extends PrismaService {
};
}),
},
publicKey: {
connect: bpiSubject.publicKey.map((pk) => {
return {
id: pk.id,
};
}),
},
},
});
return this.mapper.mapBpiSubjectPrismaModelToDomainObject(
Expand All @@ -116,17 +160,21 @@ export class BpiSubjectStorageAgent extends PrismaService {
}

async getBpiSubjectByPublicKey(publicKey: string): Promise<BpiSubject> {
const bpiSubjectKey = await this.prisma.publicKey.findUnique({
where: { value: publicKey },
});

if (!bpiSubjectKey) {
throw new NotFoundException(NOT_FOUND_ERR_MESSAGE);
}
const bpiSubjectModel = await this.prisma.bpiSubject.findFirst({
where: {
publicKey: publicKey,
},
include: {
roles: true,
},
where: { id: bpiSubjectKey.id },
});

if (!bpiSubjectModel) {
throw new NotFoundException(NOT_FOUND_ERR_MESSAGE);
}

return this.mapper.mapBpiSubjectPrismaModelToDomainObject(bpiSubjectModel);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AutoMap } from '@automapper/classes';
import { v4 } from 'uuid';
import { BpiSubjectRole } from './bpiSubjectRole';
import { PublicKey } from './publicKey';

export class BpiSubject {
@AutoMap()
Expand All @@ -13,7 +14,7 @@ export class BpiSubject {
description: string;

@AutoMap()
publicKey: string;
publicKey: PublicKey[];

@AutoMap()
loginNonce: string;
Expand All @@ -24,7 +25,7 @@ export class BpiSubject {
id: string,
name: string,
description: string,
publicKey: string,
publicKey: PublicKey[],
roles: BpiSubjectRole[],
) {
this.id = id;
Expand All @@ -42,15 +43,18 @@ export class BpiSubject {
this.description = newDescription;
}

public updatePublicKey(newPk: string): void {
this.publicKey = newPk;
public updatePublicKey(newPk: PublicKey): void {
this.publicKey.map((key) => (key.type == newPk.type ? (key = newPk) : key));
}

public updateLoginNonce(): void {
this.loginNonce = v4();
}

public getBpiSubjectDid(): string {
return `did:ethr:0x5:${this.publicKey}`;
const ecdsaPublicKey = this.publicKey.filter(
(key) => key.type == 'ecdsa',
)[0];
return `did:ethr:0x5:${ecdsaPublicKey.value}`;
}
}
38 changes: 38 additions & 0 deletions examples/bri-3/src/bri/identity/bpiSubjects/models/publicKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { AutoMap } from '@automapper/classes';
import { BpiSubject } from './bpiSubject';

export class PublicKey {
@AutoMap()
id: string;

@AutoMap()
type: string;

@AutoMap()
value: string;

@AutoMap()
bpiSubjectId: string;

@AutoMap()
bpiSubject: BpiSubject;

constructor(id: string, type: string, value: string, bpiSubjectId: string) {
this.id = id;
this.type = type;
this.value = value;
this.bpiSubjectId = bpiSubjectId;
}

public updateType(newType: string): void {
this.type = newType;
}

public updateValue(newValue: string): void {
this.value = newValue;
}

public updateBpiSubjectId(newBpiSubjectId: string): void {
this.bpiSubjectId = newBpiSubjectId;
}
}