Skip to content

Commit

Permalink
feat: added pd-manager / pd-store
Browse files Browse the repository at this point in the history
  • Loading branch information
sanderPostma committed May 28, 2024
1 parent 386bd5c commit ed77532
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,13 @@ export type PresentationDefinitionItem = {
}

export type NonPersistedPresentationDefinitionItem = Omit<PresentationDefinitionItem, 'id' | 'createdAt' | 'lastUpdatedAt'>

export type PersistablePresentationDefinitionItem = {
definitionId?: string
tenantId?: string
version?: string
purpose?: string
definitionPayload: IPresentationDefinition
}
export type PartialPresentationDefinitionItem = Partial<PresentationDefinitionItem>
export type PresentationDefinitionItemFilter = Partial<Omit<PresentationDefinitionItem, 'definitionPayload'>>
8 changes: 4 additions & 4 deletions packages/pd-manager-rest-api/src/api-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function pdReadEndpoint(router: Router, context: IRequiredContext, opts?:
router.get(`${path}/:pdId`, checkAuth(opts?.endpoint), async (request: Request, response: Response) => {
try {
const pdId = request.params.pdId
const pd = await context.agent.pdmGetDefinitionItem({ itemId: pdId })
const pd = await context.agent.pdmGetDefinition({ itemId: pdId })
response.statusCode = 200
return response.send(pd)
} catch (error) {
Expand All @@ -31,7 +31,7 @@ export function pdAddEndpoint(router: Router, context: IRequiredContext, opts?:
router.post(path, async (request: Request, response: Response) => {
try {
const addPd = request.body
const pd = await context.agent.pdmAddDefinitionItem(addPd as AddDefinitionItemArgs)
const pd = await context.agent.pdmAddDefinition(addPd as AddDefinitionItemArgs)
response.statusCode = 201
return response.send(pd)
} catch (error) {
Expand All @@ -49,7 +49,7 @@ export function pdUpdateEndpoint(router: Router, context: IRequiredContext, opts
router.put(path, async (request: Request, response: Response) => {
try {
const updatePd = request.body
const pd = await context.agent.pdmUpdateDefinitionItem(updatePd as UpdateDefinitionItemArgs)
const pd = await context.agent.pdmUpdateDefinition(updatePd as UpdateDefinitionItemArgs)
response.statusCode = 201
return response.send(pd)
} catch (error) {
Expand All @@ -67,7 +67,7 @@ export function pdDeleteEndpoint(router: Router, context: IRequiredContext, opts
router.delete(`${path}/:pdItemId`, async (request, response) => {
try {
const pdItemId = request.params.pdItemId
const result = await context.agent.pdmDeleteDefinitionItem({ itemId: pdItemId } as DeleteDefinitionArgs)
const result = await context.agent.pdmDeleteDefinition({ itemId: pdItemId } as DeleteDefinitionArgs)
return response.send(result)
} catch (error) {
return sendErrorResponse(response, 500, error.message, error)
Expand Down
20 changes: 10 additions & 10 deletions packages/pd-manager/__tests__/shared/pdManagerAgentLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,25 @@ export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Pro
},
}

defaultDefinitionItem = await agent.pdmAddDefinitionItem(definition)
defaultDefinitionItem = await agent.pdmAddDefinition(definition)
})

afterAll(testContext.tearDown)

it('should get definition item by id', async (): Promise<void> => {
const result: PresentationDefinitionItem = await agent.pdmGetDefinitionItem({ itemId: defaultDefinitionItem.id })
const result: PresentationDefinitionItem = await agent.pdmGetDefinition({ itemId: defaultDefinitionItem.id })

expect(result.id).toEqual(defaultDefinitionItem.id)
})

it('should throw error when getting definition item with unknown id', async (): Promise<void> => {
const itemId = 'unknownItemId'

await expect(agent.pdmGetDefinitionItem({ itemId })).rejects.toThrow(`No presentation definition item found for id: ${itemId}`)
await expect(agent.pdmGetDefinition({ itemId })).rejects.toThrow(`No presentation definition item found for id: ${itemId}`)
})

it('should get all definition items', async (): Promise<void> => {
const result: Array<PresentationDefinitionItem> = await agent.pdmGetDefinitionsItem({})
const result: Array<PresentationDefinitionItem> = await agent.pdmGetDefinitions({})

expect(result.length).toBeGreaterThan(0)
})
Expand All @@ -65,7 +65,7 @@ export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Pro
},
],
}
const result: Array<PresentationDefinitionItem> = await agent.pdmGetDefinitionsItem(args)
const result: Array<PresentationDefinitionItem> = await agent.pdmGetDefinitions(args)

expect(result.length).toBe(1)
})
Expand All @@ -80,14 +80,14 @@ export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Pro
},
}

const result: PresentationDefinitionItem = await agent.pdmAddDefinitionItem(definition)
const result: PresentationDefinitionItem = await agent.pdmAddDefinition(definition)

expect(result.definitionId).toEqual(definition.definitionId)
expect(result.version).toEqual(definition.version)
})

it('should update definition item by id', async (): Promise<void> => {
const result: PresentationDefinitionItem = await agent.pdmUpdateDefinitionItem({
const result: PresentationDefinitionItem = await agent.pdmUpdateDefinition({
definitionItem: {
...defaultDefinitionItem,
definitionPayload: singleDefinition,
Expand All @@ -104,19 +104,19 @@ export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Pro
id: 'unknownItemId',
}

await expect(agent.pdmUpdateDefinitionItem({ definitionItem })).rejects.toThrow(`No presentation definition item found for id: unknownItemId`)
await expect(agent.pdmUpdateDefinition({ definitionItem })).rejects.toThrow(`No presentation definition item found for id: unknownItemId`)
})

it('should delete definition item by id', async (): Promise<void> => {
const result = await agent.pdmDeleteDefinitionItem({ itemId: defaultDefinitionItem.id })
const result = await agent.pdmDeleteDefinition({ itemId: defaultDefinitionItem.id })

expect(result).toBe(true)
})

it('should throw error when deleting definition item with unknown id', async (): Promise<void> => {
const itemId = 'unknownItemId'

await expect(agent.pdmDeleteDefinitionItem({ itemId })).rejects.toThrow(`No presentation definition found with id: ${itemId}`)
await expect(agent.pdmDeleteDefinition({ itemId })).rejects.toThrow(`No presentation definition found with id: ${itemId}`)
})
})
}
99 changes: 45 additions & 54 deletions packages/pd-manager/src/agent/PDManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ import semver from 'semver/preload'

// Exposing the methods here for any REST implementation
export const pdManagerMethods: Array<string> = [
'pdmGetDefinitionItem',
'pdmGetDefinitionItems',
'pdmAddDefinitionItem',
'pdmUpdateDefinitionItem',
'pdmRemoveDefinitionItem',
'pdmGetDefinition',
'pdmGetDefinitions',
'pdmAddDefinition',
'pdmUpdateDefinition',
'pdmRemoveDefinition',
'pdmPersistDefinition',
]

Expand All @@ -32,11 +32,11 @@ export const pdManagerMethods: Array<string> = [
export class PDManager implements IAgentPlugin {
readonly schema = schema.IPDManager
readonly methods: IPDManager = {
pdmGetDefinitionItem: this.pdmGetDefinitionItem.bind(this),
pdmGetDefinitionsItem: this.pdmGetDefinitionsItems.bind(this),
pdmAddDefinitionItem: this.pdmAddDefinitionItem.bind(this),
pdmUpdateDefinitionItem: this.pdmUpdateDefinitionItem.bind(this),
pdmDeleteDefinitionItem: this.pdmDeleteDefinitionItem.bind(this),
pdmGetDefinition: this.pdmGetDefinition.bind(this),
pdmGetDefinitions: this.pdmGetDefinitions.bind(this),
pdmAddDefinition: this.pdmAddDefinition.bind(this),
pdmUpdateDefinition: this.pdmUpdateDefinition.bind(this),
pdmDeleteDefinition: this.pdmDeleteDefinition.bind(this),
pdmPersistDefinition: this.pdmPersistDefinition.bind(this),
}

Expand All @@ -46,87 +46,80 @@ export class PDManager implements IAgentPlugin {
this.store = options.store
}

/** {@inheritDoc IPDManager.pdmGetDefinitionItem} */
private async pdmGetDefinitionItem(args: GetDefinitionItemArgs): Promise<PresentationDefinitionItem> {
/** {@inheritDoc IPDManager.pdmGetDefinition} */
private async pdmGetDefinition(args: GetDefinitionItemArgs): Promise<PresentationDefinitionItem> {
const { itemId } = args
return this.store.getDefinition({ itemId })
}

/** {@inheritDoc IPDManager.pdmGetDefinitionItems} */
private async pdmGetDefinitionsItems(args: GetDefinitionsItemArgs): Promise<Array<PresentationDefinitionItem>> {
/** {@inheritDoc IPDManager.pdmGetDefinition} */
private async pdmGetDefinitions(args: GetDefinitionsItemArgs): Promise<Array<PresentationDefinitionItem>> {
const { filter } = args
return this.store.getDefinitions({ filter })
}

/** {@inheritDoc IPDManager.pdmAddDefinitionItem} */
private async pdmAddDefinitionItem(args: NonPersistedPresentationDefinitionItem): Promise<PresentationDefinitionItem> {
/** {@inheritDoc IPDManager.pdmAddDefinition} */
private async pdmAddDefinition(args: NonPersistedPresentationDefinitionItem): Promise<PresentationDefinitionItem> {
return this.store.addDefinition(args)
}

/** {@inheritDoc IPDManager.pdmUpdateDefinitionItem} */
private async pdmUpdateDefinitionItem(args: UpdateDefinitionItemArgs): Promise<PresentationDefinitionItem> {
/** {@inheritDoc IPDManager.pdmUpdateDefinition} */
private async pdmUpdateDefinition(args: UpdateDefinitionItemArgs): Promise<PresentationDefinitionItem> {
return this.store.updateDefinition(args.definitionItem)
}

/** {@inheritDoc IPDManager.pdmDeleteDefinitionItem} */
private async pdmDeleteDefinitionItem(args: DeleteDefinitionItemArgs): Promise<boolean> {
/** {@inheritDoc IPDManager.pdmDeleteDefinition} */
private async pdmDeleteDefinition(args: DeleteDefinitionItemArgs): Promise<boolean> {
return this.store.deleteDefinition(args).then((): boolean => true)
}

/** {@inheritDoc IPDManager.pdmPersistDefinition} */
private async pdmPersistDefinition(args: PersistDefinitionArgs): Promise<PresentationDefinitionItem> {
const { definition, version, tenantId, versionControlMode = 'AutoIncrementMajor' } = args
const existing = await this.store.getDefinitions({ filter: [{ definitionId: definition.id, tenantId, version }] })
const existingItem = existing.length > 0 ? existing[0] : undefined
const { definitionItem, versionControlMode = 'AutoIncrementMajor' } = args
const { version, tenantId } = definitionItem
const definitionId = definitionItem.definitionId ?? definitionItem.definitionPayload.id

const nonPersistedDefinitionItem: NonPersistedPresentationDefinitionItem = {
...definitionItem,
definitionId: definitionId,
version: definitionItem.version ?? '1',
}

const existing = await this.store.getDefinitions({ filter: [{ definitionId, tenantId, version }] })
const existingItem = existing[0]
let latestVersionItem: PresentationDefinitionItem | undefined = existingItem

if (existingItem && version) {
const latest = await this.store.getDefinitions({ filter: [{ definitionId: definition.id, tenantId }] })
latestVersionItem = latest.length > 0 ? latest[0] : existingItem
const latest = await this.store.getDefinitions({ filter: [{ definitionId, tenantId }] })
latestVersionItem = latest[0] ?? existingItem
}

const definitionItem: NonPersistedPresentationDefinitionItem = {
definitionId: definition.id,
version: version ?? '1',
tenantId: tenantId,
purpose: definition.purpose,
definitionPayload: definition,
}

const isPayloadModified = existingItem === undefined || !isPresentationDefinitionEqual(existingItem, definitionItem)
if (!isPayloadModified) {
return existingItem
}
const isPayloadModified = !existingItem || !isPresentationDefinitionEqual(existingItem, definitionItem)
if (!isPayloadModified) return existingItem

switch (versionControlMode) {
case 'Overwrite':
return this.handleOverwriteMode(existingItem, definitionItem, definition.id, version)

return this.handleOverwriteMode(existingItem, nonPersistedDefinitionItem, version)
case 'OverwriteLatest':
return this.handleOverwriteLatestMode(latestVersionItem, definitionItem, definition.id)

return this.handleOverwriteLatestMode(latestVersionItem, nonPersistedDefinitionItem)
case 'Manual':
return this.handleManualMode(existingItem, definitionItem, definition.id, tenantId, version)

return this.handleManualMode(existingItem, nonPersistedDefinitionItem, tenantId, version)
case 'AutoIncrementMajor':
return this.handleAutoIncrementMode(latestVersionItem, definitionItem, 'major')

return this.handleAutoIncrementMode(latestVersionItem, nonPersistedDefinitionItem, 'major')
case 'AutoIncrementMinor':
return this.handleAutoIncrementMode(latestVersionItem, definitionItem, 'minor')

return this.handleAutoIncrementMode(latestVersionItem, nonPersistedDefinitionItem, 'minor')
default:
throw Error(`Unknown version control mode: ${versionControlMode}`)
throw new Error(`Unknown version control mode: ${versionControlMode}`)
}
}

private async handleOverwriteMode(
existingItem: PresentationDefinitionItem | undefined,
definitionItem: NonPersistedPresentationDefinitionItem,
definitionId: string,
version: string | undefined,
): Promise<PresentationDefinitionItem> {
if (existingItem) {
existingItem.definitionId = definitionId
existingItem.definitionId = definitionItem.definitionId
existingItem.version = version ?? existingItem.version ?? '1'
existingItem.tenantId = definitionItem.tenantId
existingItem.purpose = definitionItem.purpose
Expand All @@ -141,10 +134,9 @@ export class PDManager implements IAgentPlugin {
private async handleOverwriteLatestMode(
latestVersionItem: PresentationDefinitionItem | undefined,
definitionItem: NonPersistedPresentationDefinitionItem,
definitionId: string,
): Promise<PresentationDefinitionItem> {
if (latestVersionItem) {
latestVersionItem.definitionId = definitionId
latestVersionItem.definitionId = definitionItem.definitionId
latestVersionItem.tenantId = definitionItem.tenantId
latestVersionItem.purpose = definitionItem.purpose
latestVersionItem.definitionPayload = definitionItem.definitionPayload
Expand All @@ -158,13 +150,12 @@ export class PDManager implements IAgentPlugin {
private async handleManualMode(
existingItem: PresentationDefinitionItem | undefined,
definitionItem: NonPersistedPresentationDefinitionItem,
definitionId: string,
tenantId: string | undefined,
version: string | undefined,
): Promise<PresentationDefinitionItem> {
if (existingItem && !isPresentationDefinitionEqual(existingItem, definitionItem)) {
throw Error(
`Cannot update definition ${definitionId} for tenant ${tenantId} version ${version} because definition exists and manual version control is enabled.`,
`Cannot update definition ${definitionItem.definitionId} for tenant ${tenantId} version ${version} because definition exists and manual version control is enabled.`,
)
} else {
return await this.store.addDefinition(definitionItem)
Expand Down
51 changes: 41 additions & 10 deletions packages/pd-manager/src/types/IPDManager.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,47 @@
import { IAgentContext, IPluginMethodMap } from '@veramo/core'
import { FindDefinitionArgs, NonPersistedPresentationDefinitionItem, PresentationDefinitionItem } from '@sphereon/ssi-sdk.data-store'
import { IPresentationDefinition } from '@sphereon/pex'
import {
FindDefinitionArgs,
NonPersistedPresentationDefinitionItem,
PersistablePresentationDefinitionItem,
PresentationDefinitionItem,
} from '@sphereon/ssi-sdk.data-store'

export interface IPDManager extends IPluginMethodMap {
pdmGetDefinitionItem(args: GetDefinitionItemArgs): Promise<PresentationDefinitionItem>
pdmGetDefinitionsItem(args: GetDefinitionsItemArgs): Promise<Array<PresentationDefinitionItem>>
pdmAddDefinitionItem(args: AddDefinitionItemArgs): Promise<PresentationDefinitionItem>
pdmUpdateDefinitionItem(args: UpdateDefinitionItemArgs): Promise<PresentationDefinitionItem>
pdmDeleteDefinitionItem(args: DeleteDefinitionItemArgs): Promise<boolean>
/**
* Get a single presentation definition records by primary key
* @param args
*/
pdmGetDefinition(args: GetDefinitionItemArgs): Promise<PresentationDefinitionItem>

/**
* Find one or more presentation definition records by filters
* @param args
*/
pdmGetDefinitions(args: GetDefinitionsItemArgs): Promise<Array<PresentationDefinitionItem>>

/**
* Add a presentation definition record
* @param args
*/
pdmAddDefinition(args: AddDefinitionItemArgs): Promise<PresentationDefinitionItem>

/**
* Update an existing presentation definition record
* @param args
*/
pdmUpdateDefinition(args: UpdateDefinitionItemArgs): Promise<PresentationDefinitionItem>

/**
* Delete a single presentation definition records by primary key
* @param args
*/
pdmDeleteDefinition(args: DeleteDefinitionItemArgs): Promise<boolean>

/**
* Persist a presentation definition.
* It has version control logic which will add or update presentation definition records and has settings for automatic version numbering.
* @param args
*/
pdmPersistDefinition(args: PersistDefinitionArgs): Promise<PresentationDefinitionItem>
}

Expand All @@ -33,9 +66,7 @@ export type DeleteDefinitionItemArgs = {
}

export type PersistDefinitionArgs = {
definition: IPresentationDefinition
tenantId?: string
version?: string
definitionItem: PersistablePresentationDefinitionItem
versionControlMode?: VersionControlMode
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,10 @@ export default (testContext: {
})

it('should remove definition', async () => {
await agent.pexStorePersistDefinition({ definition: singleDefinition })
await expect(agent.pexStoreHasDefinition({ definitionId: singleDefinition.id })).resolves.toEqual(true)
await expect(agent.pexStoreRemoveDefinition({ definitionId: singleDefinition.id })).resolves.toEqual(true)
await expect(agent.pexStoreHasDefinition({ definitionId: singleDefinition.id })).resolves.toEqual(false)
await agent.pexStorePersistDefinition({ definition: singleDefinition, version: 'toDelete', versionControlMode: 'Manual' })
await expect(agent.pexStoreHasDefinition({ definitionId: singleDefinition.id, version: 'toDelete' })).resolves.toEqual(true)
await expect(agent.pexStoreRemoveDefinition({ definitionId: singleDefinition.id, version: 'toDelete' })).resolves.toEqual(true)
await expect(agent.pexStoreHasDefinition({ definitionId: singleDefinition.id, version: 'toDelete' })).resolves.toEqual(false)
})

it('should clear definitions', async () => {
Expand Down
Loading

0 comments on commit ed77532

Please sign in to comment.