From 203d050d48bb12709ba6fe6ae5c30b5931ccb5fb Mon Sep 17 00:00:00 2001 From: "A.G.J. Cate" Date: Tue, 1 Aug 2023 13:18:06 +0200 Subject: [PATCH] DPP-1 WIP --- .../__tests__/localAgent.test.ts | 6 +- .../__tests__/restAgent.test.ts | 14 +- .../shared/contactManagerAgentLogic.ts | 309 ++++++++++++++---- packages/contact-manager/agent.yml | 18 +- .../src/agent/ContactManager.ts | 82 ++++- .../src/types/IContactManager.ts | 75 ++++- .../src/__tests__/contact.entities.test.ts | 22 +- .../src/__tests__/contact.store.test.ts | 18 +- .../src/contact/AbstractContactStore.ts | 31 +- .../data-store/src/contact/ContactStore.ts | 197 +++++++++-- .../src/entities/contact/BaseConfigEntity.ts | 7 +- .../src/entities/contact/ConnectionEntity.ts | 2 +- .../src/entities/contact/ContactEntity.ts | 5 +- .../entities/contact/ContactOwnerEntity.ts | 61 ---- .../contact/ContactRelationshipEntity.ts | 19 +- .../entities/contact/OrganizationEntity.ts | 1 + .../src/entities/contact/PersonEntity.ts | 2 - packages/data-store/src/index.ts | 12 +- .../types/contact/IAbstractContactStore.ts | 63 +++- .../data-store/src/types/contact/contact.ts | 4 +- .../__tests__/localAgent.test.ts | 2 +- .../__tests__/restAgent.test.ts | 2 +- 22 files changed, 706 insertions(+), 246 deletions(-) diff --git a/packages/contact-manager/__tests__/localAgent.test.ts b/packages/contact-manager/__tests__/localAgent.test.ts index 640ca6d7e..87cdfd75a 100644 --- a/packages/contact-manager/__tests__/localAgent.test.ts +++ b/packages/contact-manager/__tests__/localAgent.test.ts @@ -1,11 +1,11 @@ import { createObjects, getConfig } from '../../agent-config/dist' -import { Connection } from 'typeorm' +import { DataSource } from 'typeorm' jest.setTimeout(30000) import contactManagerAgentLogic from './shared/contactManagerAgentLogic' -let dbConnection: Promise +let dbConnection: Promise let agent: any const setup = async (): Promise => { @@ -29,6 +29,6 @@ const testContext = { tearDown, } -describe('Local integration tests', () => { +describe('Local integration tests', (): void => { contactManagerAgentLogic(testContext) }) diff --git a/packages/contact-manager/__tests__/restAgent.test.ts b/packages/contact-manager/__tests__/restAgent.test.ts index f58f177a3..2e9044520 100644 --- a/packages/contact-manager/__tests__/restAgent.test.ts +++ b/packages/contact-manager/__tests__/restAgent.test.ts @@ -1,8 +1,8 @@ import 'cross-fetch/polyfill' // @ts-ignore -import express from 'express' +import express, { Router } from 'express' import { Server } from 'http' -import { Connection } from 'typeorm' +import { DataSource } from 'typeorm' import { IAgent, createAgent, IAgentOptions } from '@veramo/core' import { AgentRestClient } from '@veramo/remote-client' import { AgentRouter, RequestWithAgentRouter } from '@veramo/remote-server' @@ -17,7 +17,7 @@ const basePath = '/agent' let serverAgent: IAgent let restServer: Server -let dbConnection: Promise +let dbConnection: Promise const getAgent = (options?: IAgentOptions) => createAgent({ @@ -41,14 +41,14 @@ const setup = async (): Promise => { exposedMethods: serverAgent.availableMethods(), }) - const requestWithAgent = RequestWithAgentRouter({ + const requestWithAgent: Router = RequestWithAgentRouter({ agent: serverAgent, }) - return new Promise((resolve) => { + return new Promise((resolve): void => { const app = express() app.use(basePath, requestWithAgent, agentRouter) - restServer = app.listen(port, () => { + restServer = app.listen(port, (): void => { resolve(true) }) }) @@ -66,6 +66,6 @@ const testContext = { tearDown, } -describe('REST integration tests', () => { +describe('REST integration tests', (): void => { contactManagerAgentLogic(testContext) }) diff --git a/packages/contact-manager/__tests__/shared/contactManagerAgentLogic.ts b/packages/contact-manager/__tests__/shared/contactManagerAgentLogic.ts index 9a0a90085..cf45d0c6a 100644 --- a/packages/contact-manager/__tests__/shared/contactManagerAgentLogic.ts +++ b/packages/contact-manager/__tests__/shared/contactManagerAgentLogic.ts @@ -1,11 +1,23 @@ import { TAgent } from '@veramo/core' import { IContactManager } from '../../src' -import { CorrelationIdentifierEnum, IContact, IdentityRoleEnum, IIdentity } from '../../../data-store/src' +import { + BasicPerson, + ContactTypeEnum, + CorrelationIdentifierEnum, + IBasicContact, + IBasicIdentity, + IContact, + IdentityRoleEnum, + IIdentity, + IPerson, + IGetContactsArgs, +} from '../../../data-store/src' +import { IContactRelationship } from '../../../data-store' type ConfiguredAgent = TAgent export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Promise; tearDown: () => Promise }): void => { - describe('Contact Manager Agent Plugin', () => { + describe('Contact Manager Agent Plugin', (): void => { let agent: ConfiguredAgent let defaultContact: IContact let defaultIdentity: IIdentity @@ -14,13 +26,22 @@ export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Pro await testContext.setup() agent = testContext.getAgent() - const contact = { - name: 'default_contact', - alias: 'default_contact_alias', + const contact: IBasicContact = { + contactType: { + type: ContactTypeEnum.PERSON, + tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289', + name: 'example_name', + }, + contactOwner: { + firstName: 'default_first_name', + middleName: 'default_middle_name', + lastName: 'default_last_name', + displayName: 'default_display_name', + }, uri: 'example.com', } const correlationId = 'default_example_did' - const identity = { + const identity: IBasicIdentity = { alias: correlationId, roles: [IdentityRoleEnum.ISSUER, IdentityRoleEnum.VERIFIER], identifier: { @@ -34,8 +55,8 @@ export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Pro afterAll(testContext.tearDown) - it('should get contact by id', async () => { - const result = await agent.cmGetContact({ contactId: defaultContact.id }) + it('should get contact by id', async (): Promise => { + const result: IContact = await agent.cmGetContact({ contactId: defaultContact.id }) expect(result.id).toEqual(defaultContact.id) }) @@ -46,119 +67,152 @@ export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Pro await expect(agent.cmGetContact({ contactId })).rejects.toThrow(`No contact found for id: ${contactId}`) }) - it('should get all contacts', async () => { - const result = await agent.cmGetContacts() + it('should get all contacts', async (): Promise => { + const result: Array = await agent.cmGetContacts() expect(result.length).toBeGreaterThan(0) }) it('should get contacts by filter', async (): Promise => { - const args = { - filter: [{ name: 'default_contact' }, { alias: 'default_contact_alias' }, { uri: 'example.com' }], + const args: IGetContactsArgs = { + filter: [ + { + contactType: { + type: ContactTypeEnum.PERSON, + }, + }, + { + contactOwner: { + displayName: 'default_display_name', + }, + }, + { uri: 'example.com' }, + ], } - const result = await agent.cmGetContacts(args) + const result: Array = await agent.cmGetContacts(args) expect(result.length).toBe(1) }) it('should get contacts by name', async (): Promise => { - const args = { - filter: [{ name: 'default_contact' }], + const args: IGetContactsArgs = { + filter: [ + { contactOwner: { firstName: 'default_first_name' } }, + { contactOwner: { middleName: 'default_middle_name' } }, + { contactOwner: { lastName: 'default_last_name' } }, + ], } - const result = await agent.cmGetContacts(args) + const result: Array = await agent.cmGetContacts(args) expect(result.length).toBe(1) }) - it('should get contacts by alias', async (): Promise => { - const args = { - filter: [{ alias: 'default_contact_alias' }], + it('should get contacts by display name', async (): Promise => { + const args: IGetContactsArgs = { + filter: [{ contactOwner: { displayName: 'default_display_name' } }], } - const result = await agent.cmGetContacts(args) + const result: Array = await agent.cmGetContacts(args) expect(result.length).toBe(1) }) it('should get contacts by uri', async (): Promise => { - const args = { + const args: IGetContactsArgs = { filter: [{ uri: 'example.com' }], } - const result = await agent.cmGetContacts(args) + const result: Array = await agent.cmGetContacts(args) expect(result.length).toBe(1) }) it('should return no contacts if filter does not match', async (): Promise => { - const args = { - filter: [{ name: 'no_match_contact' }, { alias: 'no_match_contact_alias' }, { uri: 'no_match_example.com' }], + const args: IGetContactsArgs = { + filter: [{ contactOwner: { displayName: 'no_match_contact_display_name' } }, { uri: 'no_match_example.com' }], } - const result = await agent.cmGetContacts(args) + const result: Array = await agent.cmGetContacts(args) expect(result.length).toBe(0) }) it('should add contact', async (): Promise => { - const contact = { - name: 'new_contact', - alias: 'new_contact_alias', + const contact: IBasicContact = { + contactType: { + type: ContactTypeEnum.PERSON, + tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d288', + name: 'new_name', + description: 'new_description', + }, + contactOwner: { + firstName: 'new_first_name', + middleName: 'new_middle_name', + lastName: 'new_last_name', + displayName: 'new_display_name', + }, uri: 'example.com', } - const result = await agent.cmAddContact(contact) + const result: IContact = await agent.cmAddContact(contact) - expect(result.name).toEqual(contact.name) - expect(result.alias).toEqual(contact.alias) + expect(result.contactType.type).toEqual(contact.contactType.type) + expect(result.contactType.name).toEqual(contact.contactType.name) + expect(result.contactType.description).toEqual(contact.contactType.description) + expect((result.contactOwner).firstName).toEqual((contact.contactOwner).firstName) + expect((result.contactOwner).middleName).toEqual((contact.contactOwner).middleName) + expect((result.contactOwner).lastName).toEqual((contact.contactOwner).lastName) + expect((result.contactOwner).displayName).toEqual((contact.contactOwner).displayName) expect(result.uri).toEqual(contact.uri) }) - it('should throw error when adding contact with duplicate name', async (): Promise => { - const name = 'default_contact' - const alias = 'default_contact_new_alias' - const contact = { - name, - alias, - uri: 'example.com', - } - - await expect(agent.cmAddContact(contact)).rejects.toThrow(`Duplicate names or aliases are not allowed. Name: ${name}, Alias: ${alias}`) - }) - - it('should throw error when adding contact with duplicate alias', async (): Promise => { - const name = 'default_new_contact' - const alias = 'default_contact_alias' - const contact = { - name, - alias, + it('should throw error when adding contact with duplicate display name', async (): Promise => { + const displayName = 'default_display_name' + const contact: IBasicContact = { + contactType: { + type: ContactTypeEnum.PERSON, + tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d287', + name: 'example_name2', + }, + contactOwner: { + firstName: 'default_first_name2', + middleName: 'default_middle_name2', + lastName: 'default_last_name2', + displayName, + }, uri: 'example.com', } - await expect(agent.cmAddContact(contact)).rejects.toThrow(`Duplicate names or aliases are not allowed. Name: ${name}, Alias: ${alias}`) + await expect(agent.cmAddContact(contact)).rejects.toThrow(`Duplicate names or display are not allowed. Display name: ${displayName}`) }) it('should update contact by id', async (): Promise => { - const contactName = 'updated_contact' - const contact = { + const contactFirstName = 'updated_contact_first_name' + const contact: IContact = { ...defaultContact, - name: contactName, + contactOwner: { + ...defaultContact.contactOwner, + firstName: contactFirstName, + }, } - const result = await agent.cmUpdateContact({ contact }) + const result: IContact = await agent.cmUpdateContact({ contact }) - expect(result.name).toEqual(contactName) + expect((result.contactOwner).firstName).toEqual(contactFirstName) }) it('should throw error when updating contact with unknown id', async (): Promise => { const contactId = 'unknownContactId' - const contact = { + const contact: IContact = { ...defaultContact, id: contactId, - name: 'new_name', + contactOwner: { + ...defaultContact.contactOwner, + firstName: 'new_first_name', + }, } await expect(agent.cmUpdateContact({ contact })).rejects.toThrow(`No contact found for id: ${contactId}`) }) - it('should get identity by id', async () => { - const result = await agent.cmGetIdentity({ identityId: defaultIdentity.id }) + it('should get identity by id', async (): Promise => { + const result: IIdentity = await agent.cmGetIdentity({ identityId: defaultIdentity.id }) expect(result.id).toEqual(defaultIdentity.id) }) @@ -174,14 +228,14 @@ export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Pro }) it('should get all identities for contact', async (): Promise => { - const result = await agent.cmGetIdentities({ filter: [{ contactId: defaultContact.id }] }) + const result: Array = await agent.cmGetIdentities({ filter: [{ contactId: defaultContact.id }] }) expect(result.length).toBeGreaterThan(0) }) it('should add identity to contact', async (): Promise => { const correlationId = 'new_example_did' - const identity = { + const identity: IBasicIdentity = { alias: correlationId, roles: [IdentityRoleEnum.ISSUER, IdentityRoleEnum.VERIFIER], identifier: { @@ -190,8 +244,8 @@ export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Pro }, } - const result = await agent.cmAddIdentity({ contactId: defaultContact.id, identity }) - const contact = await agent.cmGetContact({ contactId: defaultContact.id }) + const result: IIdentity = await agent.cmAddIdentity({ contactId: defaultContact.id, identity }) + const contact: IContact = await agent.cmGetContact({ contactId: defaultContact.id }) expect(result).not.toBeNull() expect(contact.identities.length).toEqual(2) @@ -205,7 +259,7 @@ export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Pro it('should throw error when adding identity with invalid identifier', async (): Promise => { const correlationId = 'missing_connection_add_example' - const identity = { + const identity: IBasicIdentity = { alias: correlationId, roles: [IdentityRoleEnum.ISSUER, IdentityRoleEnum.VERIFIER], identifier: { @@ -221,7 +275,7 @@ export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Pro it('should throw error when updating identity with invalid identifier', async (): Promise => { const correlationId = 'missing_connection_update_example' - const identity = { + const identity: IBasicIdentity = { alias: correlationId, roles: [IdentityRoleEnum.ISSUER, IdentityRoleEnum.VERIFIER], identifier: { @@ -229,7 +283,7 @@ export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Pro correlationId, }, } - const result = await agent.cmAddIdentity({ contactId: defaultContact.id, identity }) + const result: IIdentity = await agent.cmAddIdentity({ contactId: defaultContact.id, identity }) result.identifier = { ...result.identifier, type: CorrelationIdentifierEnum.URL } await expect(agent.cmUpdateIdentity({ identity: result })).rejects.toThrow(`Identity with correlation type url should contain a connection`) @@ -237,7 +291,7 @@ export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Pro it('should update identity', async (): Promise => { const correlationId = 'new_update_example_did' - const identity = { + const identity: IBasicIdentity = { alias: 'update_example_did', roles: [IdentityRoleEnum.ISSUER, IdentityRoleEnum.VERIFIER], identifier: { @@ -245,14 +299,131 @@ export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Pro correlationId: 'update_example_did', }, } - const result = await agent.cmAddIdentity({ contactId: defaultContact.id, identity }) + const result: IIdentity = await agent.cmAddIdentity({ contactId: defaultContact.id, identity }) result.identifier = { ...result.identifier, correlationId } await agent.cmUpdateIdentity({ identity: result }) - const updatedIdentity = await agent.cmGetIdentity({ identityId: result.id }) + const updatedIdentity: IIdentity = await agent.cmGetIdentity({ identityId: result.id }) expect(updatedIdentity).not.toBeNull() expect(updatedIdentity.identifier.correlationId).toEqual(correlationId) }) + + it('should add relationship', async (): Promise => { + const contact: IBasicContact = { + contactType: { + type: ContactTypeEnum.PERSON, + tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d287', + name: 'relation_contact_type_name', + description: 'new_description', + }, + contactOwner: { + firstName: 'relation_first_name', + middleName: 'relation_middle_name', + lastName: 'relation_last_name', + displayName: 'relation_display_name', + }, + uri: 'example.com', + } + + const savedContact: IContact = await agent.cmAddContact(contact) + + // TODO why does this filter not work on only first name? + const args1: IGetContactsArgs = { + filter: [ + { contactOwner: { firstName: 'default_first_name' } }, + { contactOwner: { middleName: 'default_middle_name' } }, + // { contactOwner: { lastName: 'default_last_name'} }, + ], + } + const otherContacts: Array = await agent.cmGetContacts(args1) + + expect(otherContacts.length).toEqual(1) + + const relationship: IContactRelationship = await agent.cmAddRelationship({ + leftContactId: savedContact.id, + rightContactId: otherContacts[0].id, + }) + + expect(relationship).toBeDefined() + + // TODO why does this filter not work on only first name? + const args2: IGetContactsArgs = { + filter: [ + { contactOwner: { firstName: 'relation_first_name' } }, + { contactOwner: { middleName: 'relation_middle_name' } }, + // { contactOwner: { lastName: 'default_last_name'} }, + ], + } + const result: Array = await agent.cmGetContacts(args2) + + expect(result.length).toEqual(1) + expect(result[0].relationships.length).toEqual(1) + expect(result[0].relationships[0].leftContactId).toEqual(savedContact.id) + expect(result[0].relationships[0].rightContactId).toEqual(otherContacts[0].id) + }) + + it('should remove relationship', async (): Promise => { + const contact: IBasicContact = { + contactType: { + type: ContactTypeEnum.PERSON, + tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d286', + name: 'remove_relation_contact_type_name', + description: 'new_description', + }, + contactOwner: { + firstName: 'remove_relation_first_name', + middleName: 'remove_relation_middle_name', + lastName: 'remove_relation_last_name', + displayName: 'remove_relation_display_name', + }, + uri: 'example.com', + } + + const savedContact: IContact = await agent.cmAddContact(contact) + + // TODO why does this filter not work on only first name? + const args1: IGetContactsArgs = { + filter: [ + { contactOwner: { firstName: 'default_first_name' } }, + { contactOwner: { middleName: 'default_middle_name' } }, + // { contactOwner: { lastName: 'default_last_name'} }, + ], + } + const otherContacts: Array = await agent.cmGetContacts(args1) + + expect(otherContacts.length).toEqual(1) + + const relationship: IContactRelationship = await agent.cmAddRelationship({ + leftContactId: savedContact.id, + rightContactId: otherContacts[0].id, + }) + + expect(relationship).toBeDefined() + + // TODO why does this filter not work on only first name? + const args2: IGetContactsArgs = { + filter: [ + { contactOwner: { firstName: 'relation_first_name' } }, + { contactOwner: { middleName: 'relation_middle_name' } }, + // { contactOwner: { lastName: 'default_last_name'} }, + ], + } + const retrievedContact: Array = await agent.cmGetContacts(args2) + + expect(retrievedContact.length).toEqual(1) + expect(retrievedContact[0].relationships.length).toEqual(1) + // expect(result[0].relationships[0].leftContactId).toEqual(savedContact.id) + // expect(result[0].relationships[0].rightContactId).toEqual(otherContacts[0].id) + + const removeRelationshipResult: boolean = await agent.cmRemoveRelationship({ relationshipId: relationship.id }) + expect(removeRelationshipResult).toBeTruthy() + + const result: IContact = await agent.cmGetContact({ contactId: savedContact.id }) + + expect(result.relationships.length).toEqual(0) + }) + + // remove relation }) } diff --git a/packages/contact-manager/agent.yml b/packages/contact-manager/agent.yml index 3e22f5048..3b4a79505 100644 --- a/packages/contact-manager/agent.yml +++ b/packages/contact-manager/agent.yml @@ -14,16 +14,26 @@ constants: - cmAddIdentity - cmUpdateIdentity - cmRemoveIdentity + - cmGetRelationship + - cmGetRelationships + - cmUpdateRelationship + - cmAddRelationship + - cmRemoveRelationship + - cmGetContactType + - cmGetContactTypes + - cmAddContactType + - cmUpdateContactType + - cmRemoveContactType dbConnection: $require: typeorm?t=function#createConnection $args: - type: sqlite database: ':memory:' - synchronize: false - migrationsRun: true - migrations: - $require: './packages/data-store?t=object#DataStoreMigrations' + synchronize: true #false + migrationsRun: true #true + # migrations: + # $require: './packages/data-store?t=object#DataStoreMigrations' entities: $require: './packages/data-store?t=object#DataStoreContactEntities' diff --git a/packages/contact-manager/src/agent/ContactManager.ts b/packages/contact-manager/src/agent/ContactManager.ts index 47abb9350..6f3211fe1 100644 --- a/packages/contact-manager/src/agent/ContactManager.ts +++ b/packages/contact-manager/src/agent/ContactManager.ts @@ -13,8 +13,24 @@ import { IUpdateIdentityArgs, IGetContactsArgs, IGetContactArgs, + IAddRelationshipArgs, + IRemoveRelationshipArgs, + IGetRelationshipArgs, + IGetRelationshipsArgs, + IUpdateRelationshipArgs, + IAddContactTypeArgs, + IGetContactTypeArgs, + IGetContactTypesArgs, + IRemoveContactTypeArgs, + IUpdateContactTypeArgs, } from '../types/IContactManager' -import { IContact, IIdentity, AbstractContactStore } from '@sphereon/ssi-sdk.data-store' +import { + IContact, + IIdentity, + AbstractContactStore, + IContactRelationship, + IContactType +} from '@sphereon/ssi-sdk.data-store' /** * {@inheritDoc IContactManager} @@ -32,6 +48,16 @@ export class ContactManager implements IAgentPlugin { cmAddIdentity: this.cmAddIdentity.bind(this), cmUpdateIdentity: this.cmUpdateIdentity.bind(this), cmRemoveIdentity: this.cmRemoveIdentity.bind(this), + cmAddRelationship: this.cmAddRelationship.bind(this), + cmRemoveRelationship: this.cmRemoveRelationship.bind(this), + cmGetRelationship: this.cmGetRelationship.bind(this), + cmGetRelationships: this.cmGetRelationships.bind(this), + cmUpdateRelationship: this.cmUpdateRelationship.bind(this), + cmGetContactType: this.cmGetContactType.bind(this), + cmGetContactTypes: this.cmGetContactTypes.bind(this), + cmAddContactType: this.cmAddContactType.bind(this), + cmUpdateContactType: this.cmUpdateContactType.bind(this), + cmRemoveContactType: this.cmRemoveContactType.bind(this) } private readonly store: AbstractContactStore @@ -71,7 +97,7 @@ export class ContactManager implements IAgentPlugin { } /** {@inheritDoc IContactManager.cmGetIdentities} */ - private async cmGetIdentities(args: IGetIdentitiesArgs, context: IRequiredContext): Promise> { + private async cmGetIdentities(args?: IGetIdentitiesArgs): Promise> { return this.store.getIdentities(args) } @@ -87,6 +113,56 @@ export class ContactManager implements IAgentPlugin { /** {@inheritDoc IContactManager.cmRemoveIdentity} */ private async cmRemoveIdentity(args: IRemoveIdentityArgs, context: IRequiredContext): Promise { - return this.store.removeIdentity(args).then(() => true) // TODO + return this.store.removeIdentity(args).then(() => true) + } + + /** {@inheritDoc IContactManager.cmAddRelationship} */ + private async cmAddRelationship(args: IAddRelationshipArgs, context: IRequiredContext): Promise { + return this.store.addRelationship(args) + } + + /** {@inheritDoc IContactManager.cmRemoveRelationship} */ + private async cmRemoveRelationship(args: IRemoveRelationshipArgs, context: IRequiredContext): Promise { + return this.store.removeRelationship(args).then(() => true) + } + + /** {@inheritDoc IContactManager.cmGetRelationship} */ + private async cmGetRelationship(args: IGetRelationshipArgs, context: IRequiredContext): Promise { + return this.store.getRelationship(args) + } + + /** {@inheritDoc IContactManager.cmGetRelationships} */ + private async cmGetRelationships(args?: IGetRelationshipsArgs): Promise> { + return this.store.getRelationships(args) + } + + /** {@inheritDoc IContactManager.cmUpdateRelationship} */ + private async cmUpdateRelationship(args: IUpdateRelationshipArgs, context: IRequiredContext): Promise { + return this.store.updateRelationship(args) + } + + /** {@inheritDoc IContactManager.cmGetContactType} */ + private async cmGetContactType(args: IGetContactTypeArgs, context: IRequiredContext): Promise { + return this.store.getContactType(args) + } + + /** {@inheritDoc IContactManager.cmGetContactTypes} */ + private async cmGetContactTypes(args?: IGetContactTypesArgs): Promise> { + return this.store.getContactTypes(args) + } + + /** {@inheritDoc IContactManager.cmAddContactType} */ + private async cmAddContactType(args: IAddContactTypeArgs, context: IRequiredContext): Promise { + return this.store.addContactType(args) + } + + /** {@inheritDoc IContactManager.cmUpdateContactType} */ + private async cmUpdateContactType(args: IUpdateContactTypeArgs, context: IRequiredContext): Promise { + return this.store.updateContactType(args) + } + + /** {@inheritDoc IContactManager.cmRemoveContactType} */ + private async cmRemoveContactType(args: IRemoveContactTypeArgs, context: IRequiredContext): Promise { + return this.store.removeContactType(args).then(() => true) } } diff --git a/packages/contact-manager/src/types/IContactManager.ts b/packages/contact-manager/src/types/IContactManager.ts index 3a1c1bd3a..0751b3704 100644 --- a/packages/contact-manager/src/types/IContactManager.ts +++ b/packages/contact-manager/src/types/IContactManager.ts @@ -1,5 +1,18 @@ import { IAgentContext, IPluginMethodMap } from '@veramo/core' -import { FindContactArgs, FindIdentityArgs, IBasicIdentity, IContact, IIdentity } from '@sphereon/ssi-sdk.data-store' +import { + BasicContactOwner, + BasicContactType, + FindContactArgs, + FindIdentityArgs, + IBasicIdentity, + IContact, + IContactRelationship, + IIdentity, + FindRelationshipArgs, + ContactTypeEnum, + FindContactTypeArgs, + IContactType +} from '@sphereon/ssi-sdk.data-store' export interface IContactManager extends IPluginMethodMap { cmGetContact(args: IGetContactArgs, context: IRequiredContext): Promise @@ -8,10 +21,20 @@ export interface IContactManager extends IPluginMethodMap { cmUpdateContact(args: IUpdateContactArgs, context: IRequiredContext): Promise cmRemoveContact(args: IRemoveContactArgs, context: IRequiredContext): Promise cmGetIdentity(args: IGetIdentityArgs, context: IRequiredContext): Promise - cmGetIdentities(args: IGetIdentitiesArgs, context: IRequiredContext): Promise> + cmGetIdentities(args?: IGetIdentitiesArgs): Promise> cmAddIdentity(args: IAddIdentityArgs, context: IRequiredContext): Promise cmUpdateIdentity(args: IUpdateIdentityArgs, context: IRequiredContext): Promise cmRemoveIdentity(args: IRemoveIdentityArgs, context: IRequiredContext): Promise + cmGetRelationship(args: IGetRelationshipArgs, context: IRequiredContext): Promise + cmGetRelationships(args?: IGetRelationshipsArgs): Promise> + cmUpdateRelationship(args: IUpdateRelationshipArgs, context: IRequiredContext): Promise + cmAddRelationship(args: IAddRelationshipArgs, context: IRequiredContext): Promise + cmRemoveRelationship(args: IRemoveRelationshipArgs, context: IRequiredContext): Promise + cmGetContactType(args: IGetContactTypeArgs, context: IRequiredContext): Promise + cmGetContactTypes(args?: IGetContactTypesArgs): Promise> + cmAddContactType(args: IAddContactTypeArgs, context: IRequiredContext): Promise + cmUpdateContactType(args: IUpdateContactTypeArgs, context: IRequiredContext): Promise + cmRemoveContactType(args: IRemoveContactTypeArgs, context: IRequiredContext): Promise } export interface IGetContactArgs { @@ -23,9 +46,9 @@ export interface IGetContactsArgs { } export interface IAddContactArgs { - name: string - alias: string uri?: string + contactType: BasicContactType // TODO we can have a situation where we want to add a contact to an existing type, so use BasicContactType | IContactType? also make a test for these 2 situations in the store + contactOwner: BasicContactOwner identities?: Array } @@ -58,4 +81,48 @@ export interface IRemoveIdentityArgs { identityId: string } +export interface IAddRelationshipArgs { + leftContactId: string + rightContactId: string +} + +export interface IRemoveRelationshipArgs { + relationshipId: string +} + +export interface IGetRelationshipArgs { + relationshipId: string +} + +export interface IGetRelationshipsArgs { + filter: FindRelationshipArgs +} + +export interface IUpdateRelationshipArgs { + relationship: IContactRelationship // TODO do we also want the omits here? +} + +export interface IAddContactTypeArgs { + type: ContactTypeEnum + name: string + tenantId: string + description?: string +} + +export interface IGetContactTypeArgs { + contactTypeId: string +} + +export interface IGetContactTypesArgs { + filter?: FindContactTypeArgs +} + +export interface IUpdateContactTypeArgs { + contactType: Omit // TODO do we also want the omits here? +} + +export interface IRemoveContactTypeArgs { + contactTypeId: string +} + export type IRequiredContext = IAgentContext diff --git a/packages/data-store/src/__tests__/contact.entities.test.ts b/packages/data-store/src/__tests__/contact.entities.test.ts index 78604a4eb..6cd262c59 100644 --- a/packages/data-store/src/__tests__/contact.entities.test.ts +++ b/packages/data-store/src/__tests__/contact.entities.test.ts @@ -127,7 +127,7 @@ describe('Database entities tests', (): void => { expect((fromDb?.contactOwner).cocNumber).toEqual((contact.contactOwner).cocNumber) }) - it('Should result in contact relation for the owner side only', async (): Promise => { + it('Should result in contact relationship for the owner side only', async (): Promise => { const contact1: IBasicContact = { uri: 'example1.com', contactType: { @@ -399,8 +399,6 @@ describe('Database entities tests', (): void => { await expect(dbConnection.getRepository(ContactEntity).save(contactEntity)).rejects.toThrow("Blank tenant id's are not allowed") }) - // TODO should wew enforce unique combinations of first/middle/last names - // Example, hans kraai, we have a jr and sr it('Should enforce unique display name for a person contact', async (): Promise => { const contactDisplayName = 'non_unique_name' const contact1: IBasicContact = { @@ -1571,7 +1569,7 @@ describe('Database entities tests', (): void => { const organization1: BasicOrganization = { legalName, displayName: 'example_display_name1', - cocNumber: '1234567890' + cocNumber: '1234567890', } const organizationEntity1: OrganizationEntity = organizationEntityFrom(organization1) @@ -1584,7 +1582,7 @@ describe('Database entities tests', (): void => { const organization2: BasicOrganization = { legalName, displayName: 'example_display_name2', - cocNumber: '1234567890' + cocNumber: '1234567890', } const organizationEntity2: OrganizationEntity = organizationEntityFrom(organization2) @@ -1620,7 +1618,7 @@ describe('Database entities tests', (): void => { ) }) - it('Should save contact relation to database', async (): Promise => { + it('Should save contact relationship to database', async (): Promise => { const contact1: IBasicContact = { uri: 'example1.com', contactType: { @@ -1682,7 +1680,7 @@ describe('Database entities tests', (): void => { expect(fromDb).toBeDefined() }) - it('Should set last updated date when saving contact relation', async (): Promise => { + it('Should set last updated date when saving contact relationship', async (): Promise => { const contact1: IBasicContact = { uri: 'example1.com', contactType: { @@ -1740,7 +1738,7 @@ describe('Database entities tests', (): void => { expect(fromDb?.lastUpdatedAt).toBeDefined() }) - it('Should set creation date when saving contact relation', async (): Promise => { + it('Should set creation date when saving contact relationship', async (): Promise => { const contact1: IBasicContact = { uri: 'example1.com', contactType: { @@ -1798,7 +1796,7 @@ describe('Database entities tests', (): void => { expect(fromDb?.createdAt).toBeDefined() }) - it('Should save bidirectional contact relations to database', async (): Promise => { + it('Should save bidirectional contact relationships to database', async (): Promise => { const contact1: IBasicContact = { uri: 'example1.com', contactType: { @@ -1872,7 +1870,7 @@ describe('Database entities tests', (): void => { expect(fromDb).toBeDefined() }) - it('Should enforce unique owner combination for contact relation', async (): Promise => { + it('Should enforce unique owner combination for contact relationship', async (): Promise => { const contact1: IBasicContact = { uri: 'example1.com', contactType: { @@ -2135,7 +2133,7 @@ describe('Database entities tests', (): void => { expect(fromDb).toBeDefined() }) - it('Should enforce unique contact id\'s for relationship sides', async (): Promise => { + it("Should enforce unique contact id's for relationship sides", async (): Promise => { const contact: IBasicContact = { uri: 'example.com', contactType: { @@ -2168,7 +2166,7 @@ describe('Database entities tests', (): void => { ) }) - it('Should delete contact relation', async (): Promise => { + it('Should delete contact relationship', async (): Promise => { const contact1: IBasicContact = { uri: 'example1.com', contactType: { diff --git a/packages/data-store/src/__tests__/contact.store.test.ts b/packages/data-store/src/__tests__/contact.store.test.ts index 833a27e5f..8e68e37b2 100644 --- a/packages/data-store/src/__tests__/contact.store.test.ts +++ b/packages/data-store/src/__tests__/contact.store.test.ts @@ -1,6 +1,6 @@ import { DataSource } from 'typeorm' -import { BasicContactRelationship, DataStoreContactEntities, IContactRelationship } from '../index' +import { DataStoreContactEntities } from '../index' import { ContactStore } from '../contact/ContactStore' import { IdentityRoleEnum, @@ -14,6 +14,8 @@ import { IBasicIdentity, IGetIdentitiesArgs, IGetContactsArgs, + BasicContactRelationship, + IContactRelationship } from '../types' describe('Contact store tests', (): void => { @@ -1118,7 +1120,7 @@ describe('Contact store tests', (): void => { const relationship: BasicContactRelationship = { leftContactId: savedContact1.id, - rightContactId: savedContact2.id + rightContactId: savedContact2.id, } await contactStore.addRelationship(relationship) @@ -1167,7 +1169,7 @@ describe('Contact store tests', (): void => { const relationship: BasicContactRelationship = { leftContactId: savedContact1.id, - rightContactId: savedContact2.id + rightContactId: savedContact2.id, } const savedRelationship: IContactRelationship = await contactStore.addRelationship(relationship) expect(savedRelationship).toBeDefined() @@ -1179,4 +1181,14 @@ describe('Contact store tests', (): void => { expect(result).toBeDefined() expect(result?.relationships?.length).toEqual(0) }) + + it('should throw error when removing relationship with unknown id', async (): Promise => { + const relationshipId = 'unknownRelationshipId' + + await expect(contactStore.removeRelationship({ relationshipId })).rejects.toThrow(`No relationship found for id: ${relationshipId}`) + }) + + // TODO cannot delete contact type when used by contact }) + +// maybe add some categories for the tests to find them diff --git a/packages/data-store/src/contact/AbstractContactStore.ts b/packages/data-store/src/contact/AbstractContactStore.ts index a02ebf4d1..c776617e4 100644 --- a/packages/data-store/src/contact/AbstractContactStore.ts +++ b/packages/data-store/src/contact/AbstractContactStore.ts @@ -11,19 +11,42 @@ import { IGetContactsArgs, IRemoveContactArgs, IUpdateContactArgs, + IAddRelationshipArgs, + IContactRelationship, + IRemoveRelationshipArgs, + IAddContactTypeArgs, + IContactType, + IGetContactTypeArgs, + IUpdateContactTypeArgs, + IGetContactTypesArgs, + IRemoveContactTypeArgs, + IGetRelationshipsArgs, + IGetRelationshipArgs, + IUpdateRelationshipArgs, } from '../types' export abstract class AbstractContactStore { abstract getContact(args: IGetContactArgs): Promise - abstract getContacts(args?: IGetContactsArgs): Promise> // TODO support person and organizations + abstract getContacts(args?: IGetContactsArgs): Promise> abstract addContact(args: IAddContactArgs): Promise - abstract updateContact(args: IUpdateContactArgs): Promise // TODO support person and organizations + abstract updateContact(args: IUpdateContactArgs): Promise abstract removeContact(args: IRemoveContactArgs): Promise + abstract getIdentity(args: IGetIdentityArgs): Promise - abstract getIdentities(args: IGetIdentitiesArgs): Promise> + abstract getIdentities(args?: IGetIdentitiesArgs): Promise> abstract addIdentity(args: IAddIdentityArgs): Promise abstract updateIdentity(args: IUpdateIdentityArgs): Promise abstract removeIdentity(args: IRemoveIdentityArgs): Promise - // TODO support creating relations + abstract getRelationship(args: IGetRelationshipArgs): Promise + abstract getRelationships(args?: IGetRelationshipsArgs): Promise> + abstract addRelationship(args: IAddRelationshipArgs): Promise + abstract updateRelationship(args: IUpdateRelationshipArgs): Promise + abstract removeRelationship(args: IRemoveRelationshipArgs): Promise + + abstract getContactType(args: IGetContactTypeArgs): Promise + abstract getContactTypes(args?: IGetContactTypesArgs): Promise> + abstract addContactType(args: IAddContactTypeArgs): Promise + abstract updateContactType(args: IUpdateContactTypeArgs): Promise + abstract removeContactType(args: IRemoveContactTypeArgs): Promise } diff --git a/packages/data-store/src/contact/ContactStore.ts b/packages/data-store/src/contact/ContactStore.ts index e6de04761..065221365 100644 --- a/packages/data-store/src/contact/ContactStore.ts +++ b/packages/data-store/src/contact/ContactStore.ts @@ -19,26 +19,28 @@ import { IIdentity, IRemoveRelationshipArgs, IContactRelationship, - IAddRelationshipArgs + IAddRelationshipArgs, + IGetRelationshipArgs, + IAddContactTypeArgs, + IContactType, + IGetContactTypeArgs, + IGetContactTypesArgs, + IUpdateContactTypeArgs, + IRemoveContactTypeArgs, + IGetRelationshipsArgs, + IUpdateRelationshipArgs, } from '../types' import { ContactEntity, contactEntityFrom, contactFrom } from '../entities/contact/ContactEntity' import { IdentityEntity, identityEntityFrom, identityFrom } from '../entities/contact/IdentityEntity' import { IdentityMetadataItemEntity } from '../entities/contact/IdentityMetadataItemEntity' import { CorrelationIdentifierEntity } from '../entities/contact/CorrelationIdentifierEntity' import { ConnectionEntity } from '../entities/contact/ConnectionEntity' -import { - BaseConfigEntity, - isDidAuthConfig, - isOpenIdConfig -} from '../entities/contact/BaseConfigEntity' +import { BaseConfigEntity, isDidAuthConfig, isOpenIdConfig } from '../entities/contact/BaseConfigEntity' import { DataSource, FindOptionsWhere, In, Repository } from 'typeorm' import { PersonEntity } from '../entities/contact/PersonEntity' import { OrganizationEntity } from '../entities/contact/OrganizationEntity' -import { - ContactRelationshipEntity, - contactRelationshipEntityFrom, - contactRelationshipFrom -} from '../entities/contact/ContactRelationshipEntity' +import { ContactRelationshipEntity, contactRelationshipEntityFrom, contactRelationshipFrom } from '../entities/contact/ContactRelationshipEntity' +import { ContactTypeEntity, contactTypeEntityFrom, contactTypeFrom } from '../entities/contact/ContactTypeEntity' const debug: Debug.Debugger = Debug('sphereon:ssi-sdk:contact-store') @@ -78,11 +80,10 @@ export class ContactStore extends AbstractContactStore { } addContact = async (args: IAddContactArgs): Promise => { - const { identities, contactOwner } = args //, alias, name, + const { identities, contactOwner } = args const contactRepository: Repository = (await this.dbConnection).getRepository(ContactEntity) - // TODO extend with more names? const result: ContactEntity | null = await contactRepository.findOne({ where: [ { @@ -95,7 +96,7 @@ export class ContactStore extends AbstractContactStore { if (result) { // TODO correct msg? - return Promise.reject(Error(`Duplicate names or display are not allowed. Display name: ${contactOwner.displayName}`)) //Name: ${name}, + return Promise.reject(Error(`Duplicate names or display are not allowed. Display name: ${contactOwner.displayName}`)) } for (const identity of identities ?? []) { @@ -111,7 +112,7 @@ export class ContactStore extends AbstractContactStore { } const contactEntity: ContactEntity = contactEntityFrom(args) - //debug('Adding contact', name) TODO fix + debug('Adding contact', args) const createdResult: ContactEntity = await contactRepository.save(contactEntity) return contactFrom(createdResult) @@ -143,7 +144,7 @@ export class ContactStore extends AbstractContactStore { removeContact = async ({ contactId }: IRemoveContactArgs): Promise => { const contactRepository: Repository = (await this.dbConnection).getRepository(ContactEntity) debug('Removing contact', contactId) - ;contactRepository + contactRepository .findOneById(contactId) .then(async (contact: ContactEntity | null): Promise => { if (!contact) { @@ -256,10 +257,6 @@ export class ContactStore extends AbstractContactStore { } addRelationship = async ({ leftContactId, rightContactId }: IAddRelationshipArgs): Promise => { - // if (leftContactId === rightContactId) { - // return Promise.reject(Error(`Cannot use the same id for both sides of the relationship`)) - // } - const contactRepository: Repository = (await this.dbConnection).getRepository(ContactEntity) const leftContact: ContactEntity | null = await contactRepository.findOne({ where: { id: leftContactId }, @@ -287,8 +284,57 @@ export class ContactStore extends AbstractContactStore { return contactRelationshipFrom(createdResult) } - // TODO get relationship? - // TODO get relationships? + getRelationship = async ({ relationshipId }: IGetRelationshipArgs): Promise => { + const result: ContactRelationshipEntity | null = await (await this.dbConnection).getRepository(ContactRelationshipEntity).findOne({ + where: { id: relationshipId }, + }) + + if (!result) { + return Promise.reject(Error(`No relationship found for id: ${relationshipId}`)) + } + + return contactRelationshipFrom(result) + } + + // TODO get relationships + getRelationships = async (args?: IGetRelationshipsArgs): Promise> => { + const contactRelationshipRepository: Repository = (await this.dbConnection).getRepository(ContactRelationshipEntity) + const initialResult: Array | null = await contactRelationshipRepository.find({ + ...(args?.filter && { where: args?.filter }), + }) + + const result: Array | null = await contactRelationshipRepository.find({ + where: { + id: In(initialResult.map((contactRelationship: ContactRelationshipEntity) => contactRelationship.id)), + }, + }) + + return result.map((contactRelationship: ContactRelationshipEntity) => contactRelationshipFrom(contactRelationship)) + } + + // TODO update relationship + updateRelationship = async ({ relationship }: IUpdateRelationshipArgs): Promise => { + const contactRelationshipRepository: Repository = (await this.dbConnection).getRepository(ContactRelationshipEntity) + const result: ContactRelationshipEntity | null = await contactRelationshipRepository.findOne({ + where: { id: relationship.id }, + }) + + if (!result) { + return Promise.reject(Error(`No contact relationship found for id: ${relationship.id}`)) + } + + // const updatedContactType = { + // // TODO fix type + // ...contactType, + // identities: result.identities, + // relationships: result.relationships, + // } + + debug('Updating contact relationship', relationship) + const updatedResult: ContactRelationshipEntity = await contactRelationshipRepository.save(relationship, { transaction: true }) + + return contactRelationshipFrom(updatedResult) + } removeRelationship = async ({ relationshipId }: IRemoveRelationshipArgs): Promise => { const contactRelationshipRepository: Repository = (await this.dbConnection).getRepository(ContactRelationshipEntity) @@ -305,7 +351,87 @@ export class ContactStore extends AbstractContactStore { await contactRelationshipRepository.delete(relationshipId) } - // TODO functions for adding/removing contact types? + // TODO add contact type + addContactType = async (args: IAddContactTypeArgs): Promise => { + // TODO do we need checks? + + const contactEntity: ContactTypeEntity = contactTypeEntityFrom(args) + debug('Adding contact type', args) + const createdResult: ContactTypeEntity = await (await this.dbConnection).getRepository(ContactTypeEntity).save(contactEntity) + + return contactTypeFrom(createdResult) + } + + // TODO get contact type + getContactType = async ({ contactTypeId }: IGetContactTypeArgs): Promise => { + const result: ContactTypeEntity | null = await (await this.dbConnection).getRepository(ContactTypeEntity).findOne({ + where: { id: contactTypeId }, + }) + + if (!result) { + return Promise.reject(Error(`No contact type found for id: ${contactTypeId}`)) + } + + return contactTypeFrom(result) + } + + // TODO get contact types + getContactTypes = async (args?: IGetContactTypesArgs): Promise> => { + const contactTypeRepository: Repository = (await this.dbConnection).getRepository(ContactTypeEntity) + const initialResult: Array | null = await contactTypeRepository.find({ + ...(args?.filter && { where: args?.filter }), + }) + + const result: Array | null = await contactTypeRepository.find({ + where: { + id: In(initialResult.map((contactType: ContactTypeEntity) => contactType.id)), + }, + }) + + return result.map((contactType: ContactTypeEntity) => contactTypeFrom(contactType)) + } + + // TODO update contact type + updateContactType = async ({ contactType }: IUpdateContactTypeArgs): Promise => { + const contactTypeRepository: Repository = (await this.dbConnection).getRepository(ContactTypeEntity) + const result: ContactTypeEntity | null = await contactTypeRepository.findOne({ + where: { id: contactType.id }, + }) + + if (!result) { + return Promise.reject(Error(`No contact type found for id: ${contactType.id}`)) + } + + // const updatedContactType = { + // // TODO fix type + // ...contactType, + // identities: result.identities, + // relationships: result.relationships, + // } + + debug('Updating contact type', contactType) + const updatedResult: ContactTypeEntity = await contactTypeRepository.save(contactType, { transaction: true }) + + return contactTypeFrom(updatedResult) + } + + // TODO remove contact type + removeContactType = async ({ contactTypeId }: IRemoveContactTypeArgs): Promise => { + // TODO maybe add check if contact type is used, if used, cannot delete + + const contactTypeRepository: Repository = (await this.dbConnection).getRepository(ContactTypeEntity) + const contactType: ContactTypeEntity | null = await contactTypeRepository.findOne({ + where: { id: contactTypeId }, + }) + + if (!contactType) { + return Promise.reject(Error(`No contact type found for id: ${contactTypeId}`)) + } + + debug('Removing contact type', contactTypeId) + + await contactTypeRepository.delete(contactTypeId) + } private hasCorrectConfig(type: ConnectionTypeEnum, config: BasicConnectionConfig): boolean { switch (type) { @@ -325,9 +451,9 @@ export class ContactStore extends AbstractContactStore { await ( await this.dbConnection ) - .getRepository(CorrelationIdentifierEntity) - .delete(identity.identifier.id) - .catch((error) => Promise.reject(Error(`Unable to remove identity.identifier with id: ${identity.identifier.id}. ${error}`))) + .getRepository(CorrelationIdentifierEntity) + .delete(identity.identifier.id) + .catch((error) => Promise.reject(Error(`Unable to remove identity.identifier with id: ${identity.identifier.id}. ${error}`))) if (identity.connection) { await (await this.dbConnection).getRepository(BaseConfigEntity).delete(identity.connection.config.id) @@ -335,9 +461,9 @@ export class ContactStore extends AbstractContactStore { await ( await this.dbConnection ) - .getRepository(ConnectionEntity) - .delete(identity.connection.id) - .catch((error) => Promise.reject(Error(`Unable to remove identity.connection with id. ${error}`))) + .getRepository(ConnectionEntity) + .delete(identity.connection.id) + .catch((error) => Promise.reject(Error(`Unable to remove identity.connection with id. ${error}`))) } if (identity.metadata) { @@ -345,17 +471,16 @@ export class ContactStore extends AbstractContactStore { await ( await this.dbConnection ) - .getRepository(IdentityMetadataItemEntity) - .delete(metadataItem.id) - .catch((error) => Promise.reject(Error(`Unable to remove metadataItem.id with id ${metadataItem.id}. ${error}`))) + .getRepository(IdentityMetadataItemEntity) + .delete(metadataItem.id) + .catch((error) => Promise.reject(Error(`Unable to remove metadataItem.id with id ${metadataItem.id}. ${error}`))) }) } ;(await this.dbConnection) - .getRepository(IdentityEntity) - .delete(identity.id) - .catch((error) => Promise.reject(Error(`Unable to remove metadataItem.id with id ${identity.id}. ${error}`))) + .getRepository(IdentityEntity) + .delete(identity.id) + .catch((error) => Promise.reject(Error(`Unable to remove metadataItem.id with id ${identity.id}. ${error}`))) }) } - } diff --git a/packages/data-store/src/entities/contact/BaseConfigEntity.ts b/packages/data-store/src/entities/contact/BaseConfigEntity.ts index 0565175c3..b3d7ffd10 100644 --- a/packages/data-store/src/entities/contact/BaseConfigEntity.ts +++ b/packages/data-store/src/entities/contact/BaseConfigEntity.ts @@ -1,10 +1,5 @@ import { BaseEntity, Entity, PrimaryGeneratedColumn, TableInheritance } from 'typeorm' -import { - BasicConnectionConfig, - ConnectionConfig, - IDidAuthConfig, - IOpenIdConfig -} from '../../types' +import { BasicConnectionConfig, ConnectionConfig, IDidAuthConfig, IOpenIdConfig } from '../../types' import { OpenIdConfigEntity } from './OpenIdConfigEntity' import { DidAuthConfigEntity } from './DidAuthConfigEntity' diff --git a/packages/data-store/src/entities/contact/ConnectionEntity.ts b/packages/data-store/src/entities/contact/ConnectionEntity.ts index 1d75c7756..33dc5644b 100644 --- a/packages/data-store/src/entities/contact/ConnectionEntity.ts +++ b/packages/data-store/src/entities/contact/ConnectionEntity.ts @@ -46,7 +46,7 @@ export const connectionFrom = (connection: ConnectionEntity): IConnection => { // TODO move to base config? const configEntityFrom = (config: BasicConnectionConfig): BaseConfigEntity => { - if (isOpenIdConfig((config))) { + if (isOpenIdConfig(config)) { return openIdConfigEntityFrom(config) } else if (isDidAuthConfig(config)) { return didAuthConfigEntityFrom(config) diff --git a/packages/data-store/src/entities/contact/ContactEntity.ts b/packages/data-store/src/entities/contact/ContactEntity.ts index e8f36f949..dd2b27492 100644 --- a/packages/data-store/src/entities/contact/ContactEntity.ts +++ b/packages/data-store/src/entities/contact/ContactEntity.ts @@ -113,13 +113,14 @@ export const contactEntityFrom = (contact: IBasicContact): ContactEntity => { } export const contactFrom = (contact: ContactEntity): IContact => { - console.log('') return { id: contact.id, uri: contact.uri, roles: [...new Set(contact.identities?.flatMap((identity: IdentityEntity) => identity.roles))] ?? [], identities: contact.identities ? contact.identities.map((identity: IdentityEntity) => identityFrom(identity)) : [], - relationships: contact.relationships ? contact.relationships.map((relationship: ContactRelationshipEntity) => contactRelationshipFrom(relationship)) : [], + relationships: contact.relationships + ? contact.relationships.map((relationship: ContactRelationshipEntity) => contactRelationshipFrom(relationship)) + : [], contactType: contactTypeFrom(contact.contactType), contactOwner: contactOwnerFrom(contact.contactOwner), createdAt: contact.createdAt, diff --git a/packages/data-store/src/entities/contact/ContactOwnerEntity.ts b/packages/data-store/src/entities/contact/ContactOwnerEntity.ts index 2b0f38825..00a4bc476 100644 --- a/packages/data-store/src/entities/contact/ContactOwnerEntity.ts +++ b/packages/data-store/src/entities/contact/ContactOwnerEntity.ts @@ -1,15 +1,5 @@ import { BaseEntity, CreateDateColumn, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn, TableInheritance, UpdateDateColumn } from 'typeorm' import { ContactEntity } from './ContactEntity' -// import { IPerson } from '../../types' -// import { PersonEntity } from './PersonEntity' -// import { -// // BasicContactOwner, -// // BasicOrganization, -// // BasicPerson, -// // ContactOwner -// } from '../../types' -// import { organizationEntityFrom, organizationFrom } from './OrganizationEntity' -// import { personEntityFrom, personFrom } from './PersonEntity' @Entity('ContactOwner') @TableInheritance({ column: { type: 'varchar', name: 'type' } }) @@ -29,54 +19,3 @@ export abstract class ContactOwnerEntity extends BaseEntity { @JoinColumn({ name: 'contactId' }) contact!: ContactEntity } - -// export const contactOwnerEntityFrom = (owner: BasicContactOwner): ContactOwnerEntity => { -// if (isPerson(owner)) { -// return personEntityFrom(owner) -// } else if (isOrganization(owner)) { -// return organizationEntityFrom(owner) -// } -// -// throw new Error('Owner type not supported') -// -// -// // switch (type) { -// // case ContactTypeEnum.PERSON: -// // return personEntityFrom(owner) -// // case ContactTypeEnum.ORGANIZATION: -// // return organizationEntityFrom(owner) -// // default: -// // throw new Error('Contact type not supported') -// // } -// } - -// export const contactOwnerFrom = (owner: ContactOwnerEntity): ContactOwner => { -// if (isPerson(owner)) { -// // @ts-ignore -// return personFrom(owner) -// } else if (isOrganization(owner)) { -// // @ts-ignore -// return organizationFrom(owner) -// } -// -// throw new Error('Owner type not supported') -// } -// -// // TODO move? -// const isPerson = (owner: BasicContactOwner | ContactOwnerEntity): owner is BasicPerson => -// 'firstName' in owner && 'middleName' in owner && 'lastName' in owner -// -// const isOrganization = (owner: BasicContactOwner | ContactOwnerEntity): owner is BasicOrganization => -// 'legalName' in owner && 'cocNumber' in owner - -// export const personFrom = (person: PersonEntity): IPerson => { -// return { -// id: person.id, -// firstName: person.firstName, -// middleName: person.middleName, -// lastName: person.lastName, -// displayName: person.displayName, -// createdAt: person.createdAt, -// lastUpdatedAt: person.lastUpdatedAt, -// } -// } diff --git a/packages/data-store/src/entities/contact/ContactRelationshipEntity.ts b/packages/data-store/src/entities/contact/ContactRelationshipEntity.ts index 413140b4f..d25808568 100644 --- a/packages/data-store/src/entities/contact/ContactRelationshipEntity.ts +++ b/packages/data-store/src/entities/contact/ContactRelationshipEntity.ts @@ -5,10 +5,10 @@ import { CreateDateColumn, UpdateDateColumn, ManyToOne, - // Column, + Column, Index, BeforeInsert, - BeforeUpdate + BeforeUpdate, } from 'typeorm' import { ContactEntity } from './ContactEntity' import { IContactRelationship } from '../../types' @@ -27,8 +27,8 @@ export class ContactRelationshipEntity { // @JoinColumn({ name: 'left_contact_id' }) left!: ContactEntity - // @Column({ name: 'left', nullable: true }) - // leftContactId?: string + @Column({ name: 'leftId', nullable: false }) + leftId!: string @ManyToOne(() => ContactEntity, { nullable: false, @@ -37,8 +37,8 @@ export class ContactRelationshipEntity { // @JoinColumn({ name: 'right_contact_id' }) right!: ContactEntity - // @Column({ name: 'right', nullable: true }) - // rightContactId?: string + @Column({ name: 'rightId', nullable: false }) + rightId!: string @CreateDateColumn({ name: 'created_at', nullable: false }) createdAt!: Date @@ -66,13 +66,12 @@ export const contactRelationshipEntityFrom = (relationship: { left: ContactEntit } export const contactRelationshipFrom = (relationship: ContactRelationshipEntity): IContactRelationship => { - console.log('') return { id: relationship.id, - leftContactId: relationship.left.id, - rightContactId: relationship.right.id, + leftContactId: relationship.leftId, //relationship.left.id, + rightContactId: relationship.rightId, //relationship.right.id, createdAt: relationship.createdAt, - lastUpdatedAt: relationship.lastUpdatedAt + lastUpdatedAt: relationship.lastUpdatedAt, } // TODO convert IContact to ContactEntity here } diff --git a/packages/data-store/src/entities/contact/OrganizationEntity.ts b/packages/data-store/src/entities/contact/OrganizationEntity.ts index a7516982d..939680d46 100644 --- a/packages/data-store/src/entities/contact/OrganizationEntity.ts +++ b/packages/data-store/src/entities/contact/OrganizationEntity.ts @@ -16,6 +16,7 @@ export class OrganizationEntity extends ContactOwnerEntity { @IsNotEmpty({ message: 'Blank display names are not allowed' }) displayName!: string + // TODO uniek per tenant @Column({ name: 'cocNumber', length: 255, nullable: true, unique: true }) @Validate(IsNonEmptyStringConstraint, { message: 'Blank coc numbers are not allowed' }) cocNumber?: string diff --git a/packages/data-store/src/entities/contact/PersonEntity.ts b/packages/data-store/src/entities/contact/PersonEntity.ts index 207e2a3af..45f2f2501 100644 --- a/packages/data-store/src/entities/contact/PersonEntity.ts +++ b/packages/data-store/src/entities/contact/PersonEntity.ts @@ -19,8 +19,6 @@ export class PersonEntity extends ContactOwnerEntity { @IsNotEmpty({ message: 'Blank last names are not allowed' }) lastName!: string - // TODO do we want a suffix? - @Column({ name: 'displayName', length: 255, nullable: false, unique: true }) @IsNotEmpty({ message: 'Blank display names are not allowed' }) displayName!: string diff --git a/packages/data-store/src/index.ts b/packages/data-store/src/index.ts index c538c896b..97dc8e202 100644 --- a/packages/data-store/src/index.ts +++ b/packages/data-store/src/index.ts @@ -15,11 +15,11 @@ import { ImageDimensionsEntity, imageDimensionsEntityFrom } from './entities/iss import { IssuerLocaleBrandingEntity, issuerLocaleBrandingEntityFrom } from './entities/issuanceBranding/IssuerLocaleBrandingEntity' import { IssuerBrandingEntity, issuerBrandingEntityFrom } from './entities/issuanceBranding/IssuerBrandingEntity' import { TextAttributesEntity, textAttributesEntityFrom } from './entities/issuanceBranding/TextAttributesEntity' -import { ContactRelationshipEntity } from './entities/contact/ContactRelationshipEntity' -import { ContactTypeEntity } from './entities/contact/ContactTypeEntity' +import { ContactRelationshipEntity, contactRelationshipEntityFrom } from './entities/contact/ContactRelationshipEntity' +import { ContactTypeEntity, contactTypeEntityFrom } from './entities/contact/ContactTypeEntity' import { ContactOwnerEntity } from './entities/contact/ContactOwnerEntity' -import { OrganizationEntity } from './entities/contact/OrganizationEntity' -import { PersonEntity } from './entities/contact/PersonEntity' +import { OrganizationEntity, organizationEntityFrom } from './entities/contact/OrganizationEntity' +import { PersonEntity, personEntityFrom } from './entities/contact/PersonEntity' export { ContactStore } from './contact/ContactStore' export { AbstractContactStore } from './contact/AbstractContactStore' @@ -89,4 +89,8 @@ export { textAttributesEntityFrom, issuerLocaleBrandingEntityFrom, credentialLocaleBrandingEntityFrom, + contactRelationshipEntityFrom, + contactTypeEntityFrom, + organizationEntityFrom, + personEntityFrom, } diff --git a/packages/data-store/src/types/contact/IAbstractContactStore.ts b/packages/data-store/src/types/contact/IAbstractContactStore.ts index d0c504491..f32475143 100644 --- a/packages/data-store/src/types/contact/IAbstractContactStore.ts +++ b/packages/data-store/src/types/contact/IAbstractContactStore.ts @@ -1,14 +1,22 @@ -// import { FindOptionsWhere } from 'typeorm' -// import { ContactEntity } from '../../entities/contact/ContactEntity' -// import { IdentityEntity } from '../../entities/contact/IdentityEntity' -import { BasicContactType, BasicContactOwner, IBasicIdentity, IContact, IIdentity, IPartialContact, IPartialIdentity } from './contact' - -// TODO WAL-625 refactor types to use interfaces and not the entities as the store should be replaceable -// export type FindContactArgs = FindOptionsWhere[] -// export type FindIdentityArgs = FindOptionsWhere[] +import { + BasicContactType, + BasicContactOwner, + IBasicIdentity, + IContact, + IIdentity, + IPartialContact, + IPartialIdentity, + ContactTypeEnum, + IContactType, + IContactRelationship, + IPartialContactRelationship, + IPartialContactType +} from './contact' export type FindContactArgs = Array export type FindIdentityArgs = Array +export type FindContactTypeArgs = Array +export type FindRelationshipArgs = Array export interface IGetContactArgs { contactId: string @@ -19,9 +27,7 @@ export interface IGetContactsArgs { } export interface IAddContactArgs { - // name: string - // alias: string - uri?: string // TODO what we do with uri? + uri?: string contactType: BasicContactType // TODO we can have a situation where we want to add a contact to an existing type, so use BasicContactType | IContactType? also make a test for these 2 situations in the store contactOwner: BasicContactOwner identities?: Array @@ -64,3 +70,38 @@ export interface IAddRelationshipArgs { leftContactId: string rightContactId: string } + +export interface IGetRelationshipArgs { + relationshipId: string +} + +export interface IGetRelationshipsArgs { + filter: FindRelationshipArgs +} + +export interface IUpdateRelationshipArgs { + relationship: Omit +} + +export interface IAddContactTypeArgs { + type: ContactTypeEnum + name: string + tenantId: string + description?: string +} + +export interface IGetContactTypeArgs { + contactTypeId: string +} + +export interface IGetContactTypesArgs { + filter?: FindContactTypeArgs +} + +export interface IUpdateContactTypeArgs { + contactType: Omit +} + +export interface IRemoveContactTypeArgs { + contactTypeId: string +} diff --git a/packages/data-store/src/types/contact/contact.ts b/packages/data-store/src/types/contact/contact.ts index fbc711682..6fb3e2e8c 100644 --- a/packages/data-store/src/types/contact/contact.ts +++ b/packages/data-store/src/types/contact/contact.ts @@ -171,8 +171,8 @@ export interface IPartialContactType extends Partial {} export interface IContactRelationship { id: string - leftContactId: string//IContact // TODO - rightContactId: string//IContact // TODO + leftContactId: string + rightContactId: string createdAt: Date lastUpdatedAt: Date } diff --git a/packages/issuance-branding/__tests__/localAgent.test.ts b/packages/issuance-branding/__tests__/localAgent.test.ts index deac4b496..3345ff5ac 100644 --- a/packages/issuance-branding/__tests__/localAgent.test.ts +++ b/packages/issuance-branding/__tests__/localAgent.test.ts @@ -29,6 +29,6 @@ const testContext = { tearDown, } -describe('Local integration tests', () => { +describe('Local integration tests', (): void => { issuanceBrandingAgentLogic(testContext) }) diff --git a/packages/issuance-branding/__tests__/restAgent.test.ts b/packages/issuance-branding/__tests__/restAgent.test.ts index e5a30fc72..5957779bc 100644 --- a/packages/issuance-branding/__tests__/restAgent.test.ts +++ b/packages/issuance-branding/__tests__/restAgent.test.ts @@ -66,6 +66,6 @@ const testContext = { tearDown, } -describe('REST integration tests', () => { +describe('REST integration tests', (): void => { issuanceBrandingAgentLogic(testContext) })