Skip to content

Commit

Permalink
feat: multiselect connection while issuance (#629)
Browse files Browse the repository at this point in the history
* feat: multiselect connections while issuance

Signed-off-by: bhavanakarwade <bhavana.karwade@ayanworks.com>

* feat: multi select connections functionality while issuance

Signed-off-by: bhavanakarwade <bhavana.karwade@ayanworks.com>

* feat: multi select connections

Signed-off-by: bhavanakarwade <bhavana.karwade@ayanworks.com>

* refactor: modify error message

Signed-off-by: bhavanakarwade <bhavana.karwade@ayanworks.com>

* fix: resolved comments

Signed-off-by: bhavanakarwade <bhavana.karwade@ayanworks.com>

* resolved comments

Signed-off-by: bhavanakarwade <bhavana.karwade@ayanworks.com>

---------

Signed-off-by: bhavanakarwade <bhavana.karwade@ayanworks.com>
Signed-off-by: KulkarniShashank <shashank.kulkarni@ayanworks.com>
  • Loading branch information
bhavanakarwade authored and KulkarniShashank committed Sep 11, 2024
1 parent 88cde44 commit e54a711
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 84 deletions.
30 changes: 5 additions & 25 deletions apps/api-gateway/src/issuance/dtos/issuance.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class Credential {
@IsObject()
public credentialStatus?: JsonLdCredentialDetailCredentialStatus;
}
class Attribute {
export class Attribute {
@ApiProperty()
@IsString({ message: 'Attribute name should be string' })
@IsNotEmpty({ message: 'Attribute name is required' })
Expand All @@ -118,18 +118,15 @@ class Attribute {
@ApiProperty({ default: false })
@IsBoolean()
@IsOptional()
@IsNotEmpty({ message: 'isRequired property is required' })
isRequired?: boolean = false;

}

class CredentialsIssuanceDto {
export class CredentialsIssuanceDto {
@ApiProperty({ example: 'string' })
@IsNotEmpty({ message: 'Please provide valid credential definition id' })
@IsString({ message: 'credential definition id should be string' })
@IsNotEmpty({ message: 'Credential definition Id is required' })
@IsString({ message: 'Credential definition id should be string' })
@Transform(({ value }) => value.trim())
@IsOptional()
credentialDefinitionId?: string;
credentialDefinitionId: string;

@ApiProperty({ example: 'string' })
@IsNotEmpty({ message: 'Please provide valid comment' })
Expand Down Expand Up @@ -270,23 +267,6 @@ class CredentialOffer {

}

export class IssueCredentialDto extends OOBIssueCredentialDto {
@ApiProperty({ example: 'string' })
@IsNotEmpty({ message: 'connectionId is required' })
@IsString({ message: 'connectionId should be string' })
@Transform(({ value }) => trim(value))
connectionId: string;

@ApiPropertyOptional()
@IsOptional()
@IsString({ message: 'auto accept proof must be in string' })
@IsNotEmpty({ message: 'please provide valid auto accept proof' })
@IsEnum(AutoAccept, {
message: `Invalid auto accept credential. It should be one of: ${Object.values(AutoAccept).join(', ')}`
})
autoAcceptCredential?: string;
}

export class IssuanceDto {
@ApiProperty()
@IsOptional()
Expand Down
71 changes: 71 additions & 0 deletions apps/api-gateway/src/issuance/dtos/multi-connection.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { ArrayMaxSize, ArrayMinSize, IsArray, IsBoolean, IsEnum, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator';
import { Transform, Type } from 'class-transformer';

import { AutoAccept } from '@credebl/enum/enum';
import { trim } from '@credebl/common/cast.helper';
import { Attribute, CredentialsIssuanceDto } from './issuance.dto';

class ConnectionAttributes {
@ApiProperty({ example: 'string' })
@IsNotEmpty({ message: 'connectionId is required' })
@IsString({ message: 'connectionId should be string' })
@Transform(({ value }) => trim(value))
connectionId: string;

@ApiProperty({
example: [
{
value: 'string',
name: 'string'
}
]
})
@IsArray()
@ValidateNested({ each: true })
@ArrayMinSize(1)
@IsNotEmpty({ message: 'Please provide valid attributes' })
@Type(() => Attribute)
attributes: Attribute[];
}

export class IssueCredentialDto extends CredentialsIssuanceDto {
@ApiProperty({
example: [
{
connectionId: 'string',
attributes: [
{
value: 'string',
name: 'string'
}
]
}
]
})
@IsArray()
@ValidateNested({ each: true })
@ArrayMinSize(1)
@ArrayMaxSize(Number(process.env.OOB_BATCH_SIZE), { message: `Limit reached (${process.env.OOB_BATCH_SIZE} connections max).` })
@IsNotEmpty({ message: 'credentialData is required' })
@Type(() => ConnectionAttributes)
credentialData: ConnectionAttributes[];

@ApiPropertyOptional()
@IsOptional()
@IsString({ message: 'auto accept proof must be in string' })
@IsNotEmpty({ message: 'please provide valid auto accept proof' })
@IsEnum(AutoAccept, {
message: `Invalid auto accept credential. It should be one of: ${Object.values(AutoAccept).join(', ')}`
})
autoAcceptCredential?: string;

@ApiProperty({
example: false
})
@IsOptional()
@IsNotEmpty()
@IsBoolean({message: 'isShortenUrl must be boolean'})
isShortenUrl?: boolean;

}
7 changes: 4 additions & 3 deletions apps/api-gateway/src/issuance/issuance.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ import {
ClientDetails,
FileParameter,
IssuanceDto,
IssueCredentialDto,
OutOfBandCredentialOfferDto,
OOBCredentialDtoWithEmail,
OOBIssueCredentialDto,
PreviewFileDetails
} from './dtos/issuance.dto';
import { IUserRequest } from '@credebl/user-request/user-request.interface';
Expand All @@ -62,6 +62,7 @@ import { RpcException } from '@nestjs/microservices';
/* eslint-disable @typescript-eslint/no-unused-vars */
import { user } from '@prisma/client';
import { IGetAllIssuedCredentialsDto } from './dtos/get-all-issued-credentials.dto';
import { IssueCredentialDto } from './dtos/multi-connection.dto';

@Controller()
@UseFilters(CustomExceptionFilter)
Expand Down Expand Up @@ -544,7 +545,7 @@ export class IssuanceController {
issueCredentialDto.orgId = orgId;

const getCredentialDetails = await this.issueCredentialService.sendCredentialCreateOffer(issueCredentialDto, user);

const finalResponse: IResponse = {
statusCode: HttpStatus.CREATED,
message: ResponseMessages.issuance.success.create,
Expand Down
12 changes: 5 additions & 7 deletions apps/api-gateway/src/issuance/issuance.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { Injectable, Inject } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { BaseService } from 'libs/service/base.service';
import { IUserRequest } from '@credebl/user-request/user-request.interface';
import { ClientDetails, FileParameter, IssuanceDto, IssueCredentialDto, OOBCredentialDtoWithEmail, OOBIssueCredentialDto, PreviewFileDetails } from './dtos/issuance.dto';
import { ClientDetails, FileParameter, IssuanceDto, OOBCredentialDtoWithEmail, OOBIssueCredentialDto, PreviewFileDetails } from './dtos/issuance.dto';
import { FileExportResponse, IIssuedCredentialSearchParams, IssueCredentialType, RequestPayload } from './interfaces';
import { IIssuedCredential } from '@credebl/common/interfaces/issuance.interface';
import { ICreateOfferResponse } from 'apps/issuance/interfaces/issuance.interfaces';
import { IssueCredentialDto } from './dtos/multi-connection.dto';

@Injectable()
export class IssuanceService extends BaseService {
Expand All @@ -18,13 +18,11 @@ export class IssuanceService extends BaseService {
super('IssuanceService');
}

sendCredentialCreateOffer(issueCredentialDto: IssueCredentialDto, user: IUserRequest): Promise<{
response: object;
}> {
sendCredentialCreateOffer(issueCredentialDto: IssueCredentialDto, user: IUserRequest): Promise<object> {

const payload = { attributes: issueCredentialDto.attributes, comment: issueCredentialDto.comment, credentialDefinitionId: issueCredentialDto.credentialDefinitionId, connectionId: issueCredentialDto.connectionId, orgId: issueCredentialDto.orgId, protocolVersion: issueCredentialDto.protocolVersion, autoAcceptCredential: issueCredentialDto.autoAcceptCredential, user };
const payload = { comment: issueCredentialDto.comment, credentialDefinitionId: issueCredentialDto.credentialDefinitionId, credentialData: issueCredentialDto.credentialData, orgId: issueCredentialDto.orgId, protocolVersion: issueCredentialDto.protocolVersion, autoAcceptCredential: issueCredentialDto.autoAcceptCredential, user };

return this.sendNats(this.issuanceProxy, 'send-credential-create-offer', payload);
return this.sendNatsMessage(this.issuanceProxy, 'send-credential-create-offer', payload);
}

sendCredentialOutOfBand(issueCredentialDto: OOBIssueCredentialDto): Promise<{
Expand Down
3 changes: 1 addition & 2 deletions apps/connection/src/connection.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export class ConnectionRepository {
});
return agentDetails;

const agentDetails = await this.prisma.connections.upsert({
return this.prisma.connections.upsert({
where: {
connectionId: connectionDto?.id
},
Expand All @@ -148,7 +148,6 @@ export class ConnectionRepository {
orgId: organisationId
}
});
return agentDetails;
} catch (error) {
this.logger.error(`Error in saveConnectionWebhook: ${error.message} `);
throw error;
Expand Down
9 changes: 6 additions & 3 deletions apps/issuance/interfaces/issuance.interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,23 @@ export interface IAttributes {
value: string;
isRequired?: boolean;
}

interface ICredentialsAttributes {
connectionId: string;
attributes: IAttributes[];
}
export interface IIssuance {
user?: IUserRequest;
credentialDefinitionId: string;
comment?: string;
connectionId: string;
attributes: IAttributes[];
credentialData: ICredentialsAttributes[];
orgId: string;
autoAcceptCredential?: AutoAccept,
protocolVersion?: string;
goalCode?: string,
parentThreadId?: string,
willConfirm?: boolean,
label?: string

}

interface IIndy {
Expand Down
6 changes: 2 additions & 4 deletions apps/issuance/src/issuance.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ export class IssuanceController {
constructor(private readonly issuanceService: IssuanceService) { }

@MessagePattern({ cmd: 'send-credential-create-offer' })
async sendCredentialCreateOffer(payload: IIssuance): Promise<string> {

const { orgId, user, credentialDefinitionId, comment, connectionId, attributes, autoAcceptCredential } = payload;
return this.issuanceService.sendCredentialCreateOffer(orgId, user, credentialDefinitionId, comment, connectionId, attributes, autoAcceptCredential);
async sendCredentialCreateOffer(payload: IIssuance): Promise<PromiseSettledResult<ICreateOfferResponse>[]> {
return this.issuanceService.sendCredentialCreateOffer(payload);
}

@MessagePattern({ cmd: 'send-credential-create-offer-oob' })
Expand Down
81 changes: 41 additions & 40 deletions apps/issuance/src/issuance.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,10 @@ export class IssuanceService {
private readonly emailData: EmailDto
) { }


async sendCredentialCreateOffer(payload: IIssuance): Promise<ICreateOfferResponse> {
async sendCredentialCreateOffer(payload: IIssuance): Promise<PromiseSettledResult<ICreateOfferResponse>[]> {

try {
const { orgId, credentialDefinitionId, comment, connectionId, attributes } = payload || {};
const { orgId, credentialDefinitionId, comment, credentialData } = payload || {};

const schemaResponse: SchemaDetails = await this.issuanceRepository.getCredentialDefinitionDetails(
credentialDefinitionId
Expand All @@ -58,26 +57,26 @@ export class IssuanceService {
if (schemaResponse?.attributes) {
const schemaResponseError = [];
const attributesArray: IAttributes[] = JSON.parse(schemaResponse.attributes);

attributesArray.forEach((attribute) => {
if (attribute.attributeName && attribute.isRequired) {

payload.attributes.map((attr) => {
if (attr.name === attribute.attributeName && attribute.isRequired && !attr.value) {
schemaResponseError.push(
`Attribute ${attribute.attributeName} is required`
);
}
return true;
});
}
if (attribute.attributeName && attribute.isRequired) {

credentialData.forEach((credential, i) => {
credential.attributes.forEach((attr) => {
if (attr.name === attribute.attributeName && attribute.isRequired && !attr.value) {
schemaResponseError.push(
`Attribute ${attribute.attributeName} is required at position ${i + 1}`
);
}
});
});
}
});

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

}

}
}

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

Expand All @@ -94,34 +93,36 @@ export class IssuanceService {
}

const issuanceMethodLabel = 'create-offer';

const url = await this.getAgentUrl(issuanceMethodLabel, orgAgentType, agentEndPoint, agentDetails?.tenantId);

const issueData: IIssueData = {
protocolVersion: 'v1',
connectionId,
credentialFormats: {
indy: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
attributes: (attributes).map(({ isRequired, ...rest }) => rest),
credentialDefinitionId

}
},
autoAcceptCredential: autoAcceptCredential || 'always',
comment
};
const issuancePromises: Promise<ICreateOfferResponse>[] = [];

const credentialCreateOfferDetails: ICreateOfferResponse = await this._sendCredentialCreateOffer(issueData, url, orgId);
for (const credentials of credentialData) {
const { connectionId, attributes } = credentials;
const issueData: IIssueData = {
protocolVersion: 'v1',
connectionId,
credentialFormats: {
indy: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
attributes: (attributes).map(({ isRequired, ...rest }) => rest),
credentialDefinitionId

}
},
autoAcceptCredential: payload.autoAcceptCredential || 'always',
comment
};

if (credentialCreateOfferDetails && 0 < Object.keys(credentialCreateOfferDetails).length) {
delete credentialCreateOfferDetails._tags;
delete credentialCreateOfferDetails.metadata;
delete credentialCreateOfferDetails.credentials;
delete credentialCreateOfferDetails.credentialAttributes;
delete credentialCreateOfferDetails.autoAcceptCredential;
await this.delay(500);
const credentialCreateOfferDetails = this._sendCredentialCreateOffer(issueData, url, orgId);
issuancePromises.push(credentialCreateOfferDetails);
}

return credentialCreateOfferDetails;
const results = await Promise.allSettled(issuancePromises);
return results;

} catch (error) {
this.logger.error(`[sendCredentialCreateOffer] - error in create credentials : ${JSON.stringify(error)}`);
const errorStack = error?.status?.message?.error?.reason || error?.status?.message?.error;
Expand Down

0 comments on commit e54a711

Please sign in to comment.