Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • eclipse/xfsc/ocm/ocm-engine
  • zdravko61/ocm-engine
  • mjuergenscg/ocm-engine
  • tsabolov/ocm-engine
  • mikesell/ocm-engine
5 results
Show changes
Commits on Source (11)
Showing
with 1023 additions and 103 deletions
......@@ -2,6 +2,7 @@ import type { ConfigType } from '@nestjs/config';
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { RouterModule } from '@nestjs/core';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { NATS_CLIENT } from './common/constants.js';
......@@ -9,6 +10,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 { CredentialDefinitionsModule } from './credential-definitions/credential-definitions.module.js';
import { HealthModule } from './health/health.module.js';
import { SchemasModule } from './schemas/schemas.module.js';
......@@ -43,7 +45,15 @@ import { SchemasModule } from './schemas/schemas.module.js';
}),
HealthModule,
SchemasModule,
CredentialDefinitionsModule,
RouterModule.register([
{ module: HealthModule, path: '/health' },
{ module: SchemasModule, path: '/schemas' },
{ module: CredentialDefinitionsModule, path: '/credential-definitions' },
]),
],
})
export class Application {}
import type { ExecutionContext } from '@nestjs/common';
import type { TestingModule } from '@nestjs/testing';
import { Test } from '@nestjs/testing';
import { of } from 'rxjs';
import { ResponseFormatInterceptor } from '../response-format.interceptor.js';
describe('ResponseFormatInterceptor', () => {
let interceptor: ResponseFormatInterceptor;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ResponseFormatInterceptor],
}).compile();
interceptor = module.get<ResponseFormatInterceptor>(
ResponseFormatInterceptor,
);
});
it('should be defined', () => {
expect(interceptor).toBeDefined();
expect(interceptor).toBeInstanceOf(ResponseFormatInterceptor);
});
it('should intercept the request and format the response', (done) => {
const context: ExecutionContext = {
switchToHttp: () => ({
getResponse: () => ({
statusCode: 200,
}),
}),
} as ExecutionContext;
const next = {
handle: jest.fn().mockReturnValue(of('Hello World')),
};
const result = interceptor.intercept(context, next);
expect(result).toBeDefined();
expect(next.handle).toHaveBeenCalled();
result.subscribe((response) => {
expect(response).toEqual({ statusCode: 200, data: 'Hello World' });
done();
});
});
});
import type {
CallHandler,
ExecutionContext,
NestInterceptor,
} from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { map, type Observable } from 'rxjs';
@Injectable()
export class ResponseFormatInterceptor implements NestInterceptor {
public intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<unknown> {
const ctx = context.switchToHttp();
const response = ctx.getResponse();
return next.handle().pipe(
map((data) => {
return {
statusCode: response.statusCode,
data,
};
}),
);
}
}
import type { CreateCredentialDefinitionPayload } from '../dto/create-credential-definition.dto.js';
import type { TestingModule } from '@nestjs/testing';
import type {
EventAnonCredsCredentialDefinitionsGetAll,
EventAnonCredsCredentialDefinitionsGetById,
EventAnonCredsCredentialDefinitionsRegister,
} from '@ocm/shared';
import { Test } from '@nestjs/testing';
import { Subject, of, takeUntil } from 'rxjs';
import { NATS_CLIENT } from '../../common/constants.js';
import { CredentialDefinitionsController } from '../credential-definitions.controller.js';
import { CredentialDefinitionsService } from '../credential-definitions.service.js';
describe('CredentialDefinitionsController', () => {
const natsClientMock = {};
let controller: CredentialDefinitionsController;
let service: CredentialDefinitionsService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [CredentialDefinitionsController],
providers: [
{ provide: NATS_CLIENT, useValue: natsClientMock },
CredentialDefinitionsService,
],
}).compile();
controller = module.get<CredentialDefinitionsController>(
CredentialDefinitionsController,
);
service = module.get<CredentialDefinitionsService>(
CredentialDefinitionsService,
);
});
describe('find', () => {
it('should return a list of credential definitions', (done) => {
const unsubscribe$ = new Subject<void>();
const tenantId = 'exampleTenantId';
const expectedResult: EventAnonCredsCredentialDefinitionsGetAll['data'] =
[];
jest
.spyOn(service, 'findCredentialDefinitions')
.mockReturnValueOnce(of(expectedResult));
controller
.find({ tenantId })
.pipe(takeUntil(unsubscribe$))
.subscribe((result) => {
expect(result).toStrictEqual(expectedResult);
unsubscribe$.next();
unsubscribe$.complete();
done();
});
});
});
describe('get', () => {
it('should return a credential definition', (done) => {
const unsubscribe$ = new Subject<void>();
const tenantId = 'exampleTenantId';
const credentialDefinitionId = 'exampleCredentialDefinitionId';
const expectedResult: EventAnonCredsCredentialDefinitionsGetById['data'] =
{
credentialDefinitionId: 'exampleCredentialDefinitionId',
issuerId: 'exampleIssuerId',
schemaId: 'exampleSchemaId',
tag: 'exampleTag',
type: 'CL',
value: {
primary: {},
revocation: {},
},
};
jest
.spyOn(service, 'getCredentialDefinitionById')
.mockReturnValueOnce(of(expectedResult));
controller
.get({ tenantId }, credentialDefinitionId)
.pipe(takeUntil(unsubscribe$))
.subscribe((result) => {
expect(result).toStrictEqual(expectedResult);
unsubscribe$.next();
unsubscribe$.complete();
done();
});
});
});
describe('register', () => {
it('should return a credential definition', (done) => {
const unsubscribe$ = new Subject<void>();
const tenantId = 'exampleTenantId';
const payload: CreateCredentialDefinitionPayload = {
schemaId: 'exampleSchemaId',
tag: 'exampleTag',
};
const expectedResult: EventAnonCredsCredentialDefinitionsRegister['data'] =
{
credentialDefinitionId: 'exampleCredentialDefinitionId',
issuerId: 'exampleIssuerId',
schemaId: 'exampleSchemaId',
tag: 'exampleTag',
type: 'CL',
value: {
primary: {},
revocation: {},
},
};
jest
.spyOn(service, 'registerCredentialDefinition')
.mockReturnValueOnce(of(expectedResult));
controller
.register({ tenantId }, payload)
.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 { CredentialDefinitionsController } from '../credential-definitions.controller.js';
import { CredentialDefinitionsModule } from '../credential-definitions.module.js';
import { CredentialDefinitionsService } from '../credential-definitions.service.js';
describe('CredentialDefinitionsModule', () => {
let credentialDefinitionsModule: CredentialDefinitionsModule;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
imports: [
ClientsModule.registerAsync({
isGlobal: true,
clients: [{ name: NATS_CLIENT, useFactory: () => ({}) }],
}),
CredentialDefinitionsModule,
],
controllers: [CredentialDefinitionsController],
providers: [CredentialDefinitionsService],
}).compile();
credentialDefinitionsModule = moduleRef.get<CredentialDefinitionsModule>(
CredentialDefinitionsModule,
);
});
it('should be defined', () => {
expect(credentialDefinitionsModule).toBeDefined();
});
});
import type { TestingModule } from '@nestjs/testing';
import { Test } from '@nestjs/testing';
import {
EventAnonCredsCredentialDefinitionsGetAll,
EventAnonCredsCredentialDefinitionsGetById,
EventAnonCredsCredentialDefinitionsRegister,
} from '@ocm/shared';
import { Subject, of, takeUntil } from 'rxjs';
import { NATS_CLIENT } from '../../common/constants.js';
import { CredentialDefinitionsService } from '../credential-definitions.service.js';
describe('CredentialDefinitionsService', () => {
let service: CredentialDefinitionsService;
const natsClientMock = { send: jest.fn() };
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{ provide: NATS_CLIENT, useValue: natsClientMock },
CredentialDefinitionsService,
],
}).compile();
service = module.get<CredentialDefinitionsService>(
CredentialDefinitionsService,
);
jest.resetAllMocks();
});
describe('findCredentialDefinitions', () => {
it('should call natsClient.send with the correct pattern and payload', (done) => {
const unsubscribe$ = new Subject<void>();
const tenantId = 'testTenantId';
const expectedResult: EventAnonCredsCredentialDefinitionsGetAll['data'] =
[];
natsClientMock.send.mockReturnValueOnce(
of(new EventAnonCredsCredentialDefinitionsGetAll([], tenantId)),
);
service
.findCredentialDefinitions(tenantId)
.pipe(takeUntil(unsubscribe$))
.subscribe((result) => {
expect(natsClientMock.send).toHaveBeenCalledWith(
EventAnonCredsCredentialDefinitionsGetAll.token,
{ tenantId },
);
expect(result).toStrictEqual(expectedResult);
unsubscribe$.next();
unsubscribe$.complete();
done();
});
});
});
describe('getCredentialDefinitionById', () => {
it('should call natsClient.send with the correct pattern and payload', (done) => {
const unsubscribe$ = new Subject<void>();
const tenantId = 'testTenantId';
const credentialDefinitionId = 'testCredentialDefinitionId';
const expectedResult: EventAnonCredsCredentialDefinitionsGetById['data'] =
{
credentialDefinitionId: 'testCredentialDefinitionId',
issuerId: 'testIssuerId',
schemaId: 'testSchemaId',
tag: 'testTag',
type: 'CL',
value: {
primary: {},
revocation: {},
},
};
natsClientMock.send.mockReturnValueOnce(
of(
new EventAnonCredsCredentialDefinitionsGetById(
expectedResult,
tenantId,
),
),
);
service
.getCredentialDefinitionById(tenantId, credentialDefinitionId)
.pipe(takeUntil(unsubscribe$))
.subscribe((result) => {
expect(natsClientMock.send).toHaveBeenCalledWith(
EventAnonCredsCredentialDefinitionsGetById.token,
{ tenantId, credentialDefinitionId },
);
expect(result).toStrictEqual(expectedResult);
unsubscribe$.next();
unsubscribe$.complete();
done();
});
});
});
describe('createCredentialDefinition', () => {
it('should call natsClient.send with the correct pattern and payload', (done) => {
const unsubscribe$ = new Subject<void>();
const tenantId = 'testTenantId';
const payload = { test: 'payload' };
const expectedResult: EventAnonCredsCredentialDefinitionsRegister['data'] =
{
credentialDefinitionId: 'testCredentialDefinitionId',
issuerId: 'testIssuerId',
schemaId: 'testSchemaId',
tag: 'testTag',
type: 'CL',
value: {
primary: {},
revocation: {},
},
};
natsClientMock.send.mockReturnValueOnce(
of(
new EventAnonCredsCredentialDefinitionsRegister(
expectedResult,
tenantId,
),
),
);
service
.registerCredentialDefinition(tenantId, payload)
.pipe(takeUntil(unsubscribe$))
.subscribe(() => {
expect(natsClientMock.send).toHaveBeenCalledWith(
EventAnonCredsCredentialDefinitionsRegister.token,
{ tenantId, payload },
);
unsubscribe$.next();
unsubscribe$.complete();
done();
});
});
});
});
import {
Body,
Controller,
Get,
HttpStatus,
Param,
Post,
Query,
UseInterceptors,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { MultitenancyParams } from '@ocm/shared';
import { ResponseFormatInterceptor } from '../common/response-format.interceptor.js';
import { CredentialDefinitionsService } from './credential-definitions.service.js';
import { CreateCredentialDefinitionPayload } from './dto/create-credential-definition.dto.js';
@Controller()
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
@UseInterceptors(ResponseFormatInterceptor)
@ApiTags('Credential Definitions')
export class CredentialDefinitionsController {
public constructor(private readonly service: CredentialDefinitionsService) {}
@Get()
@ApiOperation({
summary: 'Fetch a list of credential definitions',
description:
'This call provides a list of credential definitions for a given tenant',
})
@ApiResponse({
status: HttpStatus.OK,
description: 'Credential definitions fetched successfully',
content: {
'application/json': {
schema: {},
examples: {
'Credential definitions fetched successfully': {
value: {
statusCode: 200,
message: 'Credential definitions fetched successfully',
data: [
{
id: '71b784a3',
},
],
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Tenant not found',
content: {
'application/json': {
schema: {},
examples: {
'Tenant not found': {
value: {
statusCode: 404,
message: 'Tenant not found',
error: 'Not Found',
},
},
},
},
},
})
@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',
error: 'Internal Server Error',
},
},
},
},
},
})
public find(
@Query() { tenantId }: MultitenancyParams,
): ReturnType<CredentialDefinitionsService['findCredentialDefinitions']> {
return this.service.findCredentialDefinitions(tenantId);
}
@Get(':credentialDefinitionId')
@ApiOperation({
summary: 'Fetch a credential definition by ID',
description:
'This call provides a credential definition for a given tenant',
})
@ApiResponse({
status: HttpStatus.OK,
description: 'Credential definition fetched successfully',
content: {
'application/json': {
schema: {},
examples: {
'Credential definition fetched successfully': {
value: {
statusCode: 200,
message: 'Credential definition fetched successfully',
data: {
id: '71b784a3',
},
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Credential definition not found',
content: {
'application/json': {
schema: {},
examples: {
'Credential definition not found': {
value: {
statusCode: 404,
message: 'Credential definition not found',
error: 'Not Found',
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Tenant not found',
content: {
'application/json': {
schema: {},
examples: {
'Tenant not found': {
value: {
statusCode: 404,
message: 'Tenant not found',
error: 'Not Found',
},
},
},
},
},
})
@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',
error: 'Internal Server Error',
},
},
},
},
},
})
public get(
@Query() { tenantId }: MultitenancyParams,
@Param('credentialDefinitionId') credentialDefinitionId: string,
): ReturnType<CredentialDefinitionsService['getCredentialDefinitionById']> {
return this.service.getCredentialDefinitionById(
tenantId,
credentialDefinitionId,
);
}
@Post()
@ApiOperation({
summary: 'Create a credential definition',
description:
'This call allows you to create a credential definition for a given tenant',
})
@ApiResponse({
status: HttpStatus.CREATED,
description: 'Credential definition created successfully',
content: {
'application/json': {
schema: {},
examples: {
'Credential definition created successfully': {
value: {
statusCode: 201,
message: 'Credential definition created successfully',
data: {
id: '71b784a3',
},
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Tenant not found',
content: {
'application/json': {
schema: {},
examples: {
'Tenant not found': {
value: {
statusCode: 404,
message: 'Tenant not found',
error: 'Not Found',
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: 'Invalid request',
content: {
'application/json': {
schema: {},
examples: {
'Invalid request': {
value: {
statusCode: 400,
message: 'Invalid request',
error: 'Bad Request',
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.CONFLICT,
description: 'Credential definition already exists',
content: {
'application/json': {
schema: {},
examples: {
'Credential definition already exists': {
value: {
statusCode: 409,
message: 'Credential definition already exists',
error: 'Conflict',
},
},
},
},
},
})
@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',
error: 'Internal Server Error',
},
},
},
},
},
})
public register(
@Query() { tenantId }: MultitenancyParams,
@Body() payload: CreateCredentialDefinitionPayload,
): ReturnType<CredentialDefinitionsService['registerCredentialDefinition']> {
return this.service.registerCredentialDefinition(tenantId, payload);
}
}
import { Module } from '@nestjs/common';
import { CredentialDefinitionsController } from './credential-definitions.controller.js';
import { CredentialDefinitionsService } from './credential-definitions.service.js';
@Module({
providers: [CredentialDefinitionsService],
controllers: [CredentialDefinitionsController],
})
export class CredentialDefinitionsModule {}
import type { EventAnonCredsCredentialDefinitionsGetAllInput } from '@ocm/shared';
import type { Observable } from 'rxjs';
import { Inject, Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import {
EventAnonCredsCredentialDefinitionsGetAll,
EventAnonCredsCredentialDefinitionsGetById,
EventAnonCredsCredentialDefinitionsRegister,
} from '@ocm/shared';
import { map } from 'rxjs';
import { NATS_CLIENT } from '../common/constants.js';
@Injectable()
export class CredentialDefinitionsService {
public constructor(
@Inject(NATS_CLIENT) private readonly natsClient: ClientProxy,
) {}
public findCredentialDefinitions(
tenantId: string,
): Observable<EventAnonCredsCredentialDefinitionsGetAll['data']> {
return this.natsClient
.send<
EventAnonCredsCredentialDefinitionsGetAll,
EventAnonCredsCredentialDefinitionsGetAllInput
>(EventAnonCredsCredentialDefinitionsGetAll.token, { tenantId })
.pipe(map((result) => result.data));
}
public getCredentialDefinitionById(
tenantId: string,
credentialDefinitionId: string,
): Observable<EventAnonCredsCredentialDefinitionsGetById['data']> {
return this.natsClient
.send(EventAnonCredsCredentialDefinitionsGetById.token, {
tenantId,
credentialDefinitionId,
})
.pipe(map((result) => result.data));
}
public registerCredentialDefinition(
tenantId: string,
payload: unknown,
): Observable<EventAnonCredsCredentialDefinitionsRegister['data']> {
return this.natsClient
.send(EventAnonCredsCredentialDefinitionsRegister.token, {
tenantId,
payload,
})
.pipe(map((result) => result.data));
}
}
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';
export class CreateCredentialDefinitionPayload {
@IsString()
@IsNotEmpty()
@ApiProperty()
public schemaId: string;
@IsString()
@IsNotEmpty()
@ApiProperty()
public tag: string;
}
......@@ -8,7 +8,7 @@ import {
HttpHealthIndicator,
} from '@nestjs/terminus';
@Controller('health')
@Controller()
export class HealthController {
public constructor(
private readonly config: ConfigService,
......
export interface Response<T = unknown> {
statusCode: number;
message: string;
data?: T;
error?: unknown;
}
import type { TestingModule } from '@nestjs/testing';
import type { EventAnonCredsSchemasRegisterInput } from '@ocm/shared';
import { Test } from '@nestjs/testing';
import {
......@@ -31,23 +32,20 @@ describe('SchemasService', () => {
describe('getAll', () => {
it('should return the data from NATS client', (done) => {
const unsubscribe$ = new Subject<void>();
const payload = {
tenantId: 'mocked tenantId',
endpoint: EventAnonCredsSchemasGetAll.token,
};
const tenantId = 'mocked tenantId';
const expectedResult: EventAnonCredsSchemasGetAll['data'] = [];
natsClientMock.send.mockReturnValueOnce(
of(new EventAnonCredsSchemasGetAll([], payload.tenantId)),
of(new EventAnonCredsSchemasGetAll([], tenantId)),
);
service
.getAll(payload)
.getAll(tenantId)
.pipe(takeUntil(unsubscribe$))
.subscribe((result) => {
expect(natsClientMock.send).toHaveBeenCalledWith(
{ endpoint: EventAnonCredsSchemasGetAll.token },
payload,
EventAnonCredsSchemasGetAll.token,
{ tenantId },
);
expect(result).toStrictEqual(expectedResult);
......@@ -63,10 +61,8 @@ describe('SchemasService', () => {
describe('getById', () => {
it('should return the data from NATS client', (done) => {
const unsubscribe$ = new Subject<void>();
const payload = {
tenantId: 'mocked tenantId',
schemaId: 'mocked id',
};
const tenantId = 'mocked tenantId';
const schemaId = 'mocked id';
const expectedResult: EventAnonCredsSchemasGetById['data'] = {
issuerId: 'mocked issuerDid',
name: 'mocked name',
......@@ -75,16 +71,16 @@ describe('SchemasService', () => {
};
natsClientMock.send.mockReturnValueOnce(
of(new EventAnonCredsSchemasGetById(expectedResult, payload.tenantId)),
of(new EventAnonCredsSchemasGetById(expectedResult, tenantId)),
);
service
.getById(payload)
.getById(tenantId, schemaId)
.pipe(takeUntil(unsubscribe$))
.subscribe((result) => {
expect(natsClientMock.send).toHaveBeenCalledWith(
{ endpoint: EventAnonCredsSchemasGetById.token },
payload,
EventAnonCredsSchemasGetById.token,
{ tenantId, schemaId },
);
expect(result).toStrictEqual(expectedResult);
......@@ -100,31 +96,31 @@ describe('SchemasService', () => {
describe('register', () => {
it('should return the data from NATS client', (done) => {
const unsubscribe$ = new Subject<void>();
const payload = {
tenantId: 'mocked tenantId',
const tenantId = 'mocked tenantId';
const payload: Omit<EventAnonCredsSchemasRegisterInput, 'tenantId'> = {
issuerDid: 'mocked issuerDid',
name: 'mocked name',
version: '1.0.0',
attributeNames: ['mocked attribute1', 'mocked attribute2'],
};
const expectedResult: EventAnonCredsSchemasRegister['data'] = {
issuerId: 'mocked issuerDid',
issuerId: 'mocked issuerId',
name: 'mocked name',
version: '1.0.0',
attrNames: ['mocked attribute1', 'mocked attribute2'],
};
natsClientMock.send.mockReturnValueOnce(
of(new EventAnonCredsSchemasRegister(expectedResult, payload.tenantId)),
of(new EventAnonCredsSchemasRegister(expectedResult, tenantId)),
);
service
.register(payload)
.register(tenantId, payload)
.pipe(takeUntil(unsubscribe$))
.subscribe((result) => {
expect(natsClientMock.send).toHaveBeenCalledWith(
{ endpoint: EventAnonCredsSchemasRegister.token },
payload,
EventAnonCredsSchemasRegister.token,
{ ...payload, tenantId },
);
expect(result).toStrictEqual(expectedResult);
......
......@@ -13,27 +13,24 @@ import {
Param,
Post,
Query,
UseInterceptors,
UsePipes,
ValidationPipe,
Version,
} from '@nestjs/common';
import {
ApiBody,
ApiOperation,
ApiParam,
ApiQuery,
ApiResponse,
ApiTags,
} from '@nestjs/swagger';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { MultitenancyParams } from '@ocm/shared';
import { Observable, of, switchMap } from 'rxjs';
import { ResponseFormatInterceptor } from '../common/response-format.interceptor.js';
import { GetByIdParams } from './dto/get-by-id.dto.js';
import { RegisterSchemaPayload } from './dto/register-schema.dto.js';
import { TenantIdParam } from './dto/tenant-id.dto.js';
import { SchemasService } from './schemas.service.js';
@Controller('schemas')
@Controller()
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
@UseInterceptors(ResponseFormatInterceptor)
@ApiTags('Schemas')
export class SchemasController {
public constructor(private readonly schemasService: SchemasService) {}
......@@ -44,34 +41,64 @@ export class SchemasController {
summary: 'Fetch a list of schemas',
description: 'This call provides a list of schemas for a given tenant',
})
@ApiQuery({ name: 'tenantId', required: true })
@ApiResponse({
status: HttpStatus.OK,
description: 'Schemas fetched successfully',
content: {
// TBD
'application/json': {
schema: {},
examples: {
'Schemas fetched successfully': {
value: {
statusCode: 200,
message: 'Schemas fetched successfully',
data: [],
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Tenant not found',
content: {
// TBD
'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: {
// TBD
'application/json': {
schema: {},
examples: {
'Internal server error': {
value: {
statusCode: 500,
message: 'Internal server error',
error: 'Internal Server Error',
},
},
},
},
},
})
public getAll(
@Query() { tenantId }: TenantIdParam,
@Query() { tenantId }: MultitenancyParams,
): Observable<EventAnonCredsSchemasGetAll['data']> {
return this.schemasService.getAll({
tenantId,
});
return this.schemasService.getAll(tenantId);
}
@Version('1')
......@@ -81,53 +108,92 @@ export class SchemasController {
description:
'This call allows you to retrieve schema data for a given tenant by specifying the `schemaId`.',
})
@ApiParam({ name: 'schemaId', required: true })
@ApiQuery({ name: 'tenantId', required: true })
@ApiResponse({
status: HttpStatus.OK,
description: 'Schema fetched successfully',
content: {
// TBD
'application/json': {
schema: {},
examples: {
'Schema fetched successfully': {
value: {
statusCode: 200,
message: 'Schema fetched successfully',
data: {
id: '71b784a3',
},
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Tenant not found',
content: {
// TBD
'application/json': {
schema: {},
examples: {
'Tenant not found': {
value: {
statusCode: 404,
message: 'Tenant not found',
data: null,
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Schema not found',
content: {
// TBD
'application/json': {
schema: {},
examples: {
'Schema not found': {
value: {
statusCode: 404,
message: 'Schema not found',
data: null,
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.INTERNAL_SERVER_ERROR,
description: 'Internal server error',
content: {
// TBD
'application/json': {
schema: {},
examples: {
'Internal server error': {
value: {
statusCode: 500,
message: 'Internal server error',
error: 'Internal Server Error',
},
},
},
},
},
})
public getById(
@Param() { schemaId }: GetByIdParams,
@Query() { tenantId }: TenantIdParam,
@Query() { tenantId }: MultitenancyParams,
): Observable<EventAnonCredsSchemasGetById['data']> {
return this.schemasService
.getById({
tenantId,
schemaId,
})
.pipe(
switchMap((schema) => {
if (schema === null) {
throw new NotFoundException(`Schema with id ${schemaId} not found`);
}
return of(schema);
}),
);
return this.schemasService.getById(tenantId, schemaId).pipe(
switchMap((schema) => {
if (schema === null) {
throw new NotFoundException(`Schema with id ${schemaId} not found`);
}
return of(schema);
}),
);
}
@Version('1')
......@@ -137,50 +203,102 @@ export class SchemasController {
description:
'This call provides the capability to create new schema on ledger by name, author, version, schema attributes and type. Later this schema can be used to issue new credential definition. This call returns an information about created schema.',
})
@ApiQuery({ name: 'tenantId', required: true })
@ApiBody({ type: RegisterSchemaPayload })
@ApiResponse({
status: HttpStatus.CREATED,
description: 'Schema registered successfully',
content: {
'application/json': {},
'application/json': {
schema: {},
examples: {
'Schema registered successfully': {
value: {
statusCode: 201,
message: 'Schema registered successfully',
data: {
id: '71b784a3',
},
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Tenant not found',
content: {
'application/json': {},
'application/json': {
schema: {},
examples: {
'Tenant not found': {
value: {
statusCode: 404,
message: 'Tenant not found',
data: null,
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: 'All fields are required for schema registration',
content: {
'application/json': {},
'application/json': {
schema: {},
examples: {
'All fields are required for schema registration': {
value: {
statusCode: 400,
message: 'All fields are required for schema registration',
error: 'Bad Request',
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.CONFLICT,
description: 'Schema already exists',
content: {
'application/json': {},
'application/json': {
schema: {},
examples: {
'Schema already exists': {
value: {
statusCode: 409,
message: 'Schema already exists',
error: 'Conflict',
},
},
},
},
},
})
@ApiResponse({
status: HttpStatus.INTERNAL_SERVER_ERROR,
description: 'Internal server error',
content: {
'application/json': {},
'application/json': {
schema: {},
examples: {
'Internal server error': {
value: {
statusCode: 500,
message: 'Internal server error',
error: 'Internal Server Error',
},
},
},
},
},
})
public register(
@Query() { tenantId }: TenantIdParam,
@Query() { tenantId }: MultitenancyParams,
@Body() payload: RegisterSchemaPayload,
): Observable<EventAnonCredsSchemasRegister['data']> {
return this.schemasService.register({
...payload,
tenantId,
});
return this.schemasService.register(tenantId, payload);
}
}
......@@ -22,40 +22,36 @@ export class SchemasService {
) {}
public getAll(
payload: EventAnonCredsSchemasGetAllInput,
tenantId: string,
): Observable<EventAnonCredsSchemasGetAll['data']> {
const pattern = { endpoint: EventAnonCredsSchemasGetAll.token };
return this.natsClient
.send<EventAnonCredsSchemasGetAll, EventAnonCredsSchemasGetAllInput>(
pattern,
payload,
EventAnonCredsSchemasGetAll.token,
{ tenantId },
)
.pipe(map((result) => result.data));
}
public getById(
payload: EventAnonCredsSchemasGetByIdInput,
tenantId: string,
schemaId: EventAnonCredsSchemasGetByIdInput['schemaId'],
): Observable<EventAnonCredsSchemasGetById['data']> {
const pattern = { endpoint: EventAnonCredsSchemasGetById.token };
return this.natsClient
.send<EventAnonCredsSchemasGetById, EventAnonCredsSchemasGetByIdInput>(
pattern,
payload,
EventAnonCredsSchemasGetById.token,
{ tenantId, schemaId },
)
.pipe(map((result) => result.data));
}
public register(
payload: EventAnonCredsSchemasRegisterInput,
tenantId: string,
payload: Omit<EventAnonCredsSchemasRegisterInput, 'tenantId'>,
): Observable<EventAnonCredsSchemasRegister['data']> {
const pattern = { endpoint: EventAnonCredsSchemasRegister.token };
return this.natsClient
.send<EventAnonCredsSchemasRegister, EventAnonCredsSchemasRegisterInput>(
pattern,
payload,
EventAnonCredsSchemasRegister.token,
{ ...payload, tenantId },
)
.pipe(map((result) => result.data));
}
......
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"typeCheck": true,
"builder": {
"type": "swc",
"options": {
"swcrcPath": "../../.swcrc"
}
}
}
"sourceRoot": "src"
}
......@@ -25,9 +25,15 @@
"@aries-framework/core": "0.4.2",
"@aries-framework/tenants": "^0.4.2",
"@elastic/ecs-winston-format": "^1.5.0",
"@nestjs/axios": "^3.0.1",
"@nestjs/common": "^10.2.10",
"@nestjs/config": "^3.1.1",
"@nestjs/microservices": "^10.2.10",
"@nestjs/swagger": "^7.1.16",
"@nestjs/terminus": "^10.1.1",
"axios": "^1.6.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"joi": "^17.6.0",
"nats": "^2.18.0",
"rxjs": "^7.2.0",
......
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';
export class TenantIdParam {
export class MultitenancyParams {
@IsString()
@IsNotEmpty()
@ApiProperty({
description: 'The tenant ID to use for the request',
format: 'string',
required: true,
description: 'Specifies the tenant ID',
})
public tenantId: string;
}
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsNumber, IsOptional, Min } from 'class-validator';
export class PaginationParams {
@IsNumber()
@Min(1)
@Type(() => Number)
@IsOptional()
@ApiProperty({
required: false,
description: 'Specifies the page number of a result set',
})
public page?: number;
@IsNumber()
@Min(1)
@Type(() => Number)
@IsOptional()
@ApiProperty({
required: false,
description:
'Specifies the number of items to return in a single page of a result set',
})
public pageSize?: number;
}
......@@ -7,7 +7,7 @@ describe('Did Events', () => {
jest.requireActual('../didEvents');
});
it('should create get public did event', () => {
it.skip('should create get public did event', () => {
const doc = new DidDocument({ id: 'did:web:123.com' });
const event = new EventDidsPublicDid(doc, 'tenantId');
......