diff --git a/packages/data-store/src/__tests__/digitalCredential.store.test.ts b/packages/data-store/src/__tests__/digitalCredential.store.test.ts index 406d3eb9b..6dd86256a 100644 --- a/packages/data-store/src/__tests__/digitalCredential.store.test.ts +++ b/packages/data-store/src/__tests__/digitalCredential.store.test.ts @@ -1,9 +1,13 @@ import { DataSource } from 'typeorm' import { DataStoreDigitalCredentialMigrations } from '../migrations' -import { DataStoreDigitalCredentialEntities, DigitalCredentialEntity } from '../index' +import { DataStoreDigitalCredentialEntities } from '../index' import { DigitalCredentialStore } from '../digitalCredential/DigitalCredentialStore' import { CredentialCorrelationType, CredentialStateType, CredentialType, DigitalCredential } from '../types/digitalCredential/digitalCredential' -import { AddDigitalCredentialArgs, GetDigitalCredentialsArgs } from '../types/digitalCredential/IAbstractDigitalCredentialStore' +import { + AddDigitalCredentialArgs, + GetDigitalCredentialsArgs, + GetDigitalCredentialsResponse, +} from '../types/digitalCredential/IAbstractDigitalCredentialStore' import { IVerifiablePresentation } from '@sphereon/ssi-types' describe('Database entities tests', (): void => { @@ -68,11 +72,11 @@ describe('Database entities tests', (): void => { const digitalCredential2: DigitalCredential = await digitalCredentialStore.addDigitalCredential(addCredentialArgs2) expect(digitalCredential2).toBeDefined() - const result: Array = await digitalCredentialStore.getDigitalCredentials() - expect(result.length).toEqual(2) + const result: GetDigitalCredentialsResponse = await digitalCredentialStore.getDigitalCredentials() + expect(result.total).toEqual(2) }) - it('should get digital credentials by filter', async (): Promise => { + it('should get digital credentials by filters and pagination', async (): Promise => { const addCredentialArgs1: AddDigitalCredentialArgs = { raw: 'eyJraWQiOiJkaWQ6a2V5Ono2TWtyaGt5M3B1c20yNk1laUZhWFUzbjJuZWtyYW13RlVtZ0dyZUdHa0RWNnpRaiN6Nk1rcmhreTNwdXNtMjZNZWlGYVhVM24ybmVrcmFtd0ZVbWdHcmVHR2tEVjZ6UWoiLCJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vc3BoZXJlb24tb3BlbnNvdXJjZS5naXRodWIuaW8vc3NpLW1vYmlsZS13YWxsZXQvY29udGV4dC9zcGhlcmVvbi13YWxsZXQtaWRlbnRpdHktdjEuanNvbmxkIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJTcGhlcmVvbldhbGxldElkZW50aXR5Q3JlZGVudGlhbCJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJmaXJzdE5hbWUiOiJTIiwibGFzdE5hbWUiOiJLIiwiZW1haWxBZGRyZXNzIjoic0BrIn19LCJzdWIiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJqdGkiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJuYmYiOjE3MDg0NDA4MDgsImlzcyI6ImRpZDprZXk6ejZNa3Joa3kzcHVzbTI2TWVpRmFYVTNuMm5la3JhbXdGVW1nR3JlR0drRFY2elFqIn0.G0M84XVAxSmzGY-NQuB9NBofNrINSn6lvxW6761Vlq6ypvYgtc2xNdpiRmw8ryVNfnpzrr4Z5cB1RlrC05rJAw', issuerCorrelationType: CredentialCorrelationType.DID, @@ -128,25 +132,45 @@ describe('Database entities tests', (): void => { subjectCorrelationType: CredentialCorrelationType.DID, subjectCorrelationId: 'did:example:holder', } + const addCredentialArgs3: AddDigitalCredentialArgs = { + raw: 'eyJhbGciOiJFZERTQSIsInR5cCI6InZjK3NkLWp3dCIsImtpZCI6IiN6Nk1rdHF0WE5HOENEVVk5UHJydG9TdEZ6ZUNuaHBNbWd4WUwxZ2lrY1czQnp2TlcifQ.eyJ2Y3QiOiJJZGVudGl0eUNyZWRlbnRpYWwiLCJmYW1pbHlfbmFtZSI6IkRvZSIsInBob25lX251bWJlciI6IisxLTIwMi01NTUtMDEwMSIsImFkZHJlc3MiOnsic3RyZWV0X2FkZHJlc3MiOiIxMjMgTWFpbiBTdCIsImxvY2FsaXR5IjoiQW55dG93biIsIl9zZCI6WyJOSm5tY3QwQnFCTUUxSmZCbEM2alJRVlJ1ZXZwRU9OaVl3N0E3TUh1SnlRIiwib201Wnp0WkhCLUdkMDBMRzIxQ1ZfeE00RmFFTlNvaWFPWG5UQUpOY3pCNCJdfSwiY25mIjp7Imp3ayI6eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6Im9FTlZzeE9VaUg1NFg4d0pMYVZraWNDUmswMHdCSVE0c1JnYms1NE44TW8ifX0sImlzcyI6ImRpZDprZXk6ejZNa3RxdFhORzhDRFVZOVBycnRvU3RGemVDbmhwTW1neFlMMWdpa2NXM0J6dk5XIiwiaWF0IjoxNjk4MTUxNTMyLCJfc2RfYWxnIjoic2hhLTI1NiIsIl9zZCI6WyIxQ3VyMmsyQTJvSUI1Q3NoU0lmX0FfS2ctbDI2dV9xS3VXUTc5UDBWZGFzIiwiUjF6VFV2T1lIZ2NlcGowakh5cEdIejlFSHR0VktmdDB5c3diYzlFVFBiVSIsImVEcVFwZFRYSlhiV2hmLUVzSTd6dzVYNk92WW1GTi1VWlFRTWVzWHdLUHciLCJwZERrMl9YQUtIbzdnT0Fmd0YxYjdPZENVVlRpdDJrSkhheFNFQ1E5eGZjIiwicHNhdUtVTldFaTA5bnUzQ2w4OXhLWGdtcFdFTlpsNXV5MU4xbnluX2pNayIsInNOX2dlMHBIWEY2cW1zWW5YMUE5U2R3SjhjaDhhRU5reGJPRHNUNzRZd0kiXX0.coOK8NzJmEWz4qx-qRhjo-RK7aejrSkQM9La9Cw3eWmzcja9DXrkBoQZKbIJtNoSzSPLjwK2V71W78z0miZsDQ~WyJzYWx0IiwiaXNfb3Zlcl82NSIsdHJ1ZV0~WyJzYWx0IiwiaXNfb3Zlcl8yMSIsdHJ1ZV0~WyJzYWx0IiwiZW1haWwiLCJqb2huZG9lQGV4YW1wbGUuY29tIl0~WyJzYWx0IiwiY291bnRyeSIsIlVTIl0~WyJzYWx0IiwiZ2l2ZW5fbmFtZSIsIkpvaG4iXQ~eyJhbGciOiJFZERTQSIsInR5cCI6ImtiK2p3dCJ9.eyJpYXQiOjE2OTgxNTE1MzIsIm5vbmNlIjoic2FsdCIsImF1ZCI6ImRpZDprZXk6elVDNzRWRXFxaEVIUWNndjR6YWdTUGtxRkp4dU5XdW9CUEtqSnVIRVRFVWVITG9TcVd0OTJ2aVNzbWFXank4MnkiLCJfc2RfaGFzaCI6Ii1kTUd4OGZhUnpOQm91a2EwU0R6V2JkS3JYckw1TFVmUlNQTHN2Q2xPMFkifQ.TQQLqc4ZzoKjQfAghAzC_4aaU3KCS8YqzxAJtzT124guzkv9XSHtPN8d3z181_v-ca2ATXjTRoRciozitE6wBA', + issuerCorrelationType: CredentialCorrelationType.DID, + subjectCorrelationType: CredentialCorrelationType.DID, + issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj', + tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj', + } + const savedDigitalCredential1: DigitalCredential = await digitalCredentialStore.addDigitalCredential(addCredentialArgs1) expect(savedDigitalCredential1).toBeDefined() const savedDigitalCredential2: DigitalCredential = await digitalCredentialStore.addDigitalCredential(addCredentialArgs2) expect(savedDigitalCredential2).toBeDefined() - const args: GetDigitalCredentialsArgs = { + const savedDigitalCredential3: DigitalCredential = await digitalCredentialStore.addDigitalCredential(addCredentialArgs3) + expect(savedDigitalCredential3).toBeDefined() + const args1: GetDigitalCredentialsArgs = { filter: [{ credentialType: CredentialType.VP }], } - const result: Array = await digitalCredentialStore.getDigitalCredentials(args) - - expect(result.length).toEqual(1) + const result1: GetDigitalCredentialsResponse = await digitalCredentialStore.getDigitalCredentials(args1) + expect(result1.total).toEqual(1) + expect(result1.hasMore).toEqual(true) + const args2: GetDigitalCredentialsArgs = { + skip: 1, + take: 10, + } + const result2: GetDigitalCredentialsResponse = await digitalCredentialStore.getDigitalCredentials(args2) + expect(result2.data.length).toEqual(2) + expect(result2.total).toEqual(3) + expect(result2.hasMore).toEqual(false) }) it('should return no digital credentials if filter does not match', async (): Promise => { const args: GetDigitalCredentialsArgs = { filter: [{ issuerCorrelationId: 'unknown_id' }], } - const result: Array = await digitalCredentialStore.getDigitalCredentials(args) + const result: GetDigitalCredentialsResponse = await digitalCredentialStore.getDigitalCredentials(args) - expect(result.length).toEqual(0) + expect(result.data.length).toEqual(0) + expect(result.total).toEqual(0) }) it('should delete stored digital credential', async (): Promise => { diff --git a/packages/data-store/src/digitalCredential/AbstractDigitalCredentialStore.ts b/packages/data-store/src/digitalCredential/AbstractDigitalCredentialStore.ts index dea19b3b3..c15079edf 100644 --- a/packages/data-store/src/digitalCredential/AbstractDigitalCredentialStore.ts +++ b/packages/data-store/src/digitalCredential/AbstractDigitalCredentialStore.ts @@ -2,6 +2,7 @@ import { AddDigitalCredentialArgs, GetDigitalCredentialArgs, GetDigitalCredentialsArgs, + GetDigitalCredentialsResponse, RemoveDigitalCredentialArgs, UpdateDigitalCredentialStateArgs, } from '../types/digitalCredential/IAbstractDigitalCredentialStore' @@ -9,7 +10,7 @@ import { DigitalCredentialEntity } from '../entities/digitalCredential/DigitalCr export abstract class AbstractDigitalCredentialStore { abstract getDigitalCredential(args: GetDigitalCredentialArgs): Promise - abstract getDigitalCredentials(args?: GetDigitalCredentialsArgs): Promise> + abstract getDigitalCredentials(args?: GetDigitalCredentialsArgs): Promise abstract addDigitalCredential(args: AddDigitalCredentialArgs): Promise abstract updateDigitalCredentialState(args: UpdateDigitalCredentialStateArgs): Promise abstract removeDigitalCredential(args: RemoveDigitalCredentialArgs): Promise diff --git a/packages/data-store/src/digitalCredential/DigitalCredentialStore.ts b/packages/data-store/src/digitalCredential/DigitalCredentialStore.ts index 59114e240..ad20a8107 100644 --- a/packages/data-store/src/digitalCredential/DigitalCredentialStore.ts +++ b/packages/data-store/src/digitalCredential/DigitalCredentialStore.ts @@ -3,6 +3,7 @@ import { AddDigitalCredentialArgs, GetDigitalCredentialArgs, GetDigitalCredentialsArgs, + GetDigitalCredentialsResponse, RemoveDigitalCredentialArgs, UpdateDigitalCredentialStateArgs, } from '../types/digitalCredential/IAbstractDigitalCredentialStore' @@ -43,14 +44,22 @@ export class DigitalCredentialStore extends AbstractDigitalCredentialStore { return result } - getDigitalCredentials = async (args?: GetDigitalCredentialsArgs): Promise> => { - const result: Array = await (await this.dbConnection).getRepository(DigitalCredentialEntity).find({ - ...(args?.filter && { where: args?.filter }), + getDigitalCredentials = async (args?: GetDigitalCredentialsArgs): Promise => { + const { filter = {}, skip, take, order = { createdAt: 'DESC' } } = args ?? {} + const [result, total] = await (await this.dbConnection).getRepository(DigitalCredentialEntity).findAndCount({ + where: filter, + skip, + take, + order, }) if (!result) { return Promise.reject(Error(`No credential found for arg: ${args?.toString()}`)) } - return result + return { + data: result, + total, + hasMore: total > (skip || 0) + (take || 0), + } } removeDigitalCredential = async (args: RemoveDigitalCredentialArgs): Promise => { @@ -95,7 +104,7 @@ export class DigitalCredentialStore extends AbstractDigitalCredentialStore { } const updatedCredential: DigitalCredential = { ...credential, - verificationDate: args.verificationDate ?? new Date(), + lastVerificationDate: args.verificationDate ?? new Date(), lastUpdatedAt: new Date(), ...(args.verifiedState === CredentialStateType.REVOKED && { revocationDate: args.verificationDate ?? new Date() }), verifiedState: args.verifiedState, diff --git a/packages/data-store/src/types/digitalCredential/IAbstractDigitalCredentialStore.ts b/packages/data-store/src/types/digitalCredential/IAbstractDigitalCredentialStore.ts index 771914554..9913e6b2a 100644 --- a/packages/data-store/src/types/digitalCredential/IAbstractDigitalCredentialStore.ts +++ b/packages/data-store/src/types/digitalCredential/IAbstractDigitalCredentialStore.ts @@ -4,9 +4,17 @@ export type GetDigitalCredentialArgs = { id: string } | { hash: string } export type FindDigitalCredentialArgs = Array> -// TODO: discuss about what args we want here export type GetDigitalCredentialsArgs = { filter?: FindDigitalCredentialArgs + skip?: number + take?: number + order?: { [key in keyof DigitalCredential]: 'ASC' | 'DESC' } +} + +export type GetDigitalCredentialsResponse = { + data: DigitalCredential[] + total: number + hasMore: boolean } export type AddDigitalCredentialArgs = { diff --git a/packages/data-store/src/types/digitalCredential/digitalCredential.ts b/packages/data-store/src/types/digitalCredential/digitalCredential.ts index ae2f73fa1..c8f8cfcf4 100644 --- a/packages/data-store/src/types/digitalCredential/digitalCredential.ts +++ b/packages/data-store/src/types/digitalCredential/digitalCredential.ts @@ -5,19 +5,20 @@ export type DigitalCredential = { credentialType: CredentialType documentFormat: CredentialDocumentFormat raw: string + uniformDocument: string hash: string issuerCorrelationType: CredentialCorrelationType subjectCorrelationType?: CredentialCorrelationType issuerCorrelationId: string subjectCorrelationId?: string - lastVerificationDate: string - verificationDate?: Date verifiedState?: CredentialStateType tenantId?: string createdAt: Date lastUpdatedAt: Date - issuedAt?: Date expiresAt?: Date + issuedAt?: Date + lastVerificationDate?: Date + revocationDate?: Date } export enum CredentialType {