Skip to content

Commit

Permalink
feat: added pagination to digital credential store
Browse files Browse the repository at this point in the history
  • Loading branch information
sksadjad committed Feb 28, 2024
1 parent f39664a commit ecefdcf
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 21 deletions.
46 changes: 35 additions & 11 deletions packages/data-store/src/__tests__/digitalCredential.store.test.ts
Original file line number Diff line number Diff line change
@@ -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 => {
Expand Down Expand Up @@ -68,11 +72,11 @@ describe('Database entities tests', (): void => {
const digitalCredential2: DigitalCredential = await digitalCredentialStore.addDigitalCredential(addCredentialArgs2)
expect(digitalCredential2).toBeDefined()

const result: Array<DigitalCredential> = 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<void> => {
it('should get digital credentials by filters and pagination', async (): Promise<void> => {
const addCredentialArgs1: AddDigitalCredentialArgs = {
raw: 'eyJraWQiOiJkaWQ6a2V5Ono2TWtyaGt5M3B1c20yNk1laUZhWFUzbjJuZWtyYW13RlVtZ0dyZUdHa0RWNnpRaiN6Nk1rcmhreTNwdXNtMjZNZWlGYVhVM24ybmVrcmFtd0ZVbWdHcmVHR2tEVjZ6UWoiLCJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vc3BoZXJlb24tb3BlbnNvdXJjZS5naXRodWIuaW8vc3NpLW1vYmlsZS13YWxsZXQvY29udGV4dC9zcGhlcmVvbi13YWxsZXQtaWRlbnRpdHktdjEuanNvbmxkIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJTcGhlcmVvbldhbGxldElkZW50aXR5Q3JlZGVudGlhbCJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJmaXJzdE5hbWUiOiJTIiwibGFzdE5hbWUiOiJLIiwiZW1haWxBZGRyZXNzIjoic0BrIn19LCJzdWIiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJqdGkiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJuYmYiOjE3MDg0NDA4MDgsImlzcyI6ImRpZDprZXk6ejZNa3Joa3kzcHVzbTI2TWVpRmFYVTNuMm5la3JhbXdGVW1nR3JlR0drRFY2elFqIn0.G0M84XVAxSmzGY-NQuB9NBofNrINSn6lvxW6761Vlq6ypvYgtc2xNdpiRmw8ryVNfnpzrr4Z5cB1RlrC05rJAw',
issuerCorrelationType: CredentialCorrelationType.DID,
Expand Down Expand Up @@ -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<DigitalCredentialEntity> = 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<void> => {
const args: GetDigitalCredentialsArgs = {
filter: [{ issuerCorrelationId: 'unknown_id' }],
}
const result: Array<DigitalCredentialEntity> = 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<void> => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import {
AddDigitalCredentialArgs,
GetDigitalCredentialArgs,
GetDigitalCredentialsArgs,
GetDigitalCredentialsResponse,
RemoveDigitalCredentialArgs,
UpdateDigitalCredentialStateArgs,
} from '../types/digitalCredential/IAbstractDigitalCredentialStore'
import { DigitalCredentialEntity } from '../entities/digitalCredential/DigitalCredentialEntity'

export abstract class AbstractDigitalCredentialStore {
abstract getDigitalCredential(args: GetDigitalCredentialArgs): Promise<DigitalCredentialEntity>
abstract getDigitalCredentials(args?: GetDigitalCredentialsArgs): Promise<Array<DigitalCredentialEntity>>
abstract getDigitalCredentials(args?: GetDigitalCredentialsArgs): Promise<GetDigitalCredentialsResponse>
abstract addDigitalCredential(args: AddDigitalCredentialArgs): Promise<DigitalCredentialEntity>
abstract updateDigitalCredentialState(args: UpdateDigitalCredentialStateArgs): Promise<DigitalCredentialEntity>
abstract removeDigitalCredential(args: RemoveDigitalCredentialArgs): Promise<boolean>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
AddDigitalCredentialArgs,
GetDigitalCredentialArgs,
GetDigitalCredentialsArgs,
GetDigitalCredentialsResponse,
RemoveDigitalCredentialArgs,
UpdateDigitalCredentialStateArgs,
} from '../types/digitalCredential/IAbstractDigitalCredentialStore'
Expand Down Expand Up @@ -43,14 +44,22 @@ export class DigitalCredentialStore extends AbstractDigitalCredentialStore {
return result
}

getDigitalCredentials = async (args?: GetDigitalCredentialsArgs): Promise<Array<DigitalCredentialEntity>> => {
const result: Array<DigitalCredentialEntity> = await (await this.dbConnection).getRepository(DigitalCredentialEntity).find({
...(args?.filter && { where: args?.filter }),
getDigitalCredentials = async (args?: GetDigitalCredentialsArgs): Promise<GetDigitalCredentialsResponse> => {
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<boolean> => {
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,17 @@ export type GetDigitalCredentialArgs = { id: string } | { hash: string }

export type FindDigitalCredentialArgs = Array<Partial<DigitalCredential>>

// 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 = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit ecefdcf

Please sign in to comment.