@sphereon/ssi-sdk.data-store 0.36.1-next.119 → 0.36.1-next.149

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/index.cjs +669 -107
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +21 -1
  4. package/dist/index.d.ts +21 -1
  5. package/dist/index.js +686 -122
  6. package/dist/index.js.map +1 -1
  7. package/package.json +9 -8
  8. package/src/__tests__/contact.entities.test.ts +3 -3
  9. package/src/__tests__/contact.store.test.ts +3 -3
  10. package/src/__tests__/issuanceBranding.entities.test.ts +5 -5
  11. package/src/__tests__/issuanceBranding.store.test.ts +314 -4
  12. package/src/contact/ContactStore.ts +51 -4
  13. package/src/entities/issuanceBranding/CredentialBrandingEntity.ts +44 -1
  14. package/src/entities/issuanceBranding/CredentialLocaleBrandingEntity.ts +64 -1
  15. package/src/entities/issuanceBranding/IssuerLocaleBrandingEntity.ts +63 -1
  16. package/src/index.ts +12 -0
  17. package/src/issuanceBranding/IssuanceBrandingStore.ts +45 -6
  18. package/src/migrations/generic/15-AddBrandingState.ts +64 -0
  19. package/src/migrations/generic/15-AddServiceMetadata.ts +66 -0
  20. package/src/migrations/generic/index.ts +21 -1
  21. package/src/migrations/index.ts +4 -0
  22. package/src/migrations/postgres/1764000000001-AddServiceMetadata.ts +44 -0
  23. package/src/migrations/postgres/1766000000000-AddBrandingState.ts +15 -0
  24. package/src/migrations/sqlite/1764000000002-AddServiceMetadata.ts +41 -0
  25. package/src/migrations/sqlite/1766000000000-AddBrandingState.ts +87 -0
  26. package/src/utils/issuanceBranding/HashUtils.ts +30 -0
  27. package/src/utils/issuanceBranding/MappingUtils.ts +21 -1
  28. package/src/utils/presentationDefinition/MappingUtils.ts +5 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sphereon/ssi-sdk.data-store",
3
- "version": "0.36.1-next.119+86f2dfd2",
3
+ "version": "0.36.1-next.149+abd25c87",
4
4
  "source": "src/index.ts",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -28,13 +28,14 @@
28
28
  "dependencies": {
29
29
  "@sphereon/kmp-mdoc-core": "0.2.0-SNAPSHOT.26",
30
30
  "@sphereon/pex": "5.0.0-unstable.28",
31
- "@sphereon/ssi-sdk-ext.did-utils": "0.36.1-next.119+86f2dfd2",
32
- "@sphereon/ssi-sdk-ext.identifier-resolution": "0.36.1-next.119+86f2dfd2",
33
- "@sphereon/ssi-sdk.agent-config": "0.36.1-next.119+86f2dfd2",
34
- "@sphereon/ssi-sdk.core": "0.36.1-next.119+86f2dfd2",
35
- "@sphereon/ssi-sdk.data-store-types": "0.36.1-next.119+86f2dfd2",
36
- "@sphereon/ssi-types": "0.36.1-next.119+86f2dfd2",
31
+ "@sphereon/ssi-sdk-ext.did-utils": "0.36.1-next.149+abd25c87",
32
+ "@sphereon/ssi-sdk-ext.identifier-resolution": "0.36.1-next.149+abd25c87",
33
+ "@sphereon/ssi-sdk.agent-config": "0.36.1-next.149+abd25c87",
34
+ "@sphereon/ssi-sdk.core": "0.36.1-next.149+abd25c87",
35
+ "@sphereon/ssi-sdk.data-store-types": "0.36.1-next.149+abd25c87",
36
+ "@sphereon/ssi-types": "0.36.1-next.149+abd25c87",
37
37
  "@veramo/core": "4.2.0",
38
+ "@veramo/data-store": "4.2.0",
38
39
  "@veramo/utils": "4.2.0",
39
40
  "blakejs": "^1.2.1",
40
41
  "class-validator": "0.14.1",
@@ -66,5 +67,5 @@
66
67
  "PostgreSQL",
67
68
  "Contact Store"
68
69
  ],
69
- "gitHead": "86f2dfd2bac43df28073b4e079a83b85f92c3157"
70
+ "gitHead": "abd25c87e0155d6b89bff27ac74e721571f83a2f"
70
71
  }
@@ -18,7 +18,7 @@ import { PartyEntity } from '../entities/contact/PartyEntity'
18
18
  import { PartyRelationshipEntity } from '../entities/contact/PartyRelationshipEntity'
19
19
  import { PartyTypeEntity } from '../entities/contact/PartyTypeEntity'
20
20
  import { PhysicalAddressEntity } from '../entities/contact/PhysicalAddressEntity'
21
- import { contactMetadataItemEntityFrom, DataStoreContactEntities, DataStoreMigrations, identityMetadataItemEntityFrom, partyTypeFrom } from '../index'
21
+ import { contactMetadataItemEntityFrom, DataStoreEntitiesWithVeramo, DataStoreMigrationsWithVeramo, identityMetadataItemEntityFrom, partyTypeFrom } from '../index'
22
22
  import {
23
23
  IdentityOrigin,
24
24
  MetadataTypes,
@@ -64,9 +64,9 @@ describe('Database entities tests', (): void => {
64
64
  database: ':memory:',
65
65
  logging: ['info'],
66
66
  migrationsRun: false,
67
- migrations: DataStoreMigrations,
67
+ migrations: DataStoreMigrationsWithVeramo,
68
68
  synchronize: false,
69
- entities: DataStoreContactEntities,
69
+ entities: DataStoreEntitiesWithVeramo,
70
70
  }).initialize()
71
71
  await dbConnection.runMigrations()
72
72
  expect(await dbConnection.showMigrations()).toBeFalsy()
@@ -31,7 +31,7 @@ import { CredentialRole } from '@sphereon/ssi-types'
31
31
  import { DataSource } from 'typeorm'
32
32
  import { afterEach, beforeEach, describe, expect, it } from 'vitest'
33
33
  import { ContactStore } from '../contact/ContactStore'
34
- import { DataStoreContactEntities, DataStoreMigrations } from '../index'
34
+ import { DataStoreEntitiesWithVeramo, DataStoreMigrationsWithVeramo } from '../index'
35
35
 
36
36
  describe('Contact store tests', (): void => {
37
37
  let dbConnection: DataSource
@@ -44,9 +44,9 @@ describe('Contact store tests', (): void => {
44
44
  database: ':memory:',
45
45
  logging: ['info'],
46
46
  migrationsRun: false,
47
- migrations: DataStoreMigrations,
47
+ migrations: DataStoreMigrationsWithVeramo,
48
48
  synchronize: false,
49
- entities: DataStoreContactEntities,
49
+ entities: DataStoreEntitiesWithVeramo,
50
50
  }).initialize()
51
51
  await dbConnection.runMigrations()
52
52
  expect(await dbConnection.showMigrations()).toBeFalsy()
@@ -6,7 +6,7 @@ import {
6
6
  credentialBrandingEntityFrom,
7
7
  CredentialLocaleBrandingEntity,
8
8
  credentialLocaleBrandingEntityFrom,
9
- DataStoreIssuanceBrandingEntities,
9
+ DataStoreIssuanceBrandingEntities, DataStoreEntitiesWithVeramo,
10
10
  IBasicCredentialBranding,
11
11
  IBasicCredentialLocaleBranding,
12
12
  IBasicIssuerBranding,
@@ -16,7 +16,7 @@ import {
16
16
  IssuerLocaleBrandingEntity,
17
17
  issuerLocaleBrandingEntityFrom,
18
18
  } from '../index'
19
- import { DataStoreMigrations } from '../migrations'
19
+ import { DataStoreMigrationsWithVeramo } from '../migrations'
20
20
 
21
21
  describe('Database entities tests', (): void => {
22
22
  let dbConnection: DataSource
@@ -28,9 +28,9 @@ describe('Database entities tests', (): void => {
28
28
  database: ':memory:',
29
29
  //logging: ['info'],
30
30
  migrationsRun: false,
31
- migrations: DataStoreMigrations,
31
+ migrations: DataStoreMigrationsWithVeramo,
32
32
  synchronize: false,
33
- entities: DataStoreIssuanceBrandingEntities,
33
+ entities: DataStoreEntitiesWithVeramo,
34
34
  }).initialize()
35
35
  await dbConnection.runMigrations()
36
36
  expect(await dbConnection.showMigrations()).toBeFalsy()
@@ -823,7 +823,7 @@ describe('Database entities tests', (): void => {
823
823
  }
824
824
 
825
825
  const issuerLocaleBrandingEntity: IssuerLocaleBrandingEntity = issuerLocaleBrandingEntityFrom(localeBranding)
826
- const fromDb: IssuerLocaleBrandingEntity = await dbConnection.getRepository(CredentialLocaleBrandingEntity).save(issuerLocaleBrandingEntity)
826
+ const fromDb: IssuerLocaleBrandingEntity = await dbConnection.getRepository(IssuerLocaleBrandingEntity).save(issuerLocaleBrandingEntity)
827
827
 
828
828
  expect(fromDb).toBeDefined()
829
829
  expect(fromDb?.alias).toEqual(localeBranding.alias)
@@ -4,7 +4,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
4
4
  import {
5
5
  BackgroundAttributesEntity,
6
6
  CredentialLocaleBrandingEntity,
7
- DataStoreIssuanceBrandingEntities,
7
+ DataStoreIssuanceBrandingEntities, DataStoreEntitiesWithVeramo,
8
8
  IAddCredentialLocaleBrandingArgs,
9
9
  IAddIssuerLocaleBrandingArgs,
10
10
  IBasicCredentialBranding,
@@ -26,7 +26,7 @@ import {
26
26
  TextAttributesEntity,
27
27
  } from '../index'
28
28
  import { IssuanceBrandingStore } from '../issuanceBranding/IssuanceBrandingStore'
29
- import { DataStoreMigrations } from '../migrations'
29
+ import { DataStoreMigrationsWithVeramo } from '../migrations'
30
30
 
31
31
  describe('Issuance branding store tests', (): void => {
32
32
  let dbConnection: DataSource
@@ -39,9 +39,9 @@ describe('Issuance branding store tests', (): void => {
39
39
  database: ':memory:',
40
40
  //logging: ['info'],
41
41
  migrationsRun: false,
42
- migrations: DataStoreMigrations,
42
+ migrations: DataStoreMigrationsWithVeramo,
43
43
  synchronize: false,
44
- entities: DataStoreIssuanceBrandingEntities,
44
+ entities: DataStoreEntitiesWithVeramo,
45
45
  }).initialize()
46
46
  await dbConnection.runMigrations()
47
47
  expect(await dbConnection.showMigrations()).toBeFalsy()
@@ -1884,4 +1884,314 @@ describe('Issuance branding store tests', (): void => {
1884
1884
  expect(result?.localeBranding[0].background!.image!.alt).toBeUndefined()
1885
1885
  expect(result?.localeBranding[0].text!.color).toBeUndefined()
1886
1886
  })
1887
+
1888
+ // State-related tests
1889
+
1890
+ it('should populate state field when adding credential branding', async (): Promise<void> => {
1891
+ const credentialBranding: IBasicCredentialBranding = {
1892
+ issuerCorrelationId: 'issuerCorrelationId',
1893
+ vcHash: 'vcHash',
1894
+ localeBranding: [
1895
+ {
1896
+ alias: 'credentialTypeAlias',
1897
+ locale: 'en-US',
1898
+ },
1899
+ ],
1900
+ }
1901
+
1902
+ const result: ICredentialBranding = await issuanceBrandingStore.addCredentialBranding(credentialBranding)
1903
+
1904
+ expect(result).toBeDefined()
1905
+ expect(result.state).toBeDefined()
1906
+ expect(typeof result.state).toBe('string')
1907
+ expect(result.state.length).toBeGreaterThan(0)
1908
+ expect(result.localeBranding[0].state).toBeDefined()
1909
+ expect(typeof result.localeBranding[0].state).toBe('string')
1910
+ expect(result.localeBranding[0].state.length).toBeGreaterThan(0)
1911
+ })
1912
+
1913
+ it('should generate different states for different credential brandings', async (): Promise<void> => {
1914
+ const credentialBranding1: IBasicCredentialBranding = {
1915
+ issuerCorrelationId: 'issuerCorrelationId1',
1916
+ vcHash: 'vcHash1',
1917
+ localeBranding: [
1918
+ {
1919
+ alias: 'credentialTypeAlias1',
1920
+ locale: 'en-US',
1921
+ },
1922
+ ],
1923
+ }
1924
+
1925
+ const credentialBranding2: IBasicCredentialBranding = {
1926
+ issuerCorrelationId: 'issuerCorrelationId2',
1927
+ vcHash: 'vcHash2',
1928
+ localeBranding: [
1929
+ {
1930
+ alias: 'credentialTypeAlias2',
1931
+ locale: 'en-US',
1932
+ },
1933
+ ],
1934
+ }
1935
+
1936
+ const result1: ICredentialBranding = await issuanceBrandingStore.addCredentialBranding(credentialBranding1)
1937
+ const result2: ICredentialBranding = await issuanceBrandingStore.addCredentialBranding(credentialBranding2)
1938
+
1939
+ expect(result1.state).toBeDefined()
1940
+ expect(result2.state).toBeDefined()
1941
+ expect(result1.state).not.toEqual(result2.state)
1942
+ expect(result1.localeBranding[0].state).not.toEqual(result2.localeBranding[0].state)
1943
+ })
1944
+
1945
+ it('should keep same state when credential branding content does not change', async (): Promise<void> => {
1946
+ const credentialBranding: IBasicCredentialBranding = {
1947
+ issuerCorrelationId: 'issuerCorrelationId',
1948
+ vcHash: 'vcHash',
1949
+ localeBranding: [
1950
+ {
1951
+ alias: 'credentialTypeAlias',
1952
+ locale: 'en-US',
1953
+ description: 'Description',
1954
+ },
1955
+ ],
1956
+ }
1957
+
1958
+ const original: ICredentialBranding = await issuanceBrandingStore.addCredentialBranding(credentialBranding)
1959
+ const originalState: string = original.state
1960
+ const originalLocaleState: string = original.localeBranding[0].state
1961
+
1962
+ const retrieved: Array<ICredentialBranding> = await issuanceBrandingStore.getCredentialBranding({ filter: [{ id: original.id }] })
1963
+
1964
+ expect(retrieved).toBeDefined()
1965
+ expect(retrieved.length).toEqual(1)
1966
+ expect(retrieved[0].state).toEqual(originalState)
1967
+ expect(retrieved[0].localeBranding[0].state).toEqual(originalLocaleState)
1968
+ })
1969
+
1970
+ it('should filter credential branding by knownStates when all states match', async (): Promise<void> => {
1971
+ const credentialBranding: IBasicCredentialBranding = {
1972
+ issuerCorrelationId: 'issuerCorrelationId',
1973
+ vcHash: 'vcHash',
1974
+ localeBranding: [
1975
+ {
1976
+ alias: 'credentialTypeAlias',
1977
+ locale: 'en-US',
1978
+ },
1979
+ ],
1980
+ }
1981
+
1982
+ const saved: ICredentialBranding = await issuanceBrandingStore.addCredentialBranding(credentialBranding)
1983
+ const knownStates: Record<string, string> = {
1984
+ [saved.id]: saved.state,
1985
+ }
1986
+
1987
+ const result: Array<ICredentialBranding> = await issuanceBrandingStore.getCredentialBranding({ knownStates })
1988
+
1989
+ expect(result.length).toEqual(0)
1990
+ })
1991
+
1992
+ it('should return credential branding when knownStates differs', async (): Promise<void> => {
1993
+ const credentialBranding: IBasicCredentialBranding = {
1994
+ issuerCorrelationId: 'issuerCorrelationId',
1995
+ vcHash: 'vcHash',
1996
+ localeBranding: [
1997
+ {
1998
+ alias: 'credentialTypeAlias',
1999
+ locale: 'en-US',
2000
+ },
2001
+ ],
2002
+ }
2003
+
2004
+ const saved: ICredentialBranding = await issuanceBrandingStore.addCredentialBranding(credentialBranding)
2005
+ const knownStates: Record<string, string> = {
2006
+ [saved.id]: 'differentState',
2007
+ }
2008
+
2009
+ const result: Array<ICredentialBranding> = await issuanceBrandingStore.getCredentialBranding({ knownStates })
2010
+
2011
+ expect(result.length).toEqual(1)
2012
+ expect(result[0].id).toEqual(saved.id)
2013
+ })
2014
+
2015
+ it('should return credential branding when not in knownStates map', async (): Promise<void> => {
2016
+ const credentialBranding: IBasicCredentialBranding = {
2017
+ issuerCorrelationId: 'issuerCorrelationId',
2018
+ vcHash: 'vcHash',
2019
+ localeBranding: [
2020
+ {
2021
+ alias: 'credentialTypeAlias',
2022
+ locale: 'en-US',
2023
+ },
2024
+ ],
2025
+ }
2026
+
2027
+ const saved: ICredentialBranding = await issuanceBrandingStore.addCredentialBranding(credentialBranding)
2028
+ const knownStates: Record<string, string> = {}
2029
+
2030
+ const result: Array<ICredentialBranding> = await issuanceBrandingStore.getCredentialBranding({ knownStates })
2031
+
2032
+ expect(result.length).toEqual(1)
2033
+ expect(result[0].id).toEqual(saved.id)
2034
+ })
2035
+
2036
+ it('should filter multiple credential brandings correctly with knownStates', async (): Promise<void> => {
2037
+ const credentialBranding1: IBasicCredentialBranding = {
2038
+ issuerCorrelationId: 'issuerCorrelationId1',
2039
+ vcHash: 'vcHash1',
2040
+ localeBranding: [
2041
+ {
2042
+ alias: 'credentialTypeAlias1',
2043
+ locale: 'en-US',
2044
+ },
2045
+ ],
2046
+ }
2047
+
2048
+ const credentialBranding2: IBasicCredentialBranding = {
2049
+ issuerCorrelationId: 'issuerCorrelationId2',
2050
+ vcHash: 'vcHash2',
2051
+ localeBranding: [
2052
+ {
2053
+ alias: 'credentialTypeAlias2',
2054
+ locale: 'en-US',
2055
+ },
2056
+ ],
2057
+ }
2058
+
2059
+ const credentialBranding3: IBasicCredentialBranding = {
2060
+ issuerCorrelationId: 'issuerCorrelationId3',
2061
+ vcHash: 'vcHash3',
2062
+ localeBranding: [
2063
+ {
2064
+ alias: 'credentialTypeAlias3',
2065
+ locale: 'en-US',
2066
+ },
2067
+ ],
2068
+ }
2069
+
2070
+ const saved1: ICredentialBranding = await issuanceBrandingStore.addCredentialBranding(credentialBranding1)
2071
+ const saved2: ICredentialBranding = await issuanceBrandingStore.addCredentialBranding(credentialBranding2)
2072
+ const saved3: ICredentialBranding = await issuanceBrandingStore.addCredentialBranding(credentialBranding3)
2073
+
2074
+ const knownStates: Record<string, string> = {
2075
+ [saved1.id]: saved1.state,
2076
+ [saved2.id]: 'differentState',
2077
+ }
2078
+
2079
+ const result: Array<ICredentialBranding> = await issuanceBrandingStore.getCredentialBranding({ knownStates })
2080
+
2081
+ expect(result.length).toEqual(2)
2082
+ const resultIds: Array<string> = result.map((r: ICredentialBranding) => r.id)
2083
+ expect(resultIds).toContain(saved2.id)
2084
+ expect(resultIds).toContain(saved3.id)
2085
+ expect(resultIds).not.toContain(saved1.id)
2086
+ })
2087
+
2088
+ it('should add new locale branding with state field populated', async (): Promise<void> => {
2089
+ const credentialBranding: IBasicCredentialBranding = {
2090
+ issuerCorrelationId: 'issuerCorrelationId',
2091
+ vcHash: 'vcHash',
2092
+ localeBranding: [
2093
+ {
2094
+ alias: 'credentialTypeAlias',
2095
+ locale: 'en-US',
2096
+ },
2097
+ ],
2098
+ }
2099
+
2100
+ const saved: ICredentialBranding = await issuanceBrandingStore.addCredentialBranding(credentialBranding)
2101
+
2102
+ const addLocaleBrandingArgs: IAddCredentialLocaleBrandingArgs = {
2103
+ credentialBrandingId: saved.id,
2104
+ localeBranding: [
2105
+ {
2106
+ alias: 'credentialTypeAlias',
2107
+ locale: 'en-GB',
2108
+ },
2109
+ ],
2110
+ }
2111
+
2112
+ const result: ICredentialBranding = await issuanceBrandingStore.addCredentialLocaleBranding(addLocaleBrandingArgs)
2113
+
2114
+ expect(result.localeBranding.length).toEqual(2)
2115
+ expect(result.localeBranding[0].state).toBeDefined()
2116
+ expect(result.localeBranding[1].state).toBeDefined()
2117
+ expect(result.localeBranding[0].state).not.toEqual(result.localeBranding[1].state)
2118
+ })
2119
+
2120
+ it('should compute state based on locale branding claims', async (): Promise<void> => {
2121
+ const credentialBranding1: IBasicCredentialBranding = {
2122
+ issuerCorrelationId: 'issuerCorrelationId',
2123
+ vcHash: 'vcHash1',
2124
+ localeBranding: [
2125
+ {
2126
+ alias: 'credentialTypeAlias',
2127
+ locale: 'en-US',
2128
+ claims: [
2129
+ {
2130
+ key: 'name',
2131
+ name: 'Full Name',
2132
+ },
2133
+ ],
2134
+ },
2135
+ ],
2136
+ }
2137
+
2138
+ const credentialBranding2: IBasicCredentialBranding = {
2139
+ issuerCorrelationId: 'issuerCorrelationId',
2140
+ vcHash: 'vcHash2',
2141
+ localeBranding: [
2142
+ {
2143
+ alias: 'credentialTypeAlias',
2144
+ locale: 'en-US',
2145
+ claims: [
2146
+ {
2147
+ key: 'email',
2148
+ name: 'Email Address',
2149
+ },
2150
+ ],
2151
+ },
2152
+ ],
2153
+ }
2154
+
2155
+ const saved1: ICredentialBranding = await issuanceBrandingStore.addCredentialBranding(credentialBranding1)
2156
+ const saved2: ICredentialBranding = await issuanceBrandingStore.addCredentialBranding(credentialBranding2)
2157
+
2158
+ expect(saved1.localeBranding[0].state).toBeDefined()
2159
+ expect(saved2.localeBranding[0].state).toBeDefined()
2160
+ expect(saved1.localeBranding[0].state).not.toEqual(saved2.localeBranding[0].state)
2161
+ })
2162
+
2163
+ it('should compute deterministic state for same content', async (): Promise<void> => {
2164
+ const credentialBranding1: IBasicCredentialBranding = {
2165
+ issuerCorrelationId: 'issuerCorrelationId',
2166
+ vcHash: 'vcHash',
2167
+ localeBranding: [
2168
+ {
2169
+ alias: 'credentialTypeAlias',
2170
+ locale: 'en-US',
2171
+ description: 'Test description',
2172
+ },
2173
+ ],
2174
+ }
2175
+
2176
+ const saved1: ICredentialBranding = await issuanceBrandingStore.addCredentialBranding(credentialBranding1)
2177
+
2178
+ await issuanceBrandingStore.removeCredentialBranding({ filter: [{ id: saved1.id }] })
2179
+
2180
+ const credentialBranding2: IBasicCredentialBranding = {
2181
+ issuerCorrelationId: 'issuerCorrelationId',
2182
+ vcHash: 'vcHash',
2183
+ localeBranding: [
2184
+ {
2185
+ alias: 'credentialTypeAlias',
2186
+ locale: 'en-US',
2187
+ description: 'Test description',
2188
+ },
2189
+ ],
2190
+ }
2191
+
2192
+ const saved2: ICredentialBranding = await issuanceBrandingStore.addCredentialBranding(credentialBranding2)
2193
+
2194
+ expect(saved1.state).toEqual(saved2.state)
2195
+ expect(saved1.localeBranding[0].state).toEqual(saved2.localeBranding[0].state)
2196
+ })
1887
2197
  })
@@ -109,9 +109,21 @@ export class ContactStore extends AbstractContactStore {
109
109
  const initialResult = await partyRepository.find({ select: ['id'], where: filterConditions })
110
110
 
111
111
  // Fetch the complete entities based on the initial result IDs
112
- const result = await partyRepository.find({ where: { id: In(initialResult.map((party) => party.id)) } })
112
+ const result = await partyRepository.find({
113
+ where: { id: In(initialResult.map((party) => party.id)) },
114
+ relations: ['contact'], // Explicit load prevents eager loading issues
115
+ })
113
116
  debug(`getParties() resulted in ${result.length} parties`)
114
- return result.map(partyFrom)
117
+ return result
118
+ .filter((party) => {
119
+ // Do not crash fetching the entire contacts list over one missing contact relation
120
+ if (!party.contact) {
121
+ console.warn(`party ${party.id} does not have an associated contact`)
122
+ return false
123
+ }
124
+ return true
125
+ })
126
+ .map(partyFrom)
115
127
  }
116
128
 
117
129
  addParty = async (args: AddPartyArgs): Promise<Party> => {
@@ -237,10 +249,45 @@ export class ContactStore extends AbstractContactStore {
237
249
  }
238
250
  }
239
251
 
240
- const identityEntity: IdentityEntity = identityEntityFrom(identity)
252
+ const identityRepository = (await this.dbConnection).getRepository(IdentityEntity)
253
+ const correlationIdentifierRepository = (await this.dbConnection).getRepository(CorrelationIdentifierEntity)
254
+
255
+ // First check if an identity with the same correlationId already exists
256
+ const existingCorrelationIdentifier = await correlationIdentifierRepository.findOne({
257
+ where: { correlationId: identity.identifier.correlationId },
258
+ })
259
+
260
+ if (existingCorrelationIdentifier) {
261
+ // The same identifier already exists, return the existing identity
262
+ const existingIdentity = await identityRepository.findOne({
263
+ where: { identifier: { id: existingCorrelationIdentifier.id } },
264
+ })
265
+ if (existingIdentity) {
266
+ debug('Identity with same correlationId already exists, returning existing identity', identity.identifier.correlationId)
267
+ return identityFrom(existingIdentity)
268
+ }
269
+ }
270
+
271
+ // Check if an identity with the same alias exists (but different correlationId)
272
+ const existingAlias = await identityRepository.findOne({
273
+ where: { alias: identity.alias },
274
+ })
275
+
276
+ let uniqueAlias = identity.alias
277
+ if (existingAlias) {
278
+ // Generate a unique alias by appending a counter
279
+ let counter = 1
280
+ while (await identityRepository.findOne({ where: { alias: `${identity.alias}_${counter}` } })) {
281
+ counter++
282
+ }
283
+ uniqueAlias = `${identity.alias}_${counter}`
284
+ debug('Alias collision detected, using unique alias', { original: identity.alias, unique: uniqueAlias })
285
+ }
286
+
287
+ const identityEntity: IdentityEntity = identityEntityFrom({ ...identity, alias: uniqueAlias })
241
288
  identityEntity.party = party
242
289
  debug('Adding identity', identity)
243
- const result: IdentityEntity = await (await this.dbConnection).getRepository(IdentityEntity).save(identityEntity, {
290
+ const result: IdentityEntity = await identityRepository.save(identityEntity, {
244
291
  transaction: true,
245
292
  })
246
293
 
@@ -12,7 +12,8 @@ import {
12
12
  PrimaryGeneratedColumn,
13
13
  UpdateDateColumn,
14
14
  } from 'typeorm'
15
- import { CredentialLocaleBrandingEntity } from './CredentialLocaleBrandingEntity'
15
+ import { CredentialLocaleBrandingEntity, computeCredentialLocaleBrandingState } from './CredentialLocaleBrandingEntity'
16
+ import { computeCompactHash } from '../../utils/issuanceBranding/HashUtils'
16
17
 
17
18
  @Entity('CredentialBranding')
18
19
  @Index('IDX_CredentialBrandingEntity_vcHash', ['vcHash'])
@@ -29,6 +30,9 @@ export class CredentialBrandingEntity extends BaseEntity {
29
30
  @IsNotEmpty({ message: 'Blank issuerCorrelationIds are not allowed' })
30
31
  issuerCorrelationId!: string
31
32
 
33
+ @Column('varchar', { name: 'state', length: 255, nullable: false })
34
+ state!: string
35
+
32
36
  @OneToMany(
33
37
  () => CredentialLocaleBrandingEntity,
34
38
  (credentialLocaleBrandingEntity: CredentialLocaleBrandingEntity) => credentialLocaleBrandingEntity.credentialBranding,
@@ -55,6 +59,14 @@ export class CredentialBrandingEntity extends BaseEntity {
55
59
  this.lastUpdatedAt = new Date()
56
60
  }
57
61
 
62
+ @BeforeInsert()
63
+ @BeforeUpdate()
64
+ setState(): void {
65
+ if (this.localeBranding && Array.isArray(this.localeBranding)) {
66
+ this.state = this.computeState()
67
+ }
68
+ }
69
+
58
70
  @BeforeInsert()
59
71
  @BeforeUpdate()
60
72
  async validate(): Promise<undefined> {
@@ -64,4 +76,35 @@ export class CredentialBrandingEntity extends BaseEntity {
64
76
  }
65
77
  return
66
78
  }
79
+
80
+ private computeState(): string {
81
+ const localeStates: Array<{ locale: string; alias: string; id: string; state: string }> = (this.localeBranding ?? []).map(
82
+ (localeBranding: CredentialLocaleBrandingEntity) => ({
83
+ locale: localeBranding.locale ?? '',
84
+ alias: localeBranding.alias ?? '',
85
+ id: localeBranding.id ?? '',
86
+ state: computeCredentialLocaleBrandingState(localeBranding),
87
+ }),
88
+ )
89
+
90
+ localeStates.sort((first, second) => {
91
+ const localeCompare: number = first.locale.localeCompare(second.locale)
92
+ if (localeCompare !== 0) {
93
+ return localeCompare
94
+ }
95
+ const aliasCompare: number = first.alias.localeCompare(second.alias)
96
+ if (aliasCompare !== 0) {
97
+ return aliasCompare
98
+ }
99
+ return first.id.localeCompare(second.id)
100
+ })
101
+
102
+ const payload = {
103
+ issuerCorrelationId: this.issuerCorrelationId,
104
+ vcHash: this.vcHash,
105
+ localeBranding: localeStates.map((entry) => entry.state),
106
+ }
107
+
108
+ return computeCompactHash(JSON.stringify(payload))
109
+ }
67
110
  }
@@ -1,4 +1,5 @@
1
- import { ChildEntity, Column, Index, JoinColumn, ManyToOne, OneToMany } from 'typeorm'
1
+ import { BeforeInsert, BeforeUpdate, ChildEntity, Column, Index, JoinColumn, ManyToOne, OneToMany } from 'typeorm'
2
+ import { computeCompactHash } from '../../utils/issuanceBranding/HashUtils'
2
3
  import { BaseLocaleBrandingEntity } from './BaseLocaleBrandingEntity'
3
4
  import { CredentialBrandingEntity } from './CredentialBrandingEntity'
4
5
  import { CredentialClaimsEntity } from './CredentialClaimsEntity'
@@ -23,4 +24,66 @@ export class CredentialLocaleBrandingEntity extends BaseLocaleBrandingEntity {
23
24
 
24
25
  @Column('uuid', { name: 'credentialBrandingId', nullable: false })
25
26
  credentialBrandingId!: string
27
+
28
+ @Column('varchar', { name: 'state', length: 255, nullable: false })
29
+ state!: string
30
+
31
+ @BeforeInsert()
32
+ @BeforeUpdate()
33
+ setState(): void {
34
+ this.state = computeCredentialLocaleBrandingState(this)
35
+ }
36
+ }
37
+
38
+ export const computeCredentialLocaleBrandingState = (localeBranding: CredentialLocaleBrandingEntity): string => {
39
+ const sortedClaims: Array<{ key: string; name: string }> = (localeBranding.claims ?? [])
40
+ .map((claim: CredentialClaimsEntity) => ({ key: claim.key, name: claim.name }))
41
+ .sort((first: { key: string }, second: { key: string }) => first.key.localeCompare(second.key))
42
+
43
+ const payload = {
44
+ alias: localeBranding.alias ?? null,
45
+ locale: localeBranding.locale ?? null,
46
+ description: localeBranding.description ?? null,
47
+ logo: localeBranding.logo
48
+ ? {
49
+ uri: localeBranding.logo.uri ?? null,
50
+ dataUri: localeBranding.logo.dataUri ?? null,
51
+ mediaType: localeBranding.logo.mediaType ?? null,
52
+ alt: localeBranding.logo.alt ?? null,
53
+ dimensions: localeBranding.logo.dimensions
54
+ ? {
55
+ width: localeBranding.logo.dimensions.width,
56
+ height: localeBranding.logo.dimensions.height,
57
+ }
58
+ : null,
59
+ }
60
+ : null,
61
+ background: localeBranding.background
62
+ ? {
63
+ color: localeBranding.background.color ?? null,
64
+ image: localeBranding.background.image
65
+ ? {
66
+ uri: localeBranding.background.image.uri ?? null,
67
+ dataUri: localeBranding.background.image.dataUri ?? null,
68
+ mediaType: localeBranding.background.image.mediaType ?? null,
69
+ alt: localeBranding.background.image.alt ?? null,
70
+ dimensions: localeBranding.background.image.dimensions
71
+ ? {
72
+ width: localeBranding.background.image.dimensions.width,
73
+ height: localeBranding.background.image.dimensions.height,
74
+ }
75
+ : null,
76
+ }
77
+ : null,
78
+ }
79
+ : null,
80
+ text: localeBranding.text
81
+ ? {
82
+ color: localeBranding.text.color ?? null,
83
+ }
84
+ : null,
85
+ claims: sortedClaims,
86
+ }
87
+
88
+ return computeCompactHash(JSON.stringify(payload))
26
89
  }