diff --git a/apps/credential-manager/src/application.ts b/apps/credential-manager/src/application.ts index c0ad214c9e168df74c7ff3f31a15253f0cbafa39..f60686bfdfb1359cffcd9c418d60ee6bd4459be6 100644 --- a/apps/credential-manager/src/application.ts +++ b/apps/credential-manager/src/application.ts @@ -12,6 +12,7 @@ import { natsConfig } from './config/nats.config.js'; import { ssiConfig } from './config/ssi.config.js'; import { validationSchema } from './config/validation.js'; import { CredentialOffersModule } from './credential-offers/credential-offers.module.js'; +import { CredentialRequestsModule } from './credential-requests/credential-requests.module.js'; @Module({ imports: [ @@ -59,10 +60,12 @@ import { CredentialOffersModule } from './credential-offers/credential-offers.mo }), CredentialOffersModule, + CredentialRequestsModule, RouterModule.register([ { module: HealthModule, path: '/health' }, { module: CredentialOffersModule, path: '/credential-offers' }, + { module: CredentialRequestsModule, path: '/credential-requests' }, ]), ], }) diff --git a/apps/credential-manager/src/credential-offers/credential-offers.controller.ts b/apps/credential-manager/src/credential-offers/credential-offers.controller.ts index 882d08ad48ca87f1b624f51a2ebfe7f3f55269c0..1bd6ae85733d36b6c1bd08a77a40e9b00b16ccf7 100644 --- a/apps/credential-manager/src/credential-offers/credential-offers.controller.ts +++ b/apps/credential-manager/src/credential-offers/credential-offers.controller.ts @@ -45,6 +45,23 @@ export class CredentialOffersController { ], }, }, + }, + }, + }, + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + content: { + 'application/json': { + schema: {}, + examples: { + 'Credential offer not found': { + value: { + statusCode: 404, + message: 'Credential offer not found', + data: null, + }, + }, 'Tenant not found': { value: { statusCode: 404, diff --git a/apps/credential-manager/src/credential-requests/__tests__/credential-requests.controller.spec.ts b/apps/credential-manager/src/credential-requests/__tests__/credential-requests.controller.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..302cc4058798f0e1bb4cb257d2d53d22b47aa914 --- /dev/null +++ b/apps/credential-manager/src/credential-requests/__tests__/credential-requests.controller.spec.ts @@ -0,0 +1,90 @@ +import type { TestingModule } from '@nestjs/testing'; +import type { + EventAnonCredsCredentialRequestGetAll, + EventAnonCredsCredentialRequestGetById, +} from '@ocm/shared'; + +import { Test } from '@nestjs/testing'; +import { Subject, of, takeUntil } from 'rxjs'; + +import { NATS_CLIENT } from '../../common/constants.js'; +import { CredentialRequestsController } from '../credential-requests.controller.js'; +import { CredentialRequestsService } from '../credential-requests.service.js'; + +describe('CredentialRequestsController', () => { + const natsClientMock = {}; + + let controller: CredentialRequestsController; + let service: CredentialRequestsService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [CredentialRequestsController], + providers: [ + { provide: NATS_CLIENT, useValue: natsClientMock }, + CredentialRequestsService, + ], + }).compile(); + + controller = module.get<CredentialRequestsController>( + CredentialRequestsController, + ); + service = module.get<CredentialRequestsService>(CredentialRequestsService); + }); + + describe('find', () => { + it('should return a list of credential requests', (done) => { + const unsubscribe$ = new Subject<void>(); + const tenantId = 'exampleTenantId'; + const expectedResult: EventAnonCredsCredentialRequestGetAll['data'] = []; + + jest + .spyOn(service, 'findCredentialRequests') + .mockReturnValueOnce(of(expectedResult)); + + controller + .find({ tenantId }) + .pipe(takeUntil(unsubscribe$)) + .subscribe((result) => { + expect(result).toStrictEqual(expectedResult); + + unsubscribe$.next(); + unsubscribe$.complete(); + + done(); + }); + }); + }); + + describe('getById', () => { + it('should return a credential request', (done) => { + const unsubscribe$ = new Subject<void>(); + const tenantId = 'exampleTenantId'; + const credentialRequestId = 'exampleCredentialRequestId'; + const expectedResult: EventAnonCredsCredentialRequestGetById['data'] = { + blinded_ms: {}, + blinded_ms_correctness_proof: {}, + cred_def_id: 'cred_def_id', + nonce: 'nonce', + entropy: 'entropy', + prover_did: 'prover_did', + }; + + jest + .spyOn(service, 'getCredentialRequestById') + .mockReturnValueOnce(of(expectedResult)); + + controller + .getById({ credentialRequestId }, { tenantId }) + .pipe(takeUntil(unsubscribe$)) + .subscribe((result) => { + expect(result).toStrictEqual(expectedResult); + + unsubscribe$.next(); + unsubscribe$.complete(); + + done(); + }); + }); + }); +}); diff --git a/apps/credential-manager/src/credential-requests/__tests__/credential-requests.module.spec.ts b/apps/credential-manager/src/credential-requests/__tests__/credential-requests.module.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..7fcd088315f73bfcf6cfc81f4e3c895705b58092 --- /dev/null +++ b/apps/credential-manager/src/credential-requests/__tests__/credential-requests.module.spec.ts @@ -0,0 +1,41 @@ +import { ClientsModule } from '@nestjs/microservices'; +import { Test } from '@nestjs/testing'; + +import { NATS_CLIENT } from '../../common/constants.js'; +import { CredentialRequestsController } from '../credential-requests.controller.js'; +import { CredentialRequestsModule } from '../credential-requests.module.js'; +import { CredentialRequestsService } from '../credential-requests.service.js'; + +describe('CredentialRequestsModule', () => { + let credentialRequestsController: CredentialRequestsController; + let credentialRequestsService: CredentialRequestsService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [ + ClientsModule.registerAsync({ + isGlobal: true, + clients: [{ name: NATS_CLIENT, useFactory: () => ({}) }], + }), + CredentialRequestsModule, + ], + }).compile(); + + credentialRequestsController = moduleRef.get<CredentialRequestsController>( + CredentialRequestsController, + ); + credentialRequestsService = moduleRef.get<CredentialRequestsService>( + CredentialRequestsService, + ); + }); + + it('should be defined', () => { + expect(credentialRequestsController).toBeDefined(); + expect(credentialRequestsController).toBeInstanceOf( + CredentialRequestsController, + ); + + expect(credentialRequestsService).toBeDefined(); + expect(credentialRequestsService).toBeInstanceOf(CredentialRequestsService); + }); +}); diff --git a/apps/credential-manager/src/credential-requests/__tests__/credential-requests.service.spec.ts b/apps/credential-manager/src/credential-requests/__tests__/credential-requests.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..a9a8d844eb55d25548ccb150edac1d6b5a42dcbc --- /dev/null +++ b/apps/credential-manager/src/credential-requests/__tests__/credential-requests.service.spec.ts @@ -0,0 +1,83 @@ +import type { TestingModule } from '@nestjs/testing'; + +import { Test } from '@nestjs/testing'; +import { EventAnonCredsCredentialRequestGetAll } from '@ocm/shared'; +import { Subject, of, takeUntil } from 'rxjs'; + +import { NATS_CLIENT } from '../../common/constants.js'; +import { CredentialRequestsService } from '../credential-requests.service.js'; + +describe('CredentialRequestsService', () => { + const natsClientMock = { send: jest.fn() }; + let service: CredentialRequestsService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { provide: NATS_CLIENT, useValue: natsClientMock }, + CredentialRequestsService, + ], + }).compile(); + + service = module.get<CredentialRequestsService>(CredentialRequestsService); + }); + + describe('findCredentialRequests', () => { + it('should call the natsClient send method with the correct arguments', (done) => { + const unsubscribe$ = new Subject<void>(); + const tenantId = 'tenantId'; + const expectedResult: EventAnonCredsCredentialRequestGetAll['data'] = []; + + natsClientMock.send.mockReturnValueOnce( + of(new EventAnonCredsCredentialRequestGetAll(expectedResult, tenantId)), + ); + + service + .findCredentialRequests(tenantId) + .pipe(takeUntil(unsubscribe$)) + .subscribe((result) => { + expect(natsClientMock.send).toHaveBeenCalledWith( + EventAnonCredsCredentialRequestGetAll.token, + { tenantId }, + ); + + expect(result).toStrictEqual(expectedResult); + + unsubscribe$.next(); + unsubscribe$.complete(); + + done(); + }); + }); + }); + + describe('getCredentialRequestById', () => { + it('should call the natsClient send method with the correct arguments', (done) => { + const unsubscribe$ = new Subject<void>(); + const tenantId = 'tenantId'; + const credentialRequestId = 'credentialRequestId'; + const expectedResult: EventAnonCredsCredentialRequestGetAll['data'] = []; + + natsClientMock.send.mockReturnValueOnce( + of(new EventAnonCredsCredentialRequestGetAll(expectedResult, tenantId)), + ); + + service + .getCredentialRequestById(tenantId, credentialRequestId) + .pipe(takeUntil(unsubscribe$)) + .subscribe((result) => { + expect(natsClientMock.send).toHaveBeenCalledWith( + EventAnonCredsCredentialRequestGetAll.token, + { tenantId, credentialRequestId }, + ); + + expect(result).toStrictEqual(expectedResult); + + unsubscribe$.next(); + unsubscribe$.complete(); + + done(); + }); + }); + }); +}); diff --git a/apps/credential-manager/src/credential-requests/credential-requests.controller.ts b/apps/credential-manager/src/credential-requests/credential-requests.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..99297fdf950fcc017bdb1330a356a6f6e07f8ee9 --- /dev/null +++ b/apps/credential-manager/src/credential-requests/credential-requests.controller.ts @@ -0,0 +1,186 @@ +import { + Controller, + Get, + HttpStatus, + Param, + Query, + UseInterceptors, + UsePipes, + ValidationPipe, +} from '@nestjs/common'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { MultitenancyParams, ResponseFormatInterceptor } from '@ocm/shared'; + +import { CredentialRequestsService } from './credential-requests.service.js'; +import { GetByIdParams } from './dto/get-by-id.dto.js'; + +@Controller() +@UsePipes(new ValidationPipe({ transform: true, whitelist: true })) +@UseInterceptors(ResponseFormatInterceptor) +@ApiTags('Credential Requests') +export class CredentialRequestsController { + public constructor(private readonly service: CredentialRequestsService) {} + + @Get() + @ApiOperation({ + summary: 'Fetch a list of credential requests', + description: + 'This call provides a list of credential requests for a given tenant', + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Credential requests fetched successfully', + content: { + 'application/json': { + schema: {}, + examples: { + 'Credential requests fetched successfully': { + value: { + statusCode: 200, + message: 'Credential requests fetched successfully', + data: [ + { + id: '71b784a3', + }, + ], + }, + }, + 'Tenant not found': { + value: { + statusCode: 404, + message: 'Tenant not found', + data: null, + }, + }, + }, + }, + }, + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + content: { + 'application/json': { + schema: {}, + examples: { + 'Credential request not found': { + value: { + statusCode: 404, + message: 'Credential request not found', + data: null, + }, + }, + 'Tenant not found': { + value: { + statusCode: 404, + message: 'Tenant not found', + data: null, + }, + }, + }, + }, + }, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: 'Internal server error', + content: { + 'application/json': { + schema: {}, + examples: { + 'Internal server error': { + value: { + statusCode: 500, + message: 'Internal server error', + data: null, + }, + }, + }, + }, + }, + }) + public find(@Query() { tenantId }: MultitenancyParams) { + return this.service.findCredentialRequests(tenantId); + } + + @Get(':id') + @ApiOperation({ + summary: 'Fetch a credential request by id', + description: + 'This call provides a credential request for a given tenant by id', + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Credential request fetched successfully', + content: { + 'application/json': { + schema: {}, + examples: { + 'Credential request fetched successfully': { + value: { + statusCode: 200, + message: 'Credential request fetched successfully', + data: { + id: '71b784a3', + }, + }, + }, + 'Tenant not found': { + value: { + statusCode: 404, + message: 'Tenant not found', + data: null, + }, + }, + }, + }, + }, + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + content: { + 'application/json': { + schema: {}, + examples: { + 'Credential request not found': { + value: { + statusCode: 404, + message: 'Credential request not found', + data: null, + }, + }, + 'Tenant not found': { + value: { + statusCode: 404, + message: 'Tenant not found', + data: null, + }, + }, + }, + }, + }, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: 'Internal server error', + content: { + 'application/json': { + schema: {}, + examples: { + 'Internal server error': { + value: { + statusCode: 500, + message: 'Internal server error', + data: null, + }, + }, + }, + }, + }, + }) + public getById( + @Param() { credentialRequestId }: GetByIdParams, + @Query() { tenantId }: MultitenancyParams, + ) { + return this.service.getCredentialRequestById(tenantId, credentialRequestId); + } +} diff --git a/apps/credential-manager/src/credential-requests/credential-requests.module.ts b/apps/credential-manager/src/credential-requests/credential-requests.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..af559d79bd371d0d0b848ae77f27c89f087b8091 --- /dev/null +++ b/apps/credential-manager/src/credential-requests/credential-requests.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; + +import { CredentialRequestsController } from './credential-requests.controller.js'; +import { CredentialRequestsService } from './credential-requests.service.js'; + +@Module({ + providers: [CredentialRequestsService], + controllers: [CredentialRequestsController], +}) +export class CredentialRequestsModule {} diff --git a/apps/credential-manager/src/credential-requests/credential-requests.service.ts b/apps/credential-manager/src/credential-requests/credential-requests.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..81675508bb0135a679f2136b031a7bb2eba64d0f --- /dev/null +++ b/apps/credential-manager/src/credential-requests/credential-requests.service.ts @@ -0,0 +1,43 @@ +import type { + EventAnonCredsCredentialRequestGetAllInput, + EventAnonCredsCredentialRequestGetById, + EventAnonCredsCredentialRequestGetByIdInput, +} from '@ocm/shared'; + +import { Inject, Injectable } from '@nestjs/common'; +import { ClientProxy } from '@nestjs/microservices'; +import { EventAnonCredsCredentialRequestGetAll } from '@ocm/shared'; +import { map } from 'rxjs'; + +import { NATS_CLIENT } from '../common/constants.js'; + +@Injectable() +export class CredentialRequestsService { + public constructor( + @Inject(NATS_CLIENT) private readonly natsClient: ClientProxy, + ) {} + + public findCredentialRequests(tenantId: string) { + return this.natsClient + .send< + EventAnonCredsCredentialRequestGetAll, + EventAnonCredsCredentialRequestGetAllInput + >(EventAnonCredsCredentialRequestGetAll.token, { tenantId }) + .pipe(map(({ data }) => data)); + } + + public getCredentialRequestById( + tenantId: string, + credentialRequestId: string, + ) { + return this.natsClient + .send< + EventAnonCredsCredentialRequestGetById, + EventAnonCredsCredentialRequestGetByIdInput + >(EventAnonCredsCredentialRequestGetAll.token, { + tenantId, + credentialRequestId, + }) + .pipe(map(({ data }) => data)); + } +} diff --git a/apps/credential-manager/src/credential-requests/dto/get-by-id.dto.ts b/apps/credential-manager/src/credential-requests/dto/get-by-id.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..9c3e323437fe7c0cfaffb4b295d30540f8581623 --- /dev/null +++ b/apps/credential-manager/src/credential-requests/dto/get-by-id.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class GetByIdParams { + @IsString() + @IsNotEmpty() + @ApiProperty({ + description: 'The credential request ID to retrieve', + format: 'string', + }) + public credentialRequestId: string; +} diff --git a/apps/shared/src/events/credentialEvents.ts b/apps/shared/src/events/credentialEvents.ts index 32ed92aaffc305cd5011a7be238647b62f3fb62f..feb20722bee5ddd08334696b2ceb04be64f4e827 100644 --- a/apps/shared/src/events/credentialEvents.ts +++ b/apps/shared/src/events/credentialEvents.ts @@ -97,3 +97,20 @@ export class EventDidcommAnonCredsCredentialsOfferToSelf extends BaseEvent<Crede ); } } + +export type EventAnonCredsCredentialsDeleteByIdInput = BaseEventInput<{ + credentialRecordId: string; +}>; +export class EventAnonCredsCredentialsDeleteById extends BaseEvent { + public static token = 'anoncreds.credentials.offerToSelf.deleteById'; + + public static fromEvent(e: EventAnonCredsCredentialsDeleteById) { + return new EventAnonCredsCredentialsDeleteById( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); + } +} diff --git a/apps/shared/src/events/credentialOfferEvents.ts b/apps/shared/src/events/credentialOfferEvents.ts index d120f84048244d20cbcc87d3bf96966b8b58725b..63e9eb15e81cc97ef0c9a264226b5c893e21a497 100644 --- a/apps/shared/src/events/credentialOfferEvents.ts +++ b/apps/shared/src/events/credentialOfferEvents.ts @@ -1,12 +1,12 @@ import type { BaseEventInput } from './baseEvents.js'; -import type { AnonCredsCredentialOffer } from '@aries-framework/anoncreds'; +import type { CredentialExchangeRecord } from '@aries-framework/core'; import { BaseEvent } from './baseEvents.js'; export type EventAnonCredsCredentialOfferGetAllInput = BaseEventInput; export class EventAnonCredsCredentialOfferGetAll extends BaseEvent< - Array<AnonCredsCredentialOffer> + Array<CredentialExchangeRecord> > { public static token = 'anoncreds.credentialOffers.getAll'; @@ -29,7 +29,7 @@ export type EventAnonCredsCredentialOfferGetByIdInput = BaseEventInput & { credentialOfferId: string; }; -export class EventAnonCredsCredentialOfferGetById extends BaseEvent<AnonCredsCredentialOffer | null> { +export class EventAnonCredsCredentialOfferGetById extends BaseEvent<CredentialExchangeRecord | null> { public static token = 'anoncreds.credentialOffers.getById'; public get instance() { diff --git a/apps/shared/src/events/credentialRequestEvents.ts b/apps/shared/src/events/credentialRequestEvents.ts new file mode 100644 index 0000000000000000000000000000000000000000..9daefa1066c768641d71f5968464cd40ee9c9481 --- /dev/null +++ b/apps/shared/src/events/credentialRequestEvents.ts @@ -0,0 +1,48 @@ +import type { BaseEventInput } from './baseEvents.js'; +import type { CredentialExchangeRecord } from '@aries-framework/core'; + +import { BaseEvent } from './baseEvents.js'; + +export type EventAnonCredsCredentialRequestGetAllInput = BaseEventInput; + +export class EventAnonCredsCredentialRequestGetAll extends BaseEvent< + Array<CredentialExchangeRecord> +> { + public static token = 'anoncreds.credentialRequests.getAll'; + + public get instance() { + return this.data; + } + + public static fromEvent(e: EventAnonCredsCredentialRequestGetAll) { + return new EventAnonCredsCredentialRequestGetAll( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); + } +} + +export type EventAnonCredsCredentialRequestGetByIdInput = BaseEventInput & { + credentialRequestId: string; +}; + +export class EventAnonCredsCredentialRequestGetById extends BaseEvent<CredentialExchangeRecord | null> { + public static token = 'anoncreds.credentialRequests.getById'; + + public get instance() { + return this.data; + } + + public static fromEvent(e: EventAnonCredsCredentialRequestGetById) { + return new EventAnonCredsCredentialRequestGetById( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); + } +} diff --git a/apps/shared/src/index.ts b/apps/shared/src/index.ts index 7e5e2824e92af93cf89cd6432a613c83c0f3a454..20c9faf786512da49c496a8d717ff643e430242b 100644 --- a/apps/shared/src/index.ts +++ b/apps/shared/src/index.ts @@ -11,6 +11,7 @@ export * from './events/schemaEvents.js'; export * from './events/credentialDefinitionEvents.js'; export * from './events/credentialEvents.js'; export * from './events/credentialOfferEvents.js'; +export * from './events/credentialRequestEvents.js'; export * from './dto/pagination-params.dto.js'; export * from './dto/multitenancy-params.dto.js'; diff --git a/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.controller.ts b/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.controller.ts index 1cef75b483beddd9ff786a9b095c3402a2349e33..2e0df5af2f99593343ab3c09e8e8622daaee300e 100644 --- a/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.controller.ts +++ b/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.controller.ts @@ -9,6 +9,10 @@ import { EventDidcommAnonCredsCredentialsOfferInput, EventDidcommAnonCredsCredentialsOfferToSelfInput, EventDidcommAnonCredsCredentialsOfferToSelf, + EventAnonCredsCredentialOfferGetAll, + EventAnonCredsCredentialOfferGetAllInput, + EventAnonCredsCredentialRequestGetAll, + EventAnonCredsCredentialRequestGetAllInput, } from '@ocm/shared'; import { AnonCredsCredentialsService } from './anoncredsCredentials.service.js'; @@ -27,6 +31,26 @@ export class AnonCredsCredentialsController { ); } + @MessagePattern(EventAnonCredsCredentialOfferGetAll.token) + public async getAllOffers( + options: EventAnonCredsCredentialOfferGetAllInput, + ): Promise<EventAnonCredsCredentialOfferGetAll> { + return new EventAnonCredsCredentialOfferGetAll( + await this.credentialsService.getAllOffers(options), + options.tenantId, + ); + } + + @MessagePattern(EventAnonCredsCredentialRequestGetAll.token) + public async getAllRequests( + options: EventAnonCredsCredentialRequestGetAllInput, + ): Promise<EventAnonCredsCredentialRequestGetAll> { + return new EventAnonCredsCredentialRequestGetAll( + await this.credentialsService.getAllRequests(options), + options.tenantId, + ); + } + @MessagePattern(EventDidcommAnonCredsCredentialsGetById.token) public async getById( options: EventDidcommAnonCredsCredentialsGetByIdInput, diff --git a/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.service.ts b/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.service.ts index 3eb52b6e61e023f4deb42ebefbc8ff500af4cc99..f9c6b450dcbf3645dd2fad514f469753a1b344d8 100644 --- a/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.service.ts +++ b/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.service.ts @@ -1,15 +1,27 @@ import type { + EventAnonCredsCredentialRequestGetByIdInput, + EventAnonCredsCredentialOfferGetAllInput, + EventAnonCredsCredentialOfferGetById, + EventAnonCredsCredentialOfferGetByIdInput, + EventAnonCredsCredentialRequestGetAllInput, EventDidcommAnonCredsCredentialsGetAllInput, EventDidcommAnonCredsCredentialsGetByIdInput, EventDidcommAnonCredsCredentialsOfferInput, EventDidcommAnonCredsCredentialsOfferToSelfInput, + EventAnonCredsCredentialRequestGetById, + EventDidcommAnonCredsCredentialsOffer, + EventDidcommAnonCredsCredentialsOfferToSelf, + EventDidcommAnonCredsCredentialsGetById, + EventAnonCredsCredentialRequestGetAll, + EventAnonCredsCredentialOfferGetAll, + EventDidcommAnonCredsCredentialsGetAll, + EventAnonCredsCredentialsDeleteByIdInput, + EventAnonCredsCredentialsDeleteById, } from '@ocm/shared'; -import { - AutoAcceptCredential, - type CredentialExchangeRecord, -} from '@aries-framework/core'; +import { AutoAcceptCredential, CredentialState } from '@aries-framework/core'; import { Injectable } from '@nestjs/common'; +import { logger } from '@ocm/shared'; import { MetadataTokens } from '../../common/constants.js'; import { WithTenantService } from '../withTenantService.js'; @@ -21,28 +33,120 @@ export class AnonCredsCredentialsService { public async getAll({ tenantId, }: EventDidcommAnonCredsCredentialsGetAllInput): Promise< - Array<CredentialExchangeRecord> + EventDidcommAnonCredsCredentialsGetAll['data'] > { return this.withTenantService.invoke(tenantId, (t) => t.credentials.getAll(), ); } + public async getAllOffers({ + tenantId, + }: EventAnonCredsCredentialOfferGetAllInput): Promise< + EventAnonCredsCredentialOfferGetAll['data'] + > { + return this.withTenantService.invoke(tenantId, (t) => + t.credentials.findAllByQuery({ + $or: [ + { state: CredentialState.OfferSent }, + { state: CredentialState.OfferReceived }, + ], + }), + ); + } + + public async getAllRequests({ + tenantId, + }: EventAnonCredsCredentialRequestGetAllInput): Promise< + EventAnonCredsCredentialRequestGetAll['data'] + > { + return this.withTenantService.invoke(tenantId, (t) => + t.credentials.findAllByQuery({ + $or: [ + { state: CredentialState.RequestSent }, + { state: CredentialState.RequestReceived }, + ], + }), + ); + } + + public async deleteById({ + tenantId, + credentialRecordId, + }: EventAnonCredsCredentialsDeleteByIdInput): Promise< + EventAnonCredsCredentialsDeleteById['data'] + > { + return this.withTenantService.invoke(tenantId, async (t) => { + await t.credentials.deleteById(credentialRecordId); + return {}; + }); + } + public async getById({ tenantId, credentialRecordId, - }: EventDidcommAnonCredsCredentialsGetByIdInput): Promise<CredentialExchangeRecord | null> { + }: EventDidcommAnonCredsCredentialsGetByIdInput): Promise< + EventDidcommAnonCredsCredentialsGetById['data'] + > { return this.withTenantService.invoke(tenantId, (t) => t.credentials.findById(credentialRecordId), ); } + public async getOfferById({ + tenantId, + credentialOfferId, + }: EventAnonCredsCredentialOfferGetByIdInput): Promise< + EventAnonCredsCredentialOfferGetById['data'] + > { + return this.withTenantService.invoke(tenantId, async (t) => { + const credential = await t.credentials.findById(credentialOfferId); + + if ( + credential && + credential.state !== CredentialState.OfferSent && + credential.state !== CredentialState.OfferReceived + ) { + logger.warn( + `Credential '${credentialOfferId}' does exist, but is not in offer state. Actual state: ${credential.state}`, + ); + } + + return credential; + }); + } + + public async getRequestById({ + tenantId, + credentialRequestId, + }: EventAnonCredsCredentialRequestGetByIdInput): Promise< + EventAnonCredsCredentialRequestGetById['data'] + > { + return this.withTenantService.invoke(tenantId, async (t) => { + const credential = await t.credentials.findById(credentialRequestId); + + if ( + credential && + credential.state !== CredentialState.RequestSent && + credential.state !== CredentialState.RequestReceived + ) { + logger.warn( + `Credential '${credentialRequestId}' does exist, but is not in a request state. Actual state: ${credential.state}`, + ); + } + + return credential; + }); + } + public async offer({ tenantId, connectionId, credentialDefinitionId, attributes, - }: EventDidcommAnonCredsCredentialsOfferInput): Promise<CredentialExchangeRecord> { + }: EventDidcommAnonCredsCredentialsOfferInput): Promise< + EventDidcommAnonCredsCredentialsOffer['data'] + > { return this.withTenantService.invoke(tenantId, (t) => t.credentials.offerCredential({ protocolVersion: 'v2', @@ -58,7 +162,9 @@ export class AnonCredsCredentialsService { tenantId, credentialDefinitionId, attributes, - }: EventDidcommAnonCredsCredentialsOfferToSelfInput): Promise<CredentialExchangeRecord> { + }: EventDidcommAnonCredsCredentialsOfferToSelfInput): Promise< + EventDidcommAnonCredsCredentialsOfferToSelf['data'] + > { return this.withTenantService.invoke(tenantId, async (t) => { const connections = await t.connections.getAll(); const connection = connections.find((c) => {