From fcdcb422fc8646e6f4003106fed3357f95dad1f7 Mon Sep 17 00:00:00 2001 From: pallavicoder Date: Thu, 30 Jan 2025 16:29:14 +0530 Subject: [PATCH 1/8] fix:added array data type in schema payload Signed-off-by: pallavicoder --- libs/enum/src/enum.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/enum/src/enum.ts b/libs/enum/src/enum.ts index a5b491cbc..0c5adee7a 100644 --- a/libs/enum/src/enum.ts +++ b/libs/enum/src/enum.ts @@ -206,7 +206,8 @@ export enum W3CSchemaDataType { NUMBER = 'number', INTEGER = 'integer', STRING = 'string', - DATE_TIME = 'datetime-local' + DATE_TIME = 'datetime-local', + ARRAY= 'array', } export enum JSONSchemaType { From 2b978c3605075274b199272a636cd4a8d5019d68 Mon Sep 17 00:00:00 2001 From: pallavicoder Date: Fri, 31 Jan 2025 09:17:35 +0530 Subject: [PATCH 2/8] fix:added nested attributes in schema paylaod Signed-off-by: pallavicoder --- .../api-gateway/src/dtos/create-schema.dto.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/apps/api-gateway/src/dtos/create-schema.dto.ts b/apps/api-gateway/src/dtos/create-schema.dto.ts index bac862dfb..edf9c2644 100644 --- a/apps/api-gateway/src/dtos/create-schema.dto.ts +++ b/apps/api-gateway/src/dtos/create-schema.dto.ts @@ -5,6 +5,18 @@ import { Transform, Type } from 'class-transformer'; import { IsNotSQLInjection, trim } from '@credebl/common/cast.helper'; import { JSONSchemaType, SchemaTypeEnum, W3CSchemaDataType } from '@credebl/enum/enum'; + +class NestedAttribute { + @ApiProperty({ description: 'Name of the nested attribute group', example: 'ProductDetails' }) + @IsString() + @IsNotEmpty({ message: 'Name is required' }) + name: string; + + @ApiProperty() + @IsArray() + + attributes: Record[]; +} class W3CAttributeValue { @ApiProperty() @IsString() @@ -30,6 +42,17 @@ import { JSONSchemaType, SchemaTypeEnum, W3CSchemaDataType } from '@credebl/enum @IsBoolean() @IsNotEmpty({ message: 'isRequired property is required' }) isRequired: boolean; + + @ApiProperty({ + description: '', + type: [NestedAttribute], + }) + @IsArray() + @IsOptional() + @ValidateNested({ each: true }) + @Type(() => NestedAttribute) + nestedAttributes: NestedAttribute[]; + } class AttributeValue { From 52a409fec9ef783148eec6a7a51c7ef1ed8cdffa Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Wed, 5 Feb 2025 18:27:24 +0530 Subject: [PATCH 3/8] feat: add array data type while creating schema Signed-off-by: bhavanakarwade --- .../api-gateway/src/dtos/create-schema.dto.ts | 20 +----- .../src/issuance/dtos/issuance.dto.ts | 2 + .../src/issuance/interfaces/index.ts | 3 +- .../src/issuance/issuance.controller.ts | 35 +++++++-- .../src/issuance/issuance.service.ts | 9 +-- .../interfaces/issuance.interfaces.ts | 6 +- apps/issuance/src/issuance.controller.ts | 8 +-- apps/issuance/src/issuance.service.ts | 72 ++++++++++++++----- 8 files changed, 106 insertions(+), 49 deletions(-) diff --git a/apps/api-gateway/src/dtos/create-schema.dto.ts b/apps/api-gateway/src/dtos/create-schema.dto.ts index edf9c2644..9200c30cd 100644 --- a/apps/api-gateway/src/dtos/create-schema.dto.ts +++ b/apps/api-gateway/src/dtos/create-schema.dto.ts @@ -6,17 +6,6 @@ import { IsNotSQLInjection, trim } from '@credebl/common/cast.helper'; import { JSONSchemaType, SchemaTypeEnum, W3CSchemaDataType } from '@credebl/enum/enum'; -class NestedAttribute { - @ApiProperty({ description: 'Name of the nested attribute group', example: 'ProductDetails' }) - @IsString() - @IsNotEmpty({ message: 'Name is required' }) - name: string; - - @ApiProperty() - @IsArray() - - attributes: Record[]; -} class W3CAttributeValue { @ApiProperty() @IsString() @@ -44,15 +33,12 @@ class NestedAttribute { isRequired: boolean; @ApiProperty({ - description: '', - type: [NestedAttribute], + description: 'Array of objects with dynamic keys', + isArray: true }) @IsArray() @IsOptional() - @ValidateNested({ each: true }) - @Type(() => NestedAttribute) - nestedAttributes: NestedAttribute[]; - + nestedAttributes: Record | string>[]; } class AttributeValue { diff --git a/apps/api-gateway/src/issuance/dtos/issuance.dto.ts b/apps/api-gateway/src/issuance/dtos/issuance.dto.ts index fdc23c9fa..be6b9d14c 100644 --- a/apps/api-gateway/src/issuance/dtos/issuance.dto.ts +++ b/apps/api-gateway/src/issuance/dtos/issuance.dto.ts @@ -249,6 +249,8 @@ export class CredentialsIssuanceDto { reuseConnection?: boolean; orgId: string; + + isValidateSchema?: boolean; } export class OOBIssueCredentialDto extends CredentialsIssuanceDto { diff --git a/apps/api-gateway/src/issuance/interfaces/index.ts b/apps/api-gateway/src/issuance/interfaces/index.ts index 981777f8f..06d30d3ff 100644 --- a/apps/api-gateway/src/issuance/interfaces/index.ts +++ b/apps/api-gateway/src/issuance/interfaces/index.ts @@ -70,7 +70,8 @@ export interface UploadedFileDetails { templateId: string; fileKey: string; fileName: string; - type: SchemaType + type: SchemaType; + isValidateSchema?: boolean; } export interface IIssuedCredentialSearchParams { pageNumber: number; diff --git a/apps/api-gateway/src/issuance/issuance.controller.ts b/apps/api-gateway/src/issuance/issuance.controller.ts index 0245e9979..122b4330f 100644 --- a/apps/api-gateway/src/issuance/issuance.controller.ts +++ b/apps/api-gateway/src/issuance/issuance.controller.ts @@ -278,6 +278,11 @@ async downloadBulkIssuanceCSVTemplate( required: true, description: 'The type of schema to be used' }) + @ApiQuery({ + name: 'isValidateSchema', + type: Boolean, + required: false + }) @UseInterceptors(FileInterceptor('file')) async uploadCSVTemplate( @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, @@ -285,7 +290,8 @@ async downloadBulkIssuanceCSVTemplate( @UploadedFile() file: Express.Multer.File, @Body() fileDetails: object, @Res() res: Response, - @Query('schemaType') schemaType: SchemaType = SchemaType.INDY + @Query('schemaType') schemaType: SchemaType = SchemaType.INDY, + @Query('isValidateSchema') isValidateSchema: boolean = true ): Promise { const { templateId } = query; @@ -301,7 +307,8 @@ async downloadBulkIssuanceCSVTemplate( type: schemaType, templateId, fileKey, - fileName: fileDetails['fileName'] || file?.filename || file?.originalname + fileName: fileDetails['fileName'] || file?.filename || file?.originalname, + isValidateSchema }; const importCsvDetails = await this.issueCredentialService.uploadCSVTemplate(uploadedfileDetails); @@ -384,6 +391,11 @@ async downloadBulkIssuanceCSVTemplate( summary: 'bulk issue credential', description: 'bulk issue credential' }) + @ApiQuery({ + name: 'isValidateSchema', + type: Boolean, + required: false + }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiConsumes('multipart/form-data') @ApiBody({ @@ -409,6 +421,7 @@ async downloadBulkIssuanceCSVTemplate( @Param('orgId') orgId: string, @User() user: user, @Query(new ValidationPipe({ transform: true })) query: CredentialQuery, + @Query('isValidateSchema') isValidateSchema: boolean = true, @Res() res: Response, @Body() fileDetails?: object, @UploadedFile() file?: Express.Multer.File @@ -432,7 +445,7 @@ async downloadBulkIssuanceCSVTemplate( type: fileDetails?.['type'] }; } - const bulkIssuanceDetails = await this.issueCredentialService.issueBulkCredential(requestId, orgId, clientDetails, reqPayload); + const bulkIssuanceDetails = await this.issueCredentialService.issueBulkCredential(requestId, orgId, clientDetails, reqPayload, isValidateSchema); const finalResponse: IResponse = { statusCode: HttpStatus.CREATED, @@ -562,16 +575,23 @@ async downloadBulkIssuanceCSVTemplate( summary: 'Retry bulk issue credential', description: 'Retry bulk issue credential' }) + @ApiQuery({ + name: 'isValidateSchema', + type: Boolean, + required: false + }) async retryBulkCredentials( @Param('fileId') fileId: string, @Param('orgId') orgId: string, + @Query('isValidateSchema') isValidateSchema: boolean = true, @Res() res: Response, @Body() clientDetails: ClientDetails ): Promise { const bulkIssuanceDetails = await this.issueCredentialService.retryBulkCredential( fileId, orgId, - clientDetails + clientDetails, + isValidateSchema ); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, @@ -713,17 +733,24 @@ async downloadBulkIssuanceCSVTemplate( name:'credentialType', enum: IssueCredentialType }) + @ApiQuery({ + name: 'isValidateSchema', + type: Boolean, + required: false + }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) async createOOBCredentialOffer( @Query('credentialType') credentialType: IssueCredentialType = IssueCredentialType.INDY, + @Query('isValidateSchema') isValidateSchema: boolean = true, @Param('orgId') orgId: string, @Body() issueCredentialDto: OOBIssueCredentialDto, @Res() res: Response ): Promise { issueCredentialDto.orgId = orgId; issueCredentialDto.credentialType = credentialType; + issueCredentialDto.isValidateSchema = isValidateSchema; const getCredentialDetails = await this.issueCredentialService.sendCredentialOutOfBand(issueCredentialDto); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, diff --git a/apps/api-gateway/src/issuance/issuance.service.ts b/apps/api-gateway/src/issuance/issuance.service.ts index b196aef1c..2f1b749ed 100644 --- a/apps/api-gateway/src/issuance/issuance.service.ts +++ b/apps/api-gateway/src/issuance/issuance.service.ts @@ -121,13 +121,14 @@ export class IssuanceService extends BaseService { return this.natsClient.sendNats(this.issuanceProxy, 'issued-file-data', payload); } - async issueBulkCredential(requestId: string, orgId: string, clientDetails: ClientDetails, reqPayload: IReqPayload): Promise { - const payload = { requestId, orgId, clientDetails, reqPayload }; + async issueBulkCredential(requestId: string, orgId: string, clientDetails: ClientDetails, reqPayload: IReqPayload, isValidateSchema: boolean): Promise { + const payload = { requestId, orgId, clientDetails, reqPayload, isValidateSchema }; + return this.natsClient.sendNatsMessage(this.issuanceProxy, 'issue-bulk-credentials', payload); } - async retryBulkCredential(fileId: string, orgId: string, clientDetails: ClientDetails): Promise { - const payload = { fileId, orgId, clientDetails }; + async retryBulkCredential(fileId: string, orgId: string, clientDetails: ClientDetails, isValidateSchema?: boolean): Promise { + const payload = { fileId, orgId, clientDetails, isValidateSchema }; return this.natsClient.sendNatsMessage(this.issuanceProxy, 'retry-bulk-credentials', payload); } diff --git a/apps/issuance/interfaces/issuance.interfaces.ts b/apps/issuance/interfaces/issuance.interfaces.ts index 0cf3a5494..af6752be9 100644 --- a/apps/issuance/interfaces/issuance.interfaces.ts +++ b/apps/issuance/interfaces/issuance.interfaces.ts @@ -195,7 +195,8 @@ export interface ImportFileDetails { templateId: string; fileKey: string; fileName: string; - type: string + type: string; + isValidateSchema?: boolean; } export interface ICredentialPayload { schemaLedgerId: string, @@ -298,6 +299,7 @@ export interface SendEmailCredentialOffer { platformName?: string, organizationLogoUrl?: string; prettyVc?: IPrettyVc; + isValidateSchema?: boolean; } export interface TemplateDetailsInterface { @@ -328,6 +330,7 @@ export interface IQueuePayload{ id: string; jobId: string; cacheId?: string; + isValidateSchema?: boolean; clientId: string; referenceId: string; fileUploadId: string; @@ -383,6 +386,7 @@ export interface BulkPayloadDetails { clientId: string; orgId: string; requestId?: string; + isValidateSchema?: boolean; isRetry: boolean; organizationLogoUrl?: string; platformName?: string; diff --git a/apps/issuance/src/issuance.controller.ts b/apps/issuance/src/issuance.controller.ts index d05e3f70b..e174f5a0d 100644 --- a/apps/issuance/src/issuance.controller.ts +++ b/apps/issuance/src/issuance.controller.ts @@ -90,13 +90,13 @@ export class IssuanceController { @MessagePattern({ cmd: 'issue-bulk-credentials' }) - async issueBulkCredentials(payload: { requestId: string, orgId: string, clientDetails: IClientDetails, reqPayload: ImportFileDetails }): Promise { - return this.issuanceService.issueBulkCredential(payload.requestId, payload.orgId, payload.clientDetails, payload.reqPayload); + async issueBulkCredentials(payload: { requestId: string, orgId: string, clientDetails: IClientDetails, reqPayload: ImportFileDetails, isValidateSchema: boolean }): Promise { + return this.issuanceService.issueBulkCredential(payload.requestId, payload.orgId, payload.clientDetails, payload.reqPayload, payload.isValidateSchema); } @MessagePattern({ cmd: 'retry-bulk-credentials' }) - async retryeBulkCredentials(payload: { fileId: string, orgId: string, clientDetails: IClientDetails }): Promise { - return this.issuanceService.retryBulkCredential(payload.fileId, payload.orgId, payload.clientDetails); + async retryeBulkCredentials(payload: { fileId: string, orgId: string, clientDetails: IClientDetails, isValidateSchema?: boolean }): Promise { + return this.issuanceService.retryBulkCredential(payload.fileId, payload.orgId, payload.clientDetails, payload.isValidateSchema); } @MessagePattern({ cmd: 'delete-issuance-records' }) diff --git a/apps/issuance/src/issuance.service.ts b/apps/issuance/src/issuance.service.ts index 10b51dd54..3428a8e93 100644 --- a/apps/issuance/src/issuance.service.ts +++ b/apps/issuance/src/issuance.service.ts @@ -245,7 +245,7 @@ export class IssuanceService { async sendCredentialOutOfBand(payload: OOBIssueCredentialDto): Promise<{ response: object }> { try { - const { orgId, credentialDefinitionId, comment, attributes, protocolVersion, credential, options, credentialType, isShortenUrl, reuseConnection } = payload; + const { orgId, credentialDefinitionId, comment, attributes, protocolVersion, credential, options, credentialType, isShortenUrl, reuseConnection, isValidateSchema } = payload; if (credentialType === IssueCredentialType.INDY) { const schemadetailsResponse: SchemaDetails = await this.issuanceRepository.getCredentialDefinitionDetails( credentialDefinitionId @@ -346,8 +346,10 @@ export class IssuanceService { const schemaServerUrl = issueData?.credentialFormats?.jsonld?.credential?.['@context']?.[1]; const schemaUrlAttributes = await this.getW3CSchemaAttributes(schemaServerUrl); - validateW3CSchemaAttributes(filteredIssuanceAttributes, schemaUrlAttributes); - + + if (isValidateSchema) { + validateW3CSchemaAttributes(filteredIssuanceAttributes, schemaUrlAttributes); + } } const credentialCreateOfferDetails = await this._outOfBandCredentialOffer(issueData, url, orgId); if (isShortenUrl) { @@ -591,7 +593,7 @@ export class IssuanceService { } } -async outOfBandCredentialOffer(outOfBandCredential: OutOfBandCredentialOfferPayload, platformName?: string, organizationLogoUrl?: string, prettyVc?: IPrettyVc): Promise { +async outOfBandCredentialOffer(outOfBandCredential: OutOfBandCredentialOfferPayload, platformName?: string, organizationLogoUrl?: string, prettyVc?: IPrettyVc, isValidateSchema?: boolean): Promise { try { const { credentialOffer, @@ -695,6 +697,7 @@ async outOfBandCredentialOffer(outOfBandCredential: OutOfBandCredentialOfferPayl platformName?: string; organizationLogoUrl?: string; prettyVc?: IPrettyVc; + isValidateSchema?: boolean; } = { credentialType, protocolVersion, @@ -707,6 +710,7 @@ async outOfBandCredentialOffer(outOfBandCredential: OutOfBandCredentialOfferPayl errors, url, orgId, + isValidateSchema, organizationDetails, iterator: undefined, emailId: emailId || '', @@ -731,7 +735,7 @@ async outOfBandCredentialOffer(outOfBandCredential: OutOfBandCredentialOfferPayl await this.delay(500); // Wait for 0.5 seconds const sendOobOffer = await this.sendEmailForCredentialOffer(sendEmailCredentialOffer); - + arraycredentialOfferResponse.push(sendOobOffer); } if (0 < errors.length) { @@ -785,7 +789,8 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO organizationDetails, platformName, organizationLogoUrl, - isReuseConnection + isReuseConnection, + isValidateSchema } = sendEmailCredentialOffer; const iterationNo = index + 1; try { @@ -851,8 +856,10 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO const schemaServerUrl = outOfBandIssuancePayload?.credentialFormats?.jsonld?.credential?.['@context']?.[1]; const schemaUrlAttributes = await this.getW3CSchemaAttributes(schemaServerUrl); - validateW3CSchemaAttributes(filteredIssuanceAttributes, schemaUrlAttributes); + if (isValidateSchema) { + validateW3CSchemaAttributes(filteredIssuanceAttributes, schemaUrlAttributes); + } } const credentialCreateOfferDetails = await this._outOfBandCredentialOffer(outOfBandIssuancePayload, url, orgId); @@ -1045,10 +1052,26 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO const attributesArray = JSON.parse(schemaResponse.attributes); // Extract the 'attributeName' values from the objects and store them in an array - const attributeNameArray = attributesArray.map(attribute => attribute.attributeName); + const attributeNameArray = attributesArray + .filter((attribute) => 'array' !== attribute?.schemaDataType) + .map((attribute) => attribute.attributeName); + + let nestedAttributes = []; + + if (attributesArray.some((attribute) => W3CSchemaDataType.ARRAY === attribute?.schemaDataType)) { + nestedAttributes = attributesArray + .filter((attribute) => W3CSchemaDataType.ARRAY === attribute?.schemaDataType) + .flatMap((attribute) => attribute.nestedAttributes || []) + .flatMap(Object.entries) + .flatMap(([key, value]) => [key, ...Object.values(value)]); + } + attributeNameArray.unshift(TemplateIdentifier.EMAIL_COLUMN); - const [csvData, csvFields] = [jsonData, attributeNameArray]; + // const [csvData, csvFields] = [jsonData, attributeNameArray]; + const [csvData, csvFields] = 0 < nestedAttributes.length + ? [jsonData, [...attributeNameArray, ...nestedAttributes]] + : [jsonData, attributeNameArray]; if (!csvData || !csvFields) { // eslint-disable-next-line prefer-promise-reject-errors @@ -1090,7 +1113,7 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO credentialType: '', schemaName: '' }; - const { fileName, templateId, type } = importFileDetails; + const { fileName, templateId, type, isValidateSchema } = importFileDetails; if (type === SchemaType.W3C_Schema) { credentialDetails = await this.issuanceRepository.getSchemaDetailsBySchemaIdentifier(templateId); credentialPayload.schemaLedgerId = credentialDetails.schemaLedgerId; @@ -1141,7 +1164,7 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO let validatedData; - if (type === SchemaType.W3C_Schema) { + if (type === SchemaType.W3C_Schema && isValidateSchema) { validatedData = parsedData.data.map((row) => { const { email_identifier, ...rest } = row; const newRow = { ...rest }; @@ -1160,6 +1183,13 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO } }); + return { email_identifier, ...newRow }; + }); + } else if (type === SchemaType.W3C_Schema && !isValidateSchema) { + validatedData = parsedData.data.map((row) => { + const { email_identifier, ...rest } = row; + const newRow = { ...rest }; + return { email_identifier, ...newRow }; }); } @@ -1170,8 +1200,10 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO meta: parsedData.meta }; - await this.validateFileHeaders(fileHeader, attributeNameArray); - await this.validateFileData(fileData, attributesArray, fileHeader); + if (isValidateSchema) { + await this.validateFileHeaders(fileHeader, attributeNameArray); + await this.validateFileData(fileData, attributesArray, fileHeader); + } credentialPayload.fileData = type === SchemaType.W3C_Schema ? finalFileData : parsedData; credentialPayload.fileName = fileName; @@ -1333,8 +1365,8 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO * @param requestId */ - private async processInBatches(bulkPayload, bulkPayloadDetails: BulkPayloadDetails):Promise { - const {clientId, isRetry, orgId, requestId} = bulkPayloadDetails; + private async processInBatches(bulkPayload, bulkPayloadDetails: BulkPayloadDetails): Promise { + const {clientId, isRetry, orgId, requestId, isValidateSchema } = bulkPayloadDetails; const delay = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)); const batchSize = CommonConstants.ISSUANCE_BATCH_SIZE; // initial 1000 const uniqueJobId = uuidv4(); @@ -1366,6 +1398,7 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO credentialType: item.credential_type, totalJobs: bulkPayload.length, isRetry, + isValidateSchema, isLastData: false, organizationLogoUrl: bulkPayloadDetails?.organizationLogoUrl, platformName: bulkPayloadDetails?.platformName, @@ -1413,7 +1446,8 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO requestId: string, orgId: string, clientDetails: IClientDetails, - reqPayload: ImportFileDetails + reqPayload: ImportFileDetails, + isValidateSchema: boolean ): Promise { if (!requestId) { throw new BadRequestException(ResponseMessages.issuance.error.missingRequestId); @@ -1486,6 +1520,7 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO clientId: clientDetails.clientId, orgId, requestId, + isValidateSchema, isRetry: false, organizationLogoUrl: clientDetails?.organizationLogoUrl, platformName: clientDetails?.platformName, @@ -1514,7 +1549,7 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO } } - async retryBulkCredential(fileId: string, orgId: string, clientDetails: IClientDetails): Promise { + async retryBulkCredential(fileId: string, orgId: string, clientDetails: IClientDetails, isValidateSchema?: boolean): Promise { let bulkpayloadRetry; try { const fileDetails = await this.issuanceRepository.getFileDetailsById(fileId); @@ -1532,6 +1567,7 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO clientId : clientDetails.clientId, orgId, isRetry: true, + isValidateSchema, organizationLogoUrl: clientDetails?.organizationLogoUrl, platformName: clientDetails?.platformName, certificate: clientDetails?.certificate, @@ -1629,7 +1665,7 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO } const oobCredentials = await this.outOfBandCredentialOffer( - oobIssuancepayload, jobDetails?.platformName, jobDetails?.organizationLogoUrl, prettyVc); + oobIssuancepayload, jobDetails?.platformName, jobDetails?.organizationLogoUrl, prettyVc, jobDetails?.isValidateSchema); if (oobCredentials) { await this.issuanceRepository.deleteFileDataByJobId(jobDetails.id); } From 743fe2c34dbaad4376ca3f306415a07554079aee Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Wed, 5 Feb 2025 18:34:59 +0530 Subject: [PATCH 4/8] replace hardcoded value with dynamic Signed-off-by: bhavanakarwade --- apps/issuance/src/issuance.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/issuance/src/issuance.service.ts b/apps/issuance/src/issuance.service.ts index 3428a8e93..9959dc525 100644 --- a/apps/issuance/src/issuance.service.ts +++ b/apps/issuance/src/issuance.service.ts @@ -1053,7 +1053,7 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO // Extract the 'attributeName' values from the objects and store them in an array const attributeNameArray = attributesArray - .filter((attribute) => 'array' !== attribute?.schemaDataType) + .filter((attribute) => W3CSchemaDataType.ARRAY !== attribute?.schemaDataType) .map((attribute) => attribute.attributeName); let nestedAttributes = []; From 09cde2d83caef580c949a476a40212caf6254f19 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Wed, 5 Feb 2025 19:38:40 +0530 Subject: [PATCH 5/8] fix: removed commented code Signed-off-by: bhavanakarwade --- apps/issuance/src/issuance.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/issuance/src/issuance.service.ts b/apps/issuance/src/issuance.service.ts index 9959dc525..cae5369e7 100644 --- a/apps/issuance/src/issuance.service.ts +++ b/apps/issuance/src/issuance.service.ts @@ -1068,7 +1068,6 @@ async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialO attributeNameArray.unshift(TemplateIdentifier.EMAIL_COLUMN); - // const [csvData, csvFields] = [jsonData, attributeNameArray]; const [csvData, csvFields] = 0 < nestedAttributes.length ? [jsonData, [...attributeNameArray, ...nestedAttributes]] : [jsonData, attributeNameArray]; From 31510a71f95b8d9456e4cea86772ddf3ed332a39 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Thu, 13 Feb 2025 17:55:11 +0530 Subject: [PATCH 6/8] fix: added search on schema name Signed-off-by: bhavanakarwade --- .../interfaces/issuance.interfaces.ts | 4 +++ apps/issuance/src/issuance.repository.ts | 8 +++-- apps/issuance/src/issuance.service.ts | 32 +++++++++++++++++-- .../out-of-band-issuance.template.ts | 2 +- .../schema/repositories/schema.repository.ts | 13 ++++++++ apps/ledger/src/schema/schema.controller.ts | 6 ++++ apps/ledger/src/schema/schema.interface.ts | 5 ++- apps/ledger/src/schema/schema.service.ts | 11 ++++++- .../out-of-band-verification.template.ts | 2 +- 9 files changed, 74 insertions(+), 9 deletions(-) diff --git a/apps/issuance/interfaces/issuance.interfaces.ts b/apps/issuance/interfaces/issuance.interfaces.ts index af6752be9..cb30338eb 100644 --- a/apps/issuance/interfaces/issuance.interfaces.ts +++ b/apps/issuance/interfaces/issuance.interfaces.ts @@ -396,3 +396,7 @@ export interface BulkPayloadDetails { height?: string; width?: string; } + +export interface ISchemaId { + schemaLedgerId: string; +} \ No newline at end of file diff --git a/apps/issuance/src/issuance.repository.ts b/apps/issuance/src/issuance.repository.ts index 1f752916e..784b3621e 100644 --- a/apps/issuance/src/issuance.repository.ts +++ b/apps/issuance/src/issuance.repository.ts @@ -108,7 +108,8 @@ export class IssuanceRepository { async getAllIssuedCredentials( user: IUserRequest, orgId: string, - issuedCredentialsSearchCriteria: IIssuedCredentialSearchParams + issuedCredentialsSearchCriteria: IIssuedCredentialSearchParams, + schemaIds?: string[] ): Promise<{ issuedCredentialsCount: number; issuedCredentialsList: { @@ -121,11 +122,12 @@ export class IssuanceRepository { }[]; }> { try { + const issuedCredentialsList = await this.prisma.credentials.findMany({ where: { orgId, OR: [ - { schemaId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } }, + { schemaId: { in: schemaIds } }, { connectionId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } } ] }, @@ -150,7 +152,7 @@ export class IssuanceRepository { where: { orgId, OR: [ - { schemaId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } }, + { schemaId: { in: schemaIds } }, { connectionId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } } ] } diff --git a/apps/issuance/src/issuance.service.ts b/apps/issuance/src/issuance.service.ts index cae5369e7..e92e30fad 100644 --- a/apps/issuance/src/issuance.service.ts +++ b/apps/issuance/src/issuance.service.ts @@ -9,7 +9,7 @@ import { CommonConstants } from '@credebl/common/common.constant'; import { ResponseMessages } from '@credebl/common/response-messages'; import { ClientProxy, RpcException } from '@nestjs/microservices'; import { map } from 'rxjs'; -import { BulkPayloadDetails, CredentialOffer, FileUpload, FileUploadData, IAttributes, IBulkPayloadObject, IClientDetails, ICreateOfferResponse, ICredentialPayload, IIssuance, IIssueData, IPattern, IQueuePayload, ISchemaAttributes, ISendOfferNatsPayload, ImportFileDetails, IssueCredentialWebhookPayload, OutOfBandCredentialOfferPayload, PreviewRequest, SchemaDetails, SendEmailCredentialOffer, TemplateDetailsInterface } from '../interfaces/issuance.interfaces'; +import { BulkPayloadDetails, CredentialOffer, FileUpload, FileUploadData, IAttributes, IBulkPayloadObject, IClientDetails, ICreateOfferResponse, ICredentialPayload, IIssuance, IIssueData, IPattern, IQueuePayload, ISchemaAttributes, ISchemaId, ISendOfferNatsPayload, ImportFileDetails, IssueCredentialWebhookPayload, OutOfBandCredentialOfferPayload, PreviewRequest, SchemaDetails, SendEmailCredentialOffer, TemplateDetailsInterface } from '../interfaces/issuance.interfaces'; import { AutoAccept, IssuanceProcessState, OrgAgentType, PromiseResult, SchemaType, TemplateIdentifier, W3CSchemaDataType} from '@credebl/enum/enum'; import * as QRCode from 'qrcode'; import { OutOfBandIssuance } from '../templates/out-of-band-issuance.template'; @@ -457,10 +457,18 @@ export class IssuanceService { issuedCredentialsSearchCriteria: IIssuedCredentialSearchParams ): Promise { try { + + let schemaIds; + if (issuedCredentialsSearchCriteria?.search) { + const schemaDetails = await this._getSchemaDetailsByName(issuedCredentialsSearchCriteria?.search); + schemaIds = schemaDetails?.map(item => item?.schemaLedgerId); + } + const getIssuedCredentialsList = await this.issuanceRepository.getAllIssuedCredentials( user, orgId, - issuedCredentialsSearchCriteria + issuedCredentialsSearchCriteria, + schemaIds ); const getSchemaIds = getIssuedCredentialsList?.issuedCredentialsList?.map((schema) => schema?.schemaId); @@ -508,6 +516,26 @@ export class IssuanceService { } } + async _getSchemaDetailsByName(schemaName: string): Promise { + const pattern = { cmd: 'get-schemas-details-by-name' }; + + const schemaDetails = await this.natsClient + .send(this.issuanceServiceProxy, pattern, schemaName) + + .catch((error) => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); + }); + + return schemaDetails; + } + async getSchemaUrlDetails(schemaUrls: string[]): Promise { const results = []; diff --git a/apps/issuance/templates/out-of-band-issuance.template.ts b/apps/issuance/templates/out-of-band-issuance.template.ts index d8956fbb8..9723ba305 100644 --- a/apps/issuance/templates/out-of-band-issuance.template.ts +++ b/apps/issuance/templates/out-of-band-issuance.template.ts @@ -56,7 +56,7 @@ export class OutOfBandIssuance { iOS App Store. (Skip, if already downloaded)
  • Complete the onboarding process in ${process.env.MOBILE_APP}.
  • -
  • Open the “Accept Credential” link below in this email (This will open the link in the ${process.env.MOBILE_APP} App)
  • +
  • Open the “Accept Credential” link below in this email (This will open the link in the ${process.env.MOBILE_APP})
  • Accept the Credential in ${process.env.MOBILE_APP}.
  • Check "Credentials" tab in ${process.env.MOBILE_APP} to view the issued credential.
  • diff --git a/apps/ledger/src/schema/repositories/schema.repository.ts b/apps/ledger/src/schema/repositories/schema.repository.ts index e4f6ce602..350ce66b3 100644 --- a/apps/ledger/src/schema/repositories/schema.repository.ts +++ b/apps/ledger/src/schema/repositories/schema.repository.ts @@ -7,6 +7,7 @@ import { ResponseMessages } from '@credebl/common/response-messages'; import { AgentDetails, ISchemasWithCount } from '../interfaces/schema.interface'; import { SchemaType, SortValue } from '@credebl/enum/enum'; import { ICredDefWithCount, IPlatformSchemas } from '@credebl/common/interfaces/schema.interface'; +import { ISchemaId } from '../schema.interface'; @Injectable() export class SchemaRepository { @@ -156,6 +157,18 @@ export class SchemaRepository { } } + async getSchemasDetailsBySchemaName(schemaName: string): Promise { + const schemaDetails = await this.prisma.schema.findMany({ + where: { + name: { contains: schemaName, mode: 'insensitive' } + }, + + select: { schemaLedgerId: true } + }); + + return schemaDetails; + } + async getSchemasDetailsBySchemaIds(templateIds: string[]): Promise { try { const schemasResult = await this.prisma.schema.findMany({ diff --git a/apps/ledger/src/schema/schema.controller.ts b/apps/ledger/src/schema/schema.controller.ts index 9409855dd..aa5bae984 100644 --- a/apps/ledger/src/schema/schema.controller.ts +++ b/apps/ledger/src/schema/schema.controller.ts @@ -16,6 +16,7 @@ import { ISchemasWithPagination } from '@credebl/common/interfaces/schema.interface'; import { IschemaPayload } from './interfaces/schema.interface'; +import { ISchemaId } from './schema.interface'; @Controller('schema') export class SchemaController { @@ -33,6 +34,11 @@ export class SchemaController { return this.schemaService.getSchemaDetails(templateIds); } + @MessagePattern({ cmd: 'get-schemas-details-by-name' }) + async getSchemasDetailsBySchemaName(schemaName: string): Promise { + return this.schemaService.getSchemaDetailsBySchemaName(schemaName); + } + @MessagePattern({ cmd: 'get-schema-by-id' }) async getSchemaById(payload: ISchema): Promise { const { schemaId, orgId } = payload; diff --git a/apps/ledger/src/schema/schema.interface.ts b/apps/ledger/src/schema/schema.interface.ts index 9acf33b25..92d0e55d6 100644 --- a/apps/ledger/src/schema/schema.interface.ts +++ b/apps/ledger/src/schema/schema.interface.ts @@ -1,5 +1,8 @@ -import { IUserRequestInterface } from "./interfaces/schema.interface"; +import { IUserRequestInterface } from './interfaces/schema.interface'; +export interface ISchemaId { + schemaLedgerId: string; +} export interface SchemaSearchCriteria { schemaLedgerId: string; credentialDefinitionId: string; diff --git a/apps/ledger/src/schema/schema.service.ts b/apps/ledger/src/schema/schema.service.ts index 524848030..01e195635 100644 --- a/apps/ledger/src/schema/schema.service.ts +++ b/apps/ledger/src/schema/schema.service.ts @@ -13,7 +13,7 @@ import { Prisma, schema } from '@prisma/client'; import { ISaveSchema, ISchema, ISchemaCredDeffSearchInterface, ISchemaExist, ISchemaSearchCriteria, W3CCreateSchema } from './interfaces/schema-payload.interface'; import { ResponseMessages } from '@credebl/common/response-messages'; import { ICreateSchema, ICreateW3CSchema, IGenericSchema, IUserRequestInterface } from './interfaces/schema.interface'; -import { CreateSchemaAgentRedirection, GetSchemaAgentRedirection } from './schema.interface'; +import { CreateSchemaAgentRedirection, GetSchemaAgentRedirection, ISchemaId } from './schema.interface'; import { map } from 'rxjs/operators'; import { JSONSchemaType, LedgerLessConstant, LedgerLessMethods, OrgAgentType, SchemaType, SchemaTypeEnum } from '@credebl/enum/enum'; import { ICredDefWithPagination, ISchemaData, ISchemaDetails, ISchemasWithPagination } from '@credebl/common/interfaces/schema.interface'; @@ -716,6 +716,15 @@ export class SchemaService extends BaseService { } } + async getSchemaDetailsBySchemaName(schemaName: string): Promise { + try { + const getSchemaDetails = await this.schemaRepository.getSchemasDetailsBySchemaName(schemaName); + return getSchemaDetails; + } catch (error) { + throw new RpcException(error.response ? error.response : error); + } + } + async _getSchemaById(payload: GetSchemaAgentRedirection): Promise<{ response: string }> { try { const pattern = { diff --git a/apps/verification/templates/out-of-band-verification.template.ts b/apps/verification/templates/out-of-band-verification.template.ts index df33d0d68..3e236900b 100644 --- a/apps/verification/templates/out-of-band-verification.template.ts +++ b/apps/verification/templates/out-of-band-verification.template.ts @@ -44,7 +44,7 @@ export class OutOfBandVerification { iOS App Store. (Skip, if already downloaded)
  • Complete the onboarding process in ${process.env.MOBILE_APP}.
  • -
  • Open the “Share Credential” link below in this email (This will open the link in the ${process.env.MOBILE_APP} App)
  • +
  • Open the “Share Credential” link below in this email (This will open the link in the ${process.env.MOBILE_APP})
  • Tap the "Send Proof" button in ${process.env.MOBILE_APP} to share you credential data.
  • From 202ba748bb3c92f418e021f24ed602cfad62fd47 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Thu, 13 Feb 2025 18:35:56 +0530 Subject: [PATCH 7/8] fix: made api property optional Signed-off-by: bhavanakarwade --- apps/api-gateway/src/dtos/create-schema.dto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api-gateway/src/dtos/create-schema.dto.ts b/apps/api-gateway/src/dtos/create-schema.dto.ts index 9200c30cd..61f45e2b3 100644 --- a/apps/api-gateway/src/dtos/create-schema.dto.ts +++ b/apps/api-gateway/src/dtos/create-schema.dto.ts @@ -32,7 +32,7 @@ import { JSONSchemaType, SchemaTypeEnum, W3CSchemaDataType } from '@credebl/enum @IsNotEmpty({ message: 'isRequired property is required' }) isRequired: boolean; - @ApiProperty({ + @ApiPropertyOptional({ description: 'Array of objects with dynamic keys', isArray: true }) From 970269828037dee28b038949a51d25307e415a01 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Thu, 13 Feb 2025 18:51:42 +0530 Subject: [PATCH 8/8] handled conditions for empty array Signed-off-by: bhavanakarwade --- apps/issuance/src/issuance.service.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/issuance/src/issuance.service.ts b/apps/issuance/src/issuance.service.ts index e92e30fad..cd0c4c8c5 100644 --- a/apps/issuance/src/issuance.service.ts +++ b/apps/issuance/src/issuance.service.ts @@ -461,8 +461,11 @@ export class IssuanceService { let schemaIds; if (issuedCredentialsSearchCriteria?.search) { const schemaDetails = await this._getSchemaDetailsByName(issuedCredentialsSearchCriteria?.search); - schemaIds = schemaDetails?.map(item => item?.schemaLedgerId); - } + + if (schemaDetails && 0 < schemaDetails?.length) { + schemaIds = schemaDetails.map(item => item?.schemaLedgerId); + } + } const getIssuedCredentialsList = await this.issuanceRepository.getAllIssuedCredentials( user,