UNPKG

@sphereon/ssi-sdk.data-store

Version:

769 lines (629 loc) • 30.7 kB
import { OrPromise } from '@sphereon/ssi-types' import { BaseEntity, DataSource, type FindOptionsWhere, In, type Repository } from 'typeorm' import Debug from 'debug' import { AbstractContactStore } from './AbstractContactStore' import { PartyEntity } from '../entities/contact/PartyEntity' import { IdentityEntity } from '../entities/contact/IdentityEntity' import { IdentityMetadataItemEntity } from '../entities/contact/IdentityMetadataItemEntity' import { CorrelationIdentifierEntity } from '../entities/contact/CorrelationIdentifierEntity' import { ConnectionEntity } from '../entities/contact/ConnectionEntity' import { BaseConfigEntity } from '../entities/contact/BaseConfigEntity' import { PartyRelationshipEntity } from '../entities/contact/PartyRelationshipEntity' import { PartyTypeEntity } from '../entities/contact/PartyTypeEntity' import { BaseContactEntity } from '../entities/contact/BaseContactEntity' import { ElectronicAddressEntity } from '../entities/contact/ElectronicAddressEntity' import { PhysicalAddressEntity } from '../entities/contact/PhysicalAddressEntity' import { electronicAddressEntityFrom, electronicAddressFrom, identityEntityFrom, identityFrom, isDidAuthConfig, isNaturalPerson, isOpenIdConfig, isOrganization, partyEntityFrom, partyFrom, partyRelationshipEntityFrom, partyRelationshipFrom, partyTypeEntityFrom, partyTypeFrom, physicalAddressEntityFrom, physicalAddressFrom, } from '../utils/contact/MappingUtils' import { AddElectronicAddressArgs, AddIdentityArgs, AddPartyArgs, AddPartyTypeArgs, AddPhysicalAddressArgs, AddRelationshipArgs, ConnectionType, CorrelationIdentifierType, ElectronicAddress, GetElectronicAddressArgs, GetElectronicAddressesArgs, GetIdentitiesArgs, GetIdentityArgs, GetPartiesArgs, GetPartyArgs, GetPartyTypeArgs, GetPartyTypesArgs, GetPhysicalAddressArgs, GetPhysicalAddressesArgs, GetRelationshipArgs, GetRelationshipsArgs, IMetadataEntity, Identity, MetadataItem, MetadataTypes, NonPersistedConnectionConfig, NonPersistedContact, Party, PartyRelationship, PartyType, PartyTypeType, PhysicalAddress, RemoveElectronicAddressArgs, RemoveIdentityArgs, RemovePartyArgs, RemovePartyTypeArgs, RemovePhysicalAddressArgs, RemoveRelationshipArgs, UpdateElectronicAddressArgs, UpdateIdentityArgs, UpdatePartyArgs, UpdatePartyTypeArgs, UpdatePhysicalAddressArgs, UpdateRelationshipArgs, } from '../types' const debug: Debug.Debugger = Debug('sphereon:ssi-sdk:contact-store') export class ContactStore extends AbstractContactStore { private readonly dbConnection: OrPromise<DataSource> constructor(dbConnection: OrPromise<DataSource>) { super() this.dbConnection = dbConnection } getParty = async (args: GetPartyArgs): Promise<Party> => { const { partyId } = args const result: PartyEntity | null = await (await this.dbConnection).getRepository(PartyEntity).findOne({ where: { id: partyId }, }) if (!result) { return Promise.reject(Error(`No party found for id: ${partyId}`)) } return partyFrom(result) } getParties = async (args?: GetPartiesArgs): Promise<Array<Party>> => { debug('getParties()', args) const { filter } = args ?? {} const partyRepository = (await this.dbConnection).getRepository(PartyEntity) const filterConditions = this.buildFilters(filter) const initialResult = await partyRepository.find({ select: ['id'], where: filterConditions }) // Fetch the complete entities based on the initial result IDs const result = await partyRepository.find({ where: { id: In(initialResult.map((party) => party.id)) } }) debug(`getParties() resulted in ${result.length} parties`) return result.map(partyFrom) } addParty = async (args: AddPartyArgs): Promise<Party> => { const { identities, contact, partyType } = args const partyRepository: Repository<PartyEntity> = (await this.dbConnection).getRepository(PartyEntity) if (!this.hasCorrectPartyType(partyType.type, contact)) { return Promise.reject(Error(`Party type ${partyType.type}, does not match for provided contact`)) } for (const identity of identities ?? []) { if (identity.identifier.type === CorrelationIdentifierType.URL) { if (!identity.connection) { return Promise.reject(Error(`Identity with correlation type ${CorrelationIdentifierType.URL} should contain a connection`)) } if (!this.hasCorrectConnectionConfig(identity.connection.type, identity.connection.config)) { return Promise.reject(Error(`Connection type ${identity.connection.type}, does not match for provided config`)) } } } const partyEntity: PartyEntity = partyEntityFrom(args) debug('Adding party', args) const createdResult: PartyEntity = await partyRepository.save(partyEntity) return partyFrom(createdResult) } updateParty = async (args: UpdatePartyArgs): Promise<Party> => { const { party } = args const partyRepository: Repository<PartyEntity> = (await this.dbConnection).getRepository(PartyEntity) const result: PartyEntity | null = await partyRepository.findOne({ where: { id: party.id }, }) if (!result) { return Promise.reject(Error(`No party found for id: ${party.id}`)) } const updatedParty = { ...party, identities: result.identities, type: result.partyType, relationships: result.relationships, electronicAddresses: result.electronicAddresses, } debug('Updating party', party) const updatedResult: PartyEntity = await partyRepository.save(updatedParty, { transaction: true }) return partyFrom(updatedResult) } removeParty = async (args: RemovePartyArgs): Promise<void> => { const { partyId } = args const partyRepository: Repository<PartyEntity> = (await this.dbConnection).getRepository(PartyEntity) debug('Removing party', partyId) partyRepository .findOneById(partyId) .then(async (party: PartyEntity | null): Promise<void> => { if (!party) { await Promise.reject(Error(`Unable to find the party with id to remove: ${partyId}`)) } else { await this.deleteIdentities(party.identities) await this.deleteElectronicAddresses(party.electronicAddresses) await this.deletePhysicalAddresses(party.physicalAddresses) await partyRepository .delete({ id: partyId }) .catch((error) => Promise.reject(Error(`Unable to remove party with id: ${partyId}. ${error}`))) const partyContactRepository: Repository<BaseContactEntity> = (await this.dbConnection).getRepository(BaseContactEntity) await partyContactRepository .delete({ id: party.contact.id }) .catch((error) => Promise.reject(Error(`Unable to remove party contact with id: ${party.contact.id}. ${error}`))) } }) .catch((error) => Promise.reject(Error(`Unable to remove party with id: ${partyId}. ${error}`))) } getIdentity = async (args: GetIdentityArgs): Promise<Identity> => { const { identityId } = args const result: IdentityEntity | null = await (await this.dbConnection).getRepository(IdentityEntity).findOne({ where: { id: identityId }, }) if (!result) { return Promise.reject(Error(`No identity found for id: ${identityId}`)) } return identityFrom(result) } getIdentities = async (args?: GetIdentitiesArgs): Promise<Array<Identity>> => { const { filter } = args ?? {} const identityRepository = (await this.dbConnection).getRepository(IdentityEntity) const filterConditions = this.buildFilters(filter) const initialResult = await identityRepository.find({ select: ['id'], where: filterConditions }) const result = await identityRepository.find({ where: { id: In(initialResult.map((identity) => identity.id)) } }) return result.map(identityFrom) } addIdentity = async (args: AddIdentityArgs): Promise<Identity> => { const { identity, partyId } = args const party: PartyEntity | null = await (await this.dbConnection).getRepository(PartyEntity).findOne({ where: { id: partyId }, }) if (!party) { return Promise.reject(Error(`No party found for id: ${partyId}`)) } if (identity.identifier.type === CorrelationIdentifierType.URL) { if (!identity.connection) { return Promise.reject(Error(`Identity with correlation type ${CorrelationIdentifierType.URL} should contain a connection`)) } if (!this.hasCorrectConnectionConfig(identity.connection.type, identity.connection.config)) { return Promise.reject(Error(`Connection type ${identity.connection.type}, does not match for provided config`)) } } const identityEntity: IdentityEntity = identityEntityFrom(identity) identityEntity.party = party debug('Adding identity', identity) const result: IdentityEntity = await (await this.dbConnection).getRepository(IdentityEntity).save(identityEntity, { transaction: true, }) return identityFrom(result) } updateIdentity = async (args: UpdateIdentityArgs): Promise<Identity> => { const { identity } = args const identityRepository: Repository<IdentityEntity> = (await this.dbConnection).getRepository(IdentityEntity) const result: IdentityEntity | null = await identityRepository.findOne({ where: { id: identity.id }, }) if (!result) { return Promise.reject(Error(`No identity found for id: ${identity.id}`)) } if (identity.identifier.type === CorrelationIdentifierType.URL) { if (!identity.connection) { return Promise.reject(Error(`Identity with correlation type ${CorrelationIdentifierType.URL} should contain a connection`)) } if (!this.hasCorrectConnectionConfig(identity.connection.type, identity.connection.config)) { return Promise.reject(Error(`Connection type ${identity.connection.type}, does not match for provided config`)) } } debug('Updating identity', identity) const updatedResult: IdentityEntity = await identityRepository.save(identity, { transaction: true }) return identityFrom(updatedResult) } removeIdentity = async (args: RemoveIdentityArgs): Promise<void> => { const { identityId } = args const identity: IdentityEntity | null = await (await this.dbConnection).getRepository(IdentityEntity).findOne({ where: { id: identityId }, }) if (!identity) { return Promise.reject(Error(`No identity found for id: ${identityId}`)) } debug('Removing identity', identityId) await this.deleteIdentities([identity]) } addRelationship = async (args: AddRelationshipArgs): Promise<PartyRelationship> => { const { leftId, rightId } = args return this.assertRelationshipSides(leftId, rightId).then(async (): Promise<PartyRelationship> => { const relationship: PartyRelationshipEntity = partyRelationshipEntityFrom({ leftId, rightId, }) debug('Adding party relationship', relationship) const createdResult: PartyRelationshipEntity = await (await this.dbConnection).getRepository(PartyRelationshipEntity).save(relationship) return partyRelationshipFrom(createdResult) }) } getRelationship = async (args: GetRelationshipArgs): Promise<PartyRelationship> => { const { relationshipId } = args const result: PartyRelationshipEntity | null = await (await this.dbConnection).getRepository(PartyRelationshipEntity).findOne({ where: { id: relationshipId }, }) if (!result) { return Promise.reject(Error(`No relationship found for id: ${relationshipId}`)) } return partyRelationshipFrom(result) } getRelationships = async (args?: GetRelationshipsArgs): Promise<Array<PartyRelationship>> => { const { filter } = args ?? {} const partyRelationshipRepository: Repository<PartyRelationshipEntity> = (await this.dbConnection).getRepository(PartyRelationshipEntity) const initialResult: Array<PartyRelationshipEntity> = await partyRelationshipRepository.find({ ...(filter && { where: filter }), }) const result: Array<PartyRelationshipEntity> = await partyRelationshipRepository.find({ where: { id: In(initialResult.map((partyRelationship: PartyRelationshipEntity) => partyRelationship.id)), }, }) return result.map((partyRelationship: PartyRelationshipEntity) => partyRelationshipFrom(partyRelationship)) } updateRelationship = async (args: UpdateRelationshipArgs): Promise<PartyRelationship> => { const { relationship } = args const partyRelationshipRepository: Repository<PartyRelationshipEntity> = (await this.dbConnection).getRepository(PartyRelationshipEntity) const result: PartyRelationshipEntity | null = await partyRelationshipRepository.findOne({ where: { id: relationship.id }, }) if (!result) { return Promise.reject(Error(`No party relationship found for id: ${relationship.id}`)) } return this.assertRelationshipSides(relationship.leftId, relationship.rightId).then(async (): Promise<PartyRelationship> => { debug('Updating party relationship', relationship) const updatedResult: PartyRelationshipEntity = await partyRelationshipRepository.save(relationship, { transaction: true }) return partyRelationshipFrom(updatedResult) }) } removeRelationship = async (args: RemoveRelationshipArgs): Promise<void> => { const { relationshipId } = args const partyRelationshipRepository: Repository<PartyRelationshipEntity> = (await this.dbConnection).getRepository(PartyRelationshipEntity) const relationship: PartyRelationshipEntity | null = await partyRelationshipRepository.findOne({ where: { id: relationshipId }, }) if (!relationship) { return Promise.reject(Error(`No relationship found for id: ${relationshipId}`)) } debug('Removing relationship', relationshipId) await partyRelationshipRepository.delete(relationshipId) } addPartyType = async (args: AddPartyTypeArgs): Promise<PartyType> => { const partyEntity: PartyTypeEntity = partyTypeEntityFrom(args) debug('Adding party type', args) const createdResult: PartyTypeEntity = await (await this.dbConnection).getRepository(PartyTypeEntity).save(partyEntity) return partyTypeFrom(createdResult) } getPartyType = async (args: GetPartyTypeArgs): Promise<PartyType> => { const { partyTypeId } = args const result: PartyTypeEntity | null = await (await this.dbConnection).getRepository(PartyTypeEntity).findOne({ where: { id: partyTypeId }, }) if (!result) { return Promise.reject(Error(`No party type found for id: ${partyTypeId}`)) } return partyTypeFrom(result) } getPartyTypes = async (args?: GetPartyTypesArgs): Promise<Array<PartyType>> => { const { filter } = args ?? {} const partyTypeRepository: Repository<PartyTypeEntity> = (await this.dbConnection).getRepository(PartyTypeEntity) const initialResult: Array<PartyTypeEntity> = await partyTypeRepository.find({ ...(filter && { where: filter }), }) const result: Array<PartyTypeEntity> = await partyTypeRepository.find({ where: { id: In(initialResult.map((partyType: PartyTypeEntity) => partyType.id)), }, }) return result.map((partyType: PartyTypeEntity) => partyTypeFrom(partyType)) } updatePartyType = async (args: UpdatePartyTypeArgs): Promise<PartyType> => { const { partyType } = args const partyTypeRepository: Repository<PartyTypeEntity> = (await this.dbConnection).getRepository(PartyTypeEntity) const result: PartyTypeEntity | null = await partyTypeRepository.findOne({ where: { id: partyType.id }, }) if (!result) { return Promise.reject(Error(`No party type found for id: ${partyType.id}`)) } debug('Updating party type', partyType) const updatedResult: PartyTypeEntity = await partyTypeRepository.save(partyType, { transaction: true }) return partyTypeFrom(updatedResult) } removePartyType = async (args: RemovePartyTypeArgs): Promise<void> => { const { partyTypeId } = args const parties: Array<PartyEntity> = await (await this.dbConnection).getRepository(PartyEntity).find({ where: { partyType: { id: partyTypeId, }, }, }) if (parties.length > 0) { return Promise.reject(Error(`Unable to remove party type with id: ${partyTypeId}. Party type is in use`)) } const partyTypeRepository: Repository<PartyTypeEntity> = (await this.dbConnection).getRepository(PartyTypeEntity) const partyType: PartyTypeEntity | null = await partyTypeRepository.findOne({ where: { id: partyTypeId }, }) if (!partyType) { return Promise.reject(Error(`No party type found for id: ${partyTypeId}`)) } debug('Removing party type', partyTypeId) await partyTypeRepository.delete(partyTypeId) } getElectronicAddress = async (args: GetElectronicAddressArgs): Promise<ElectronicAddress> => { const { electronicAddressId } = args const result: ElectronicAddressEntity | null = await (await this.dbConnection).getRepository(ElectronicAddressEntity).findOne({ where: { id: electronicAddressId }, }) if (!result) { return Promise.reject(Error(`No electronic address found for id: ${electronicAddressId}`)) } return electronicAddressFrom(result) } getElectronicAddresses = async (args?: GetElectronicAddressesArgs): Promise<Array<ElectronicAddress>> => { const { filter } = args ?? {} const electronicAddressRepository: Repository<ElectronicAddressEntity> = (await this.dbConnection).getRepository(ElectronicAddressEntity) const initialResult: Array<ElectronicAddressEntity> = await electronicAddressRepository.find({ ...(filter && { where: filter }), }) const result: Array<ElectronicAddressEntity> = await electronicAddressRepository.find({ where: { id: In(initialResult.map((electronicAddress: ElectronicAddressEntity) => electronicAddress.id)), }, }) return result.map((electronicAddress: ElectronicAddressEntity) => electronicAddressFrom(electronicAddress)) } addElectronicAddress = async (args: AddElectronicAddressArgs): Promise<ElectronicAddress> => { const { electronicAddress, partyId } = args const party: PartyEntity | null = await (await this.dbConnection).getRepository(PartyEntity).findOne({ where: { id: partyId }, }) if (!party) { return Promise.reject(Error(`No party found for id: ${partyId}`)) } const electronicAddressEntity: ElectronicAddressEntity = electronicAddressEntityFrom(electronicAddress) electronicAddressEntity.party = party debug('Adding electronic address', electronicAddress) const result: ElectronicAddressEntity = await (await this.dbConnection).getRepository(ElectronicAddressEntity).save(electronicAddressEntity, { transaction: true, }) return electronicAddressFrom(result) } updateElectronicAddress = async (args: UpdateElectronicAddressArgs): Promise<ElectronicAddress> => { const { electronicAddress } = args const electronicAddressRepository: Repository<ElectronicAddressEntity> = (await this.dbConnection).getRepository(ElectronicAddressEntity) const result: ElectronicAddressEntity | null = await electronicAddressRepository.findOne({ where: { id: electronicAddress.id }, }) if (!result) { return Promise.reject(Error(`No electronic address found for id: ${electronicAddress.id}`)) } debug('Updating electronic address', electronicAddress) const updatedResult: ElectronicAddressEntity = await electronicAddressRepository.save(electronicAddress, { transaction: true }) return electronicAddressFrom(updatedResult) } removeElectronicAddress = async (args: RemoveElectronicAddressArgs): Promise<void> => { const { electronicAddressId } = args const electronicAddressRepository: Repository<ElectronicAddressEntity> = (await this.dbConnection).getRepository(ElectronicAddressEntity) const electronicAddress: ElectronicAddressEntity | null = await electronicAddressRepository.findOne({ where: { id: electronicAddressId }, }) if (!electronicAddress) { return Promise.reject(Error(`No electronic address found for id: ${electronicAddressId}`)) } debug('Removing electronic address', electronicAddressId) await electronicAddressRepository.delete(electronicAddressId) } getPhysicalAddress = async (args: GetPhysicalAddressArgs): Promise<PhysicalAddress> => { const { physicalAddressId } = args const result: PhysicalAddressEntity | null = await (await this.dbConnection).getRepository(PhysicalAddressEntity).findOne({ where: { id: physicalAddressId }, }) if (!result) { return Promise.reject(Error(`No physical address found for id: ${physicalAddressId}`)) } return physicalAddressFrom(result) } getPhysicalAddresses = async (args?: GetPhysicalAddressesArgs): Promise<Array<PhysicalAddress>> => { const { filter } = args ?? {} const physicalAddressRepository: Repository<PhysicalAddressEntity> = (await this.dbConnection).getRepository(PhysicalAddressEntity) const initialResult: Array<PhysicalAddressEntity> = await physicalAddressRepository.find({ ...(filter && { where: filter }), }) const result: Array<PhysicalAddressEntity> = await physicalAddressRepository.find({ where: { id: In(initialResult.map((physicalAddress: PhysicalAddressEntity) => physicalAddress.id)), }, }) return result.map((physicalAddress: PhysicalAddressEntity) => physicalAddressFrom(physicalAddress)) } addPhysicalAddress = async (args: AddPhysicalAddressArgs): Promise<PhysicalAddress> => { const { physicalAddress, partyId } = args const party: PartyEntity | null = await (await this.dbConnection).getRepository(PartyEntity).findOne({ where: { id: partyId }, }) if (!party) { return Promise.reject(Error(`No party found for id: ${partyId}`)) } const physicalAddressEntity: PhysicalAddressEntity = physicalAddressEntityFrom(physicalAddress) physicalAddressEntity.party = party debug('Adding physical address', physicalAddress) const result: PhysicalAddressEntity = await (await this.dbConnection).getRepository(PhysicalAddressEntity).save(physicalAddressEntity, { transaction: true, }) return physicalAddressFrom(result) } updatePhysicalAddress = async (args: UpdatePhysicalAddressArgs): Promise<PhysicalAddress> => { const { physicalAddress } = args const physicalAddressRepository: Repository<PhysicalAddressEntity> = (await this.dbConnection).getRepository(PhysicalAddressEntity) const result: PhysicalAddressEntity | null = await physicalAddressRepository.findOne({ where: { id: physicalAddress.id }, }) if (!result) { return Promise.reject(Error(`No physical address found for id: ${physicalAddress.id}`)) } debug('Updating physical address', physicalAddress) const updatedResult: PhysicalAddressEntity = await physicalAddressRepository.save(physicalAddress, { transaction: true }) return physicalAddressFrom(updatedResult) } removePhysicalAddress = async (args: RemovePhysicalAddressArgs): Promise<void> => { const { physicalAddressId } = args const physicalAddressRepository: Repository<PhysicalAddressEntity> = (await this.dbConnection).getRepository(PhysicalAddressEntity) const physicalAddress: PhysicalAddressEntity | null = await physicalAddressRepository.findOne({ where: { id: physicalAddressId }, }) if (!physicalAddress) { return Promise.reject(Error(`No physical address found for id: ${physicalAddressId}`)) } debug('Removing physical address', physicalAddressId) await physicalAddressRepository.delete(physicalAddressId) } private hasCorrectConnectionConfig = (type: ConnectionType, config: NonPersistedConnectionConfig): boolean => { switch (type) { case ConnectionType.OPENID_CONNECT: return isOpenIdConfig(config) case ConnectionType.SIOPv2: return isDidAuthConfig(config) default: throw new Error('Connection type not supported') } } private hasCorrectPartyType = (type: PartyTypeType, contact: NonPersistedContact): boolean => { switch (type) { case PartyTypeType.NATURAL_PERSON: return isNaturalPerson(contact) case PartyTypeType.ORGANIZATION: return isOrganization(contact) default: throw new Error('Party type not supported') } } private deleteIdentities = async (identities: Array<IdentityEntity>): Promise<void> => { debug('Removing identities', identities) const connection: DataSource = await this.dbConnection const correlationIdentifierRepository: Repository<CorrelationIdentifierEntity> = connection.getRepository(CorrelationIdentifierEntity) const baseConfigRepository: Repository<BaseConfigEntity> = connection.getRepository(BaseConfigEntity) const connectionRepository: Repository<ConnectionEntity> = connection.getRepository(ConnectionEntity) const identityMetadataItemRepository: Repository<IdentityMetadataItemEntity> = connection.getRepository(IdentityMetadataItemEntity) const identityRepository: Repository<IdentityEntity> = connection.getRepository(IdentityEntity) identities.map(async (identity: IdentityEntity): Promise<void> => { await correlationIdentifierRepository .delete(identity.identifier.id) .catch((error) => Promise.reject(Error(`Unable to remove identity.identifier with id ${identity.identifier.id}. ${error}`))) if (identity.connection) { await baseConfigRepository.delete(identity.connection.config.id) await connectionRepository .delete(identity.connection.id) .catch((error) => Promise.reject(Error(`Unable to remove identity.connection with id ${identity.connection?.id}. ${error}`))) } if (identity.metadata) { identity.metadata.map(async (metadataItem: IdentityMetadataItemEntity): Promise<void> => { await identityMetadataItemRepository .delete(metadataItem.id) .catch((error) => Promise.reject(Error(`Unable to remove identity.metadataItem with id ${metadataItem.id}. ${error}`))) }) } await identityRepository .delete(identity.id) .catch((error) => Promise.reject(Error(`Unable to remove identity with id ${identity.id}. ${error}`))) }) } private deleteElectronicAddresses = async (electronicAddresses: Array<ElectronicAddressEntity>): Promise<void> => { debug('Removing electronic addresses', electronicAddresses) const electronicAddressRepository: Repository<ElectronicAddressEntity> = (await this.dbConnection).getRepository(ElectronicAddressEntity) electronicAddresses.map(async (electronicAddress: ElectronicAddressEntity): Promise<void> => { await electronicAddressRepository .delete(electronicAddress.id) .catch((error) => Promise.reject(Error(`Unable to remove electronic address with id ${electronicAddress.id}. ${error}`))) }) } private deletePhysicalAddresses = async (physicalAddresses: Array<PhysicalAddressEntity>): Promise<void> => { debug('Removing physical addresses', physicalAddresses) const physicalAddressRepository: Repository<PhysicalAddressEntity> = (await this.dbConnection).getRepository(PhysicalAddressEntity) physicalAddresses.map(async (physicalAddress: PhysicalAddressEntity): Promise<void> => { await physicalAddressRepository .delete(physicalAddress.id) .catch((error) => Promise.reject(Error(`Unable to remove physical address with id ${physicalAddress.id}. ${error}`))) }) } private assertRelationshipSides = async (leftId: string, rightId: string): Promise<void> => { const partyRepository: Repository<PartyEntity> = (await this.dbConnection).getRepository(PartyEntity) const leftParty: PartyEntity | null = await partyRepository.findOne({ where: { id: leftId }, }) if (!leftParty) { return Promise.reject(Error(`No party found for left side of the relationship, party id: ${leftId}`)) } const rightParty: PartyEntity | null = await partyRepository.findOne({ where: { id: rightId }, }) if (!rightParty) { return Promise.reject(Error(`No party found for right side of the relationship, party id: ${rightId}`)) } } private buildFilters = <T extends BaseEntity>(filter?: Array<Record<string, any>>): Array<FindOptionsWhere<T>> | FindOptionsWhere<T> => { if (!filter) return {} return filter.map((condition) => this.processCondition(condition)) } private processCondition = (condition: Record<string, any>): Record<string, any> => { const conditionObject: Record<string, any> = {} Object.keys(condition).forEach((key) => { const value = condition[key] if (key === 'metadata' && value) { conditionObject[key] = this.buildMetadataCondition(value) } else if (typeof value === 'object' && value !== null) { conditionObject[key] = this.processCondition(value) } else { conditionObject[key] = value } }) return conditionObject } private buildMetadataCondition = <T extends MetadataItem<MetadataTypes>>(metadata: Partial<T>): FindOptionsWhere<IMetadataEntity> => { const metadataCondition: FindOptionsWhere<any> = { label: metadata.label, } switch (typeof metadata.value) { case 'string': metadataCondition.stringValue = metadata.value as string break case 'number': metadataCondition.numberValue = metadata.value as number break case 'boolean': metadataCondition.boolValue = metadata.value as boolean break case 'object': if (metadata.value instanceof Date) { metadataCondition.dateValue = metadata.value as Date } else { // For now, we only support / implement not-primitive type Date in the entity throw new Error(`Unsupported object type: ${Object.prototype.toString.call(metadata.value).slice(8, -1)} for value ${metadata.value}`) // slice to extract type from string [object String] } break default: throw new Error(`Unsupported value type: ${typeof metadata.value}`) } return metadataCondition } }