Skip to content
Snippets Groups Projects
Verified Commit 8ab2a1c9 authored by Konstantin Tsabolov's avatar Konstantin Tsabolov
Browse files

feat: implement proof-manager base CRUD

parent b2e9f0d2
No related branches found
No related tags found
No related merge requests found
Showing
with 1135 additions and 30 deletions
......@@ -7,7 +7,7 @@ export default {
moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node',
transform: {
'^.+\\.(ts|js)$': [
'^.+\\.(js|ts)$': [
'@swc/jest',
{
...swcConfig,
......@@ -31,10 +31,9 @@ export default {
: ['text-summary', 'html'],
coveragePathIgnorePatterns: [
'<rootDir>/node_modules/',
'<rootDir>/test/',
'<rootDir>/coverage/',
'<rootDir>/dist/',
'<rootDir>/**/test',
'__tests__',
'@types',
'.dto.(t|j)s',
'.enum.ts',
......
......@@ -16,8 +16,7 @@
"prisma:generate": "prisma generate --schema=./src/prisma/schema.prisma",
"prisma:migrate": "prisma migrate deploy --schema=./src/prisma/schema.prisma",
"prisma:studio": "prisma studio",
"start": "nest start",
"start:dev": "nest start --watch --preserveWatchOutput",
"start": "nest start --watch --preserveWatchOutput",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
......@@ -34,6 +33,7 @@
"@nestjs/swagger": "^7.1.16",
"@ocm/shared": "workspace:*",
"class-validator": "^0.14.0",
"class-transformer": "^0.5.1",
"express": "^4.17.3",
"joi": "^17.11.0",
"nats": "^2.18.0",
......
import type { ConfigType } from '@nestjs/config';
import type { ClientProvider } from '@nestjs/microservices';
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
......@@ -11,6 +12,7 @@ import { httpConfig } from './config/http.config.js';
import { natsConfig } from './config/nats.config.js';
import { ssiConfig } from './config/ssi.config.js';
import { validationSchema } from './config/validation.js';
import { ProofsModule } from './proofs/proofs.module.js';
@Module({
imports: [
......@@ -32,12 +34,21 @@ import { validationSchema } from './config/validation.js';
{
name: NATS_CLIENT,
inject: [natsConfig.KEY],
useFactory: (config: ConfigType<typeof natsConfig>) => ({
transport: Transport.NATS,
options: {
url: config.url as string,
},
}),
useFactory: (config: ConfigType<typeof natsConfig>) => {
const provider: Required<ClientProvider> = {
transport: Transport.NATS,
options: {
servers: config.url as string,
},
};
if ('user' in config && 'password' in config) {
provider.options.user = config.user as string;
provider.options.pass = config.password as string;
}
return provider;
},
},
],
}),
......@@ -57,7 +68,12 @@ import { validationSchema } from './config/validation.js';
},
}),
RouterModule.register([{ module: HealthModule, path: '/health' }]),
ProofsModule,
RouterModule.register([
{ module: HealthModule, path: '/health' },
{ module: ProofsModule, path: '/proofs' },
]),
],
})
export class Application {}
import { registerAs } from '@nestjs/config';
export const httpConfig = registerAs('http', () => ({
host: process.env.HOST,
port: Number(process.env.PORT),
host: process.env.HOST || '0.0.0.0',
port: Number(process.env.PORT) || 3000,
}));
import { registerAs } from '@nestjs/config';
export const natsConfig = registerAs('nats', () => ({
url: process.env.NATS_URL,
monitoringUrl: process.env.NATS_MONITORING_URL,
url: process.env.NATS_URL || 'nats://localhost:4222',
user: process.env.NATS_USER,
password: process.env.NATS_PASSWORD,
monitoringUrl: process.env.NATS_MONITORING_URL || 'http://localhost:8222',
}));
import { registerAs } from '@nestjs/config';
export const ssiConfig = registerAs('ssi', () => ({
agentUrl: process.env.SSI_AGENT_URL,
agentUrl: process.env.SSI_AGENT_URL || 'http://localhost:3010',
}));
import Joi from 'joi';
export const validationSchema = Joi.object({
HTTP_HOST: Joi.string().default('0.0.0.0'),
HTTP_PORT: Joi.number().default(3000),
HTTP_HOST: Joi.string(),
HTTP_PORT: Joi.number(),
NATS_URL: Joi.string().uri().default('nats://localhost:4222'),
NATS_MONITORING_URL: Joi.string().uri().default('http://localhost:8222'),
NATS_URL: Joi.string().uri(),
NATS_USER: Joi.string().optional(),
NATS_PASSWORD: Joi.string().optional(),
NATS_MONITORING_URL: Joi.string().uri(),
SSI_AGENT_URL: Joi.string().default('http://localhost:3010'),
SSI_AGENT_URL: Joi.string().uri(),
});
/* c8 ignore start */
import type { MicroserviceOptions } from '@nestjs/microservices';
import type { ConfigType } from '@nestjs/config';
import type { MicroserviceOptions, NatsOptions } from '@nestjs/microservices';
import { VersioningType } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Logger, VersioningType } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { Transport } from '@nestjs/microservices';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { Application } from './application.js';
import { httpConfig } from './config/http.config.js';
import { natsConfig } from './config/nats.config.js';
const app = await NestFactory.create(Application);
const configService = app.get(ConfigService);
app.enableCors();
app.connectMicroservice<MicroserviceOptions>({
const { url, user, password } = app.get(natsConfig.KEY) as ConfigType<
typeof natsConfig
>;
const microserviceOptions: Required<NatsOptions> = {
transport: Transport.NATS,
options: {
servers: [configService.get('nats').url],
servers: [url],
},
});
};
if (user && password) {
microserviceOptions.options.user = user;
microserviceOptions.options.pass = password;
}
app.connectMicroservice<MicroserviceOptions>(microserviceOptions);
app.enableVersioning({
defaultVersion: ['1'],
......@@ -36,5 +48,8 @@ const document = SwaggerModule.createDocument(app, swaggerConfig);
SwaggerModule.setup('/swagger', app, document);
await app.startAllMicroservices();
await app.listen(configService.get('PORT') || 3000);
const { host, port } = app.get(httpConfig.KEY) as ConfigType<typeof httpConfig>;
await app.listen(port as number, host as string);
Logger.log(`Application is running on: ${await app.getUrl()}`);
/* c8 ignore stop */
import type { TestingModule } from '@nestjs/testing';
import type {
EventAnonCredsProofsDeleteById,
EventAnonCredsProofsGetAll,
EventAnonCredsProofsGetById,
EventDidcommAnonCredsProofsRequest,
} from '@ocm/shared';
import { Test } from '@nestjs/testing';
import { Subject, of, takeUntil } from 'rxjs';
import { NATS_CLIENT } from '../../common/constants.js';
import { ProofsController } from '../proofs.controller.js';
import { ProofsService } from '../proofs.service.js';
describe('ProofsController', () => {
const natsClientMock = {};
let controller: ProofsController;
let service: ProofsService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [ProofsController],
providers: [
{ provide: NATS_CLIENT, useValue: natsClientMock },
ProofsService,
],
}).compile();
controller = module.get<ProofsController>(ProofsController);
service = module.get<ProofsService>(ProofsService);
});
describe('find', () => {
it('should return a list of schemas', (done) => {
const unsubscribe$ = new Subject<void>();
const tenantId = 'exampleTenantId';
const expectedResult: EventAnonCredsProofsGetAll['data'] = [];
jest.spyOn(service, 'find').mockReturnValue(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 schema by id', (done) => {
const unsubscribe$ = new Subject<void>();
const proofRecordId = 'exampleProofRecordId';
const tenantId = 'exampleTenantId';
const expectedResult = {} as NonNullable<
EventAnonCredsProofsGetById['data']
>;
jest.spyOn(service, 'getById').mockReturnValue(of(expectedResult));
controller
.get({ proofRecordId }, { tenantId })
.pipe(takeUntil(unsubscribe$))
.subscribe((result) => {
expect(result).toStrictEqual(expectedResult);
unsubscribe$.next();
unsubscribe$.complete();
done();
});
});
it('should throw a NotFoundException if the service returned null', (done) => {
const unsubscribe$ = new Subject<void>();
const proofRecordId = 'exampleProofRecordId';
const tenantId = 'exampleTenantId';
jest.spyOn(service, 'getById').mockReturnValue(of(null));
controller
.get({ proofRecordId }, { tenantId })
.pipe(takeUntil(unsubscribe$))
.subscribe({
error: (error) => {
expect(error.status).toBe(404);
expect(error.message).toBe(
`Presentation proof with id ${proofRecordId} not found`,
);
unsubscribe$.next();
unsubscribe$.complete();
done();
},
});
});
});
describe('request', () => {
it('should return a proof record', (done) => {
const unsubscribe$ = new Subject<void>();
const tenantId = 'exampleTenantId';
const name = 'exampleName';
const connectionId = 'exampleConnectionId';
const requestedAttributes = {};
const requestedPredicates = {};
const expectedResult = {} as EventDidcommAnonCredsProofsRequest['data'];
jest.spyOn(service, 'request').mockReturnValue(of(expectedResult));
controller
.request(
{ tenantId },
{ name, connectionId, requestedAttributes, requestedPredicates },
)
.pipe(takeUntil(unsubscribe$))
.subscribe((result) => {
expect(result).toStrictEqual(expectedResult);
unsubscribe$.next();
unsubscribe$.complete();
done();
});
});
});
describe('delete', () => {
it('should return a proof record', (done) => {
const unsubscribe$ = new Subject<void>();
const tenantId = 'exampleTenantId';
const proofRecordId = 'exampleProofRecordId';
const expectedResult = {} as EventAnonCredsProofsDeleteById['data'];
jest.spyOn(service, 'delete').mockReturnValue(of(expectedResult));
controller
.delete({ proofRecordId }, { tenantId })
.pipe(takeUntil(unsubscribe$))
.subscribe((result) => {
expect(result).toStrictEqual(expectedResult);
unsubscribe$.next();
unsubscribe$.complete();
done();
});
});
});
});
import { ClientsModule } from '@nestjs/microservices';
import { Test } from '@nestjs/testing';
import { NATS_CLIENT } from '../../common/constants.js';
import { ProofsController } from '../proofs.controller.js';
import { ProofsModule } from '../proofs.module.js';
import { ProofsService } from '../proofs.service.js';
describe('Proofs Module', () => {
let proofsController: ProofsController;
let proofsService: ProofsService;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
imports: [
ClientsModule.registerAsync({
isGlobal: true,
clients: [{ name: NATS_CLIENT, useFactory: () => ({}) }],
}),
ProofsModule,
],
}).compile();
proofsController = moduleRef.get<ProofsController>(ProofsController);
proofsService = moduleRef.get<ProofsService>(ProofsService);
});
it('should be defined', () => {
expect(proofsController).toBeDefined();
expect(proofsController).toBeInstanceOf(ProofsController);
expect(proofsService).toBeDefined();
expect(proofsService).toBeInstanceOf(ProofsService);
});
});
import type { TestingModule } from '@nestjs/testing';
import { Test } from '@nestjs/testing';
import {
EventAnonCredsProofsDeleteById,
EventAnonCredsProofsGetAll,
EventAnonCredsProofsGetById,
EventDidcommAnonCredsProofsRequest,
} from '@ocm/shared';
import { Subject, of, takeUntil } from 'rxjs';
import { NATS_CLIENT } from '../../common/constants.js';
import { ProofsService } from '../proofs.service.js';
describe('ProofsService', () => {
let service: ProofsService;
const natsClientMock = { send: jest.fn() };
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{ provide: NATS_CLIENT, useValue: natsClientMock },
ProofsService,
],
}).compile();
service = module.get<ProofsService>(ProofsService);
jest.resetAllMocks();
});
describe('getAll', () => {
it('should return the data from NATS client', (done) => {
const unsubscribe$ = new Subject<void>();
const tenantId = 'mocked tenantId';
const expectedResult: EventAnonCredsProofsGetAll['data'] = [];
natsClientMock.send.mockReturnValueOnce(
of(new EventAnonCredsProofsGetAll([], tenantId)),
);
service
.find(tenantId)
.pipe(takeUntil(unsubscribe$))
.subscribe((result) => {
expect(natsClientMock.send).toHaveBeenCalledWith(
EventAnonCredsProofsGetAll.token,
{ tenantId },
);
expect(result).toStrictEqual(expectedResult);
unsubscribe$.next();
unsubscribe$.complete();
done();
});
});
});
describe('getById', () => {
it('should return the data from NATS client', (done) => {
const unsubscribe$ = new Subject<void>();
const tenantId = 'mocked tenantId';
const proofRecordId = 'mocked id';
const expectedResult = {} as EventAnonCredsProofsGetById['data'];
natsClientMock.send.mockReturnValueOnce(
of(new EventAnonCredsProofsGetById(expectedResult, tenantId)),
);
service
.getById(tenantId, proofRecordId)
.pipe(takeUntil(unsubscribe$))
.subscribe((result) => {
expect(natsClientMock.send).toHaveBeenCalledWith(
EventAnonCredsProofsGetById.token,
{ tenantId, proofRecordId },
);
expect(result).toStrictEqual(expectedResult);
unsubscribe$.next();
unsubscribe$.complete();
done();
});
});
});
describe('request', () => {
it('should return the data from NATS client', (done) => {
const unsubscribe$ = new Subject<void>();
const tenantId = 'mocked tenantId';
const name = 'mocked name';
const connectionId = 'mocked connectionId';
const requestedAttributes = {};
const requestedPredicates = {};
const expectedResult = {
name,
connectionId,
requestedAttributes,
requestedPredicates,
} as unknown as EventDidcommAnonCredsProofsRequest['data'];
natsClientMock.send.mockReturnValueOnce(
of(new EventDidcommAnonCredsProofsRequest(expectedResult, tenantId)),
);
service
.request(
tenantId,
name,
connectionId,
requestedAttributes,
requestedPredicates,
)
.pipe(takeUntil(unsubscribe$))
.subscribe((result) => {
expect(natsClientMock.send).toHaveBeenCalledWith(
EventDidcommAnonCredsProofsRequest.token,
{
name,
connectionId,
requestedAttributes,
requestedPredicates,
tenantId,
},
);
expect(result).toStrictEqual(expectedResult);
unsubscribe$.next();
unsubscribe$.complete();
done();
});
});
});
describe('delete', () => {
it('should return the data from NATS client', (done) => {
const unsubscribe$ = new Subject<void>();
const tenantId = 'mocked tenantId';
const proofRecordId = 'mocked id';
const expectedResult = {} as EventAnonCredsProofsGetById['data'];
natsClientMock.send.mockReturnValueOnce(
of(new EventAnonCredsProofsGetById(expectedResult, tenantId)),
);
service
.delete(tenantId, proofRecordId)
.pipe(takeUntil(unsubscribe$))
.subscribe((result) => {
expect(natsClientMock.send).toHaveBeenCalledWith(
EventAnonCredsProofsDeleteById.token,
{ tenantId, proofRecordId },
);
expect(result).toStrictEqual(expectedResult);
unsubscribe$.next();
unsubscribe$.complete();
done();
});
});
});
});
import { IsNotEmpty, IsString } from 'class-validator';
export class GetByIdParams {
@IsString()
@IsNotEmpty()
public readonly proofRecordId: string;
}
import { Type } from 'class-transformer';
import {
IsArray,
IsEnum,
IsNotEmpty,
IsNumber,
IsObject,
IsOptional,
IsString,
ValidateNested,
} from 'class-validator';
export class RequestPayload {
@IsString()
@IsNotEmpty()
public readonly name: string;
@IsString()
@IsNotEmpty()
public readonly connectionId: string;
@IsObject()
@ValidateNested({ each: true })
@Type(() => RequestedAttribute)
public readonly requestedAttributes: Record<string, RequestedAttribute>;
@IsObject()
@ValidateNested({ each: true })
@Type(() => RequestedPredicate)
public readonly requestedPredicates: Record<string, RequestedPredicate>;
}
class RequestRestriction {
@IsString()
@IsNotEmpty()
@IsOptional()
public schema_id?: string;
@IsString()
@IsNotEmpty()
@IsOptional()
public schema_issuer_id?: string;
@IsString()
@IsNotEmpty()
@IsOptional()
public schema_name?: string;
@IsString()
@IsNotEmpty()
@IsOptional()
public schema_version?: string;
@IsString()
@IsNotEmpty()
@IsOptional()
public issuer_id?: string;
@IsString()
@IsNotEmpty()
@IsOptional()
public cred_def_id?: string;
@IsString()
@IsNotEmpty()
@IsOptional()
public rev_reg_id?: string;
@IsString()
@IsNotEmpty()
@IsOptional()
public schema_issuer_did?: string;
@IsString()
@IsNotEmpty()
@IsOptional()
public issuer_did?: string;
[key: `attr::${string}::marker`]: '1' | '0';
[key: `attr::${string}::value`]: string;
}
class RequestedAttribute {
@IsArray()
@IsString({ each: true })
@IsNotEmpty({ each: true })
public names: string[];
@IsArray()
@IsOptional()
@ValidateNested({ each: true })
@Type(() => RequestRestriction)
public restrictions?: RequestRestriction[];
}
const predicateType = ['>=', '>', '<=', '<'] as const;
class RequestedPredicate {
@IsString()
@IsNotEmpty()
public name: string;
@IsString()
@IsEnum(predicateType)
public predicateType: (typeof predicateType)[number];
@IsNumber()
public predicateValue: number;
@IsArray()
@IsOptional()
@ValidateNested({ each: true })
@Type(() => RequestRestriction)
public restrictions?: RequestRestriction[];
}
import {
Body,
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
NotFoundException,
Param,
Post,
Query,
UseInterceptors,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { MultitenancyParams, ResponseFormatInterceptor } from '@ocm/shared';
import { of, switchMap } from 'rxjs';
import { GetByIdParams } from './dto/get-by-id.dto.js';
import { RequestPayload } from './dto/register.dto.js';
import { ProofsService } from './proofs.service.js';
@Controller()
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
@UseInterceptors(ResponseFormatInterceptor)
@ApiTags('Presentation Proofs')
export class ProofsController {
public constructor(private readonly service: ProofsService) {}
@Get()
@ApiOperation({
summary: 'Fetch a list of presentation proofs',
description:
'This call provides a list of presentation proofs for a given tenant',
})
@ApiResponse({
status: HttpStatus.OK,
description: 'Presentation proofs fetched successfully',
content: {
'application/json': {
schema: {},
examples: {
'Presentation proofs fetched successfully': {
value: {
statusCode: 200,
message: 'Presentation proofs fetched successfully',
data: [],
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Tenant not found',
content: {
'application/json': {
schema: {},
examples: {
'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,
): ReturnType<ProofsService['find']> {
return this.service.find(tenantId);
}
@Get(':id')
@ApiOperation({
summary: 'Fetch a presentation proof by id',
description:
'This call provides a presentation proof for a given tenant and id',
})
@ApiResponse({
status: HttpStatus.OK,
description: 'Presentation proof fetched successfully',
content: {
'application/json': {
schema: {},
examples: {
'Presentation proof fetched successfully': {
value: {
statusCode: 200,
message: 'Presentation proof fetched successfully',
data: {},
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Tenant not found',
content: {
'application/json': {
schema: {},
examples: {
'Tenant not found': {
value: {
statusCode: 404,
message: 'Tenant not found',
data: null,
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: 'Invalid presentation proof id',
content: {
'application/json': {
schema: {},
examples: {
'Invalid presentation proof id': {
value: {
statusCode: 400,
message: 'Invalid presentation proof id',
data: null,
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Presentation proof not found',
content: {
'application/json': {
schema: {},
examples: {
'Presentation proof not found': {
value: {
statusCode: 404,
message: 'Presentation proof 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 get(
@Param() { proofRecordId }: GetByIdParams,
@Query() { tenantId }: MultitenancyParams,
): ReturnType<ProofsService['getById']> {
return this.service.getById(tenantId, proofRecordId).pipe(
switchMap((proofRecord) => {
if (!proofRecord) {
throw new NotFoundException(
`Presentation proof with id ${proofRecordId} not found`,
);
}
return of(proofRecord);
}),
);
}
@Post()
@ApiOperation({
summary: 'Request a presentation proof',
description: 'This call requests a presentation proof for a given tenant',
})
@ApiResponse({
status: HttpStatus.CREATED,
description: 'Presentation proof requested successfully',
content: {
'application/json': {
schema: {},
examples: {
'Presentation proof requested successfully': {
value: {
statusCode: 201,
message: 'Presentation proof requested successfully',
data: {},
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Tenant not found',
content: {
'application/json': {
schema: {},
examples: {
'Tenant not found': {
value: {
statusCode: 404,
message: 'Tenant not found',
data: null,
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: 'Invalid request payload',
content: {
'application/json': {
schema: {},
examples: {
'Invalid request payload': {
value: {
statusCode: 400,
message: 'Invalid request payload',
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,
},
},
},
},
},
})
@ApiBody({
schema: {
type: 'object',
properties: {
name: {
type: 'string',
example: 'Proof of Vaccination',
},
connectionId: {
type: 'string',
example: '1234567890',
},
requestedAttributes: {
type: 'object',
additionalProperties: {
type: 'object',
properties: {
names: {
type: 'array',
items: {
type: 'string',
},
},
restrictions: {
type: 'array',
items: {
type: 'object',
properties: {
schema_id: { type: 'string' },
schema_issuer_id: { type: 'string' },
schema_name: { type: 'string' },
schema_version: { type: 'string' },
issuer_id: { type: 'string' },
cred_def_id: { type: 'string' },
rev_reg_id: { type: 'string' },
schema_issuer_did: { type: 'string' },
issuer_did: { type: 'string' },
},
patternProperties: {
'^attr::.*?::marker$': { enum: ['1', '0'] },
'^attr::.*?::value$': { type: 'string' },
},
additionalProperties: {
type: 'string',
anyOf: [{ enum: ['1', '0'] }, { type: 'string' }],
},
},
},
},
required: ['names'],
},
},
requestedPredicates: {
type: 'object',
properties: {
name: { type: 'string' },
predicateType: { enum: ['>=', '>', '<=', '<'] },
predicateValue: { type: 'number' },
restrictions: {
type: 'array',
items: {
type: 'object',
properties: {
schema_id: { type: 'string' },
schema_issuer_id: { type: 'string' },
schema_name: { type: 'string' },
schema_version: { type: 'string' },
issuer_id: { type: 'string' },
cred_def_id: { type: 'string' },
rev_reg_id: { type: 'string' },
schema_issuer_did: { type: 'string' },
issuer_did: { type: 'string' },
},
patternProperties: {
'^attr::.*?::marker$': { enum: ['1', '0'] },
'^attr::.*?::value$': { type: 'string' },
},
additionalProperties: {
type: 'string',
anyOf: [{ enum: ['1', '0'] }, { type: 'string' }],
},
},
},
},
required: ['name', 'predicateType', 'predicateValue'],
},
},
required: [
'name',
'connectionId',
'requestedAttributes',
'requestedPredicates',
],
},
})
public request(
@Query() { tenantId }: MultitenancyParams,
@Body()
{
name,
connectionId,
requestedAttributes,
requestedPredicates,
}: RequestPayload,
): ReturnType<ProofsService['request']> {
return this.service.request(
tenantId,
name,
connectionId,
requestedAttributes,
requestedPredicates,
);
}
@Delete(':id')
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'Delete a presentation proof',
description: 'This call deletes a presentation proof for a given tenant',
})
@ApiResponse({
status: HttpStatus.OK,
description: 'Presentation proof deleted successfully',
content: {
'application/json': {
schema: {},
examples: {
'Presentation proof deleted successfully': {
value: {
statusCode: 200,
message: 'Presentation proof deleted successfully',
data: null,
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Tenant not found',
content: {
'application/json': {
schema: {},
examples: {
'Tenant not found': {
value: {
statusCode: 404,
message: 'Tenant not found',
data: null,
},
},
'Presentation proof not found': {
value: {
statusCode: 404,
message: 'Presentation proof not found',
data: null,
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: 'Invalid presentation proof id',
content: {
'application/json': {
schema: {},
examples: {
'Invalid presentation proof id': {
value: {
statusCode: 400,
message: 'Invalid presentation proof id',
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 delete(
@Param() { proofRecordId }: GetByIdParams,
@Query() { tenantId }: MultitenancyParams,
): ReturnType<ProofsService['delete']> {
return this.service.delete(tenantId, proofRecordId);
}
}
import { Module } from '@nestjs/common';
import { ProofsController } from './proofs.controller.js';
import { ProofsService } from './proofs.service.js';
@Module({
providers: [ProofsService],
controllers: [ProofsController],
})
export class ProofsModule {}
import type {
EventAnonCredsProofsDeleteByIdInput,
EventAnonCredsProofsGetAllInput,
EventAnonCredsProofsGetByIdInput,
EventDidcommAnonCredsProofsRequestInput,
} from '@ocm/shared';
import { Inject, Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import {
EventAnonCredsProofsDeleteById,
EventAnonCredsProofsGetById,
EventDidcommAnonCredsProofsRequest,
EventAnonCredsProofsGetAll,
} from '@ocm/shared';
import { map, type Observable } from 'rxjs';
import { NATS_CLIENT } from '../common/constants.js';
@Injectable()
export class ProofsService {
public constructor(
@Inject(NATS_CLIENT) private readonly natsClient: ClientProxy,
) {}
public find(
tenantId: string,
): Observable<EventAnonCredsProofsGetAll['data']> {
return this.natsClient
.send<
EventAnonCredsProofsGetAll,
EventAnonCredsProofsGetAllInput
>(EventAnonCredsProofsGetAll.token, { tenantId })
.pipe(map((result) => result.data));
}
public getById(
tenantId: string,
proofRecordId: string,
): Observable<EventAnonCredsProofsGetById['data']> {
return this.natsClient
.send<EventAnonCredsProofsGetById, EventAnonCredsProofsGetByIdInput>(
EventAnonCredsProofsGetById.token,
{
tenantId,
proofRecordId,
},
)
.pipe(map((results) => results.data));
}
public request(
tenantId: string,
name: EventDidcommAnonCredsProofsRequestInput['name'],
connectionId: EventDidcommAnonCredsProofsRequestInput['connectionId'],
requestedAttributes: EventDidcommAnonCredsProofsRequestInput['requestedAttributes'],
requestedPredicates: EventDidcommAnonCredsProofsRequestInput['requestedPredicates'],
): Observable<EventDidcommAnonCredsProofsRequest['data']> {
return this.natsClient
.send<
EventDidcommAnonCredsProofsRequest,
EventDidcommAnonCredsProofsRequestInput
>(EventDidcommAnonCredsProofsRequest.token, {
tenantId,
name,
connectionId,
requestedAttributes,
requestedPredicates,
})
.pipe(map((results) => results.data));
}
public delete(
tenantId: string,
proofRecordId: string,
): Observable<EventAnonCredsProofsDeleteById['data']> {
return this.natsClient
.send<
EventAnonCredsProofsDeleteById,
EventAnonCredsProofsDeleteByIdInput
>(EventAnonCredsProofsDeleteById.token, { tenantId, proofRecordId })
.pipe(map((results) => results.data));
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment