Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: required attributes in create schema API #521

Merged
merged 3 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions apps/api-gateway/src/dtos/create-schema.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IsArray, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator';
import { ArrayMinSize, IsArray, IsBoolean, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator';

import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
Expand All @@ -23,6 +23,11 @@ class AttributeValue {
@Transform(({ value }) => trim(value))
@IsNotEmpty({ message: 'displayName is required' })
displayName: string;

@ApiProperty()
@IsBoolean()
@IsNotEmpty({ message: 'isRequired property is required' })
isRequired: boolean;
}

export class CreateSchemaDto {
Expand All @@ -44,12 +49,14 @@ export class CreateSchemaDto {
{
attributeName: 'name',
schemaDataType: 'string',
displayName: 'Name'
displayName: 'Name',
isRequired: 'true'
}
]
})
@IsArray({ message: 'attributes must be an array' })
@IsNotEmpty({ message: 'attributes are required' })
@ArrayMinSize(1)
@ValidateNested({ each: true })
@Type(() => AttributeValue)
attributes: AttributeValue[];
Expand Down
41 changes: 28 additions & 13 deletions apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
import { ApiExtraModels, ApiProperty } from '@nestjs/swagger';
import { ApiProperty } from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
import { IsArray, IsBoolean, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator';
import { ArrayMinSize, IsArray, IsBoolean, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator';
import { trim } from '@credebl/common/cast.helper';


@ApiExtraModels()

class AttributeValue {
class AttributeValues {

@ApiProperty()
@IsString()
@IsNotEmpty({ message: 'attributeName is required.' })
@Transform(({ value }) => trim(value))
@IsNotEmpty({ message: 'attributeName is required' })
attributeName: string;

@ApiProperty()
@IsString()
@IsNotEmpty({ message: 'schemaDataType is required.' })
schemaDataType: string;
@Transform(({ value }) => trim(value))
@IsNotEmpty({ message: 'displayName is required' })
displayName: string;

@ApiProperty()
@IsBoolean()
@IsNotEmpty({ message: 'isRequired property is required' })
isRequired: boolean;

@ApiProperty()
@IsString()
@IsNotEmpty({ message: 'displayName is required.' })
displayName: string;
@Transform(({ value }) => trim(value))
@IsNotEmpty({ message: 'schemaDataType is required' })
schemaDataType: string;

}


Expand All @@ -37,17 +47,22 @@ export class RequestSchemaDto {
version: string;

@ApiProperty({
type: [AttributeValues],
'example': [
{
attributeName: 'name',
schemaDataType: 'string',
displayName: 'Name'
displayName: 'Name',
isRequired: 'true'
}
]
})
@IsArray({ message: 'attributes must be an array' })
@IsNotEmpty({ message: 'please provide valid attributes' })
attributes: AttributeValue[];
@IsNotEmpty({ message: 'attributes are required' })
@ArrayMinSize(1)
@ValidateNested({ each: true })
@Type(() => AttributeValues)
attributes: AttributeValues[];

@ApiProperty()
@IsBoolean({ message: 'endorse must be a boolean.' })
Expand Down
31 changes: 22 additions & 9 deletions apps/api-gateway/src/issuance/dtos/issuance.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ class Attribute {
@IsDefined()
@Transform(({ value }) => trim(value))
value: string;

@ApiProperty()
@IsBoolean()
@IsNotEmpty({ message: 'isRequired property is required' })
isRequired: boolean;

}

class CredentialsIssuanceDto {
Expand Down Expand Up @@ -77,17 +83,24 @@ class CredentialsIssuanceDto {
}

export class OOBIssueCredentialDto extends CredentialsIssuanceDto {

@ApiProperty({ example: [{ 'value': 'string', 'name': 'string' }] })
@IsArray()
@ValidateNested({ each: true })
@ArrayMinSize(1)
@Type(() => Attribute)
attributes: Attribute[];
@ApiProperty({
example: [
{
value: 'string',
name: 'string',
isRequired: 'boolean'
}
]
})
@IsArray()
@ValidateNested({ each: true })
@ArrayMinSize(1)
@Type(() => Attribute)
attributes: Attribute[];
}

class CredentialOffer {
@ApiProperty({ example: [{ 'value': 'string', 'name': 'string' }] })
@ApiProperty({ example: [{ 'value': 'string', 'name': 'string', 'isRequired':'boolean' }] })
@IsNotEmpty({ message: 'Attribute name is required' })
@IsArray({ message: 'Attributes should be an array' })
@ValidateNested({ each: true })
Expand Down Expand Up @@ -199,7 +212,7 @@ export class CredentialAttributes {
}

export class OOBCredentialDtoWithEmail {
@ApiProperty({ example: [{ 'emailId': 'abc@example.com', 'attributes': [{ 'value': 'string', 'name': 'string' }] }] })
@ApiProperty({ example: [{ 'emailId': 'abc@example.com', 'attributes': [{ 'value': 'string', 'name': 'string', 'isRequired':'boolean' }] }] })
@IsNotEmpty({ message: 'Please provide valid attributes' })
@IsArray({ message: 'attributes should be array' })
@ArrayMaxSize(Number(process.env.OOB_BATCH_SIZE), { message: `Limit reached (${process.env.OOB_BATCH_SIZE} credentials max). Easily handle larger batches via seamless CSV file uploads` })
Expand Down
2 changes: 2 additions & 0 deletions apps/issuance/interfaces/issuance.interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { IUserRequest } from '@credebl/user-request/user-request.interface';
import { IUserRequestInterface } from 'apps/agent-service/src/interface/agent-service.interface';

export interface IAttributes {
attributeName: string;
isRequired: boolean;
name: string;
value: string;
}
Expand Down
58 changes: 57 additions & 1 deletion apps/issuance/src/issuance.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,25 @@ export class IssuanceService {
async sendCredentialCreateOffer(payload: IIssuance): Promise<ICreateOfferResponse> {
try {
const { orgId, credentialDefinitionId, comment, connectionId, attributes } = payload || {};

const attrError = [];
if (0 < attributes?.length) {
attributes?.forEach((attribute, i) => {

if (attribute.isRequired && !attribute.value) {
attrError.push(`attributes.${i}.Value of "${attribute.name}" is required`);
return true;
}

return attribute.isRequired && !attribute.value;
});

if (0 < attrError.length) {
throw new BadRequestException(attrError);
}
}


const agentDetails = await this.issuanceRepository.getAgentEndPoint(orgId);

if (!agentDetails) {
Expand Down Expand Up @@ -118,9 +137,26 @@ export class IssuanceService {
async sendCredentialOutOfBand(payload: OOBIssueCredentialDto): Promise<{ response: object; }> {
try {
const { orgId, credentialDefinitionId, comment, attributes, protocolVersion } = payload;

const attrError = [];
if (0 < attributes?.length) {
attributes?.forEach((attribute, i) => {

if (attribute.isRequired && !attribute.value) {
attrError.push(`attributes.${i}.Value of "${attribute.name}" is required`);
return true;
}

return attribute.isRequired && !attribute.value;
});

if (0 < attrError.length) {
throw new BadRequestException(attrError);
}
}

const agentDetails = await this.issuanceRepository.getAgentEndPoint(orgId);
// eslint-disable-next-line camelcase
// const platformConfig: platform_config = await this.issuanceRepository.getPlatformConfigDetails();

const { agentEndPoint } = agentDetails;
if (!agentDetails) {
Expand Down Expand Up @@ -351,6 +387,26 @@ export class IssuanceService {
emailId
} = outOfBandCredential;

const attrError = [];
if (0 < credentialOffer?.length) {
credentialOffer?.forEach((credential, i) => {
credential.attributes.forEach((attribute, i2) => {

if (attribute.isRequired && !attribute.value) {
attrError.push(`credentialOffer.${i}.attributes.${i2}.Value of "${attribute.name}" is required`);
return true;
}

return attribute.isRequired && !attribute.value;
});

});

if (0 < attrError.length) {
throw new BadRequestException(attrError);
}
}

const agentDetails = await this.issuanceRepository.getAgentEndPoint(orgId);
if (!agentDetails) {
throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ export interface ISchema {
}

export interface IAttributeValue {
isRequired: boolean;
attributeName: string;
schemaDataType: string;
displayName: string
displayName: string;

}

export interface ISchemaPayload {
Expand Down
15 changes: 13 additions & 2 deletions apps/ledger/src/schema/schema.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export class SchemaService extends BaseService {
schema: ISchemaPayload,
user: IUserRequestInterface,
orgId: string
): Promise<ISchemaData> {
): Promise<ISchemaData> {

let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY);
if (!apiKey || null === apiKey || undefined === apiKey) {
Expand Down Expand Up @@ -82,7 +82,8 @@ export class SchemaService extends BaseService {
const trimmedAttributes = schema.attributes.map(attribute => ({
attributeName: attribute.attributeName.trim(),
schemaDataType: attribute.schemaDataType,
displayName: attribute.displayName.trim()
displayName: attribute.displayName.trim(),
isRequired: attribute.isRequired
}));


Expand Down Expand Up @@ -122,7 +123,17 @@ export class SchemaService extends BaseService {
const did = schema.orgDid?.split(':').length >= 4 ? schema.orgDid : orgDid;

const orgAgentType = await this.schemaRepository.getOrgAgentType(getAgentDetails.org_agents[0].orgAgentTypeId);

const attributeArray = trimmedAttributes.map(item => item.attributeName);

const isRequiredAttributeExists = trimmedAttributes.some(attribute => attribute.isRequired);

if (!isRequiredAttributeExists) {
throw new BadRequestException(
ResponseMessages.schema.error.atLeastOneRequired
);
}

let schemaResponseFromAgentService;
if (OrgAgentType.DEDICATED === orgAgentType) {
const issuerId = did;
Expand Down
3 changes: 2 additions & 1 deletion libs/common/src/response-messages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ export const ResponseMessages = {
credentialDefinitionNotFound: 'No credential definition exist',
notStoredCredential: 'User credential not stored',
agentDetailsNotFound: 'Agent details not found',
failedFetchSchema: 'Failed to fetch schema data'
failedFetchSchema: 'Failed to fetch schema data',
atLeastOneRequired: 'At least one of the attributes should have isReuired as `true`'
}
},
credentialDefinition: {
Expand Down