diff --git a/apps/api-gateway/src/issuance/dtos/issuance.dto.ts b/apps/api-gateway/src/issuance/dtos/issuance.dto.ts index fd5470c7b..b1a495666 100644 --- a/apps/api-gateway/src/issuance/dtos/issuance.dto.ts +++ b/apps/api-gateway/src/issuance/dtos/issuance.dto.ts @@ -19,6 +19,26 @@ class Issuer { @Type(() => String) id: string | { id?: string }; } + +class PrettyVc { + @ApiPropertyOptional() + @IsOptional() + @Transform(({ value }) => trim(value)) + @IsString({ message: 'Certificate must be in string format.' }) + certificate: string; + + @ApiPropertyOptional({example: 'a4'}) + @IsOptional() + @Transform(({ value }) => trim(value)) + @IsString({ message: 'Size must be in string format.' }) + size: string; + + @ApiPropertyOptional({example: 'landscape'}) + @IsOptional() + @Transform(({ value }) => trim(value)) + @IsString({ message: 'orientation must be in string format.' }) + orientation: string; +} export class Credential { @ApiProperty() @IsNotEmpty({ message: 'context is required' }) @@ -35,8 +55,7 @@ export class Credential { @Type(() => String) @IsOptional() id?:string; - - + @ApiProperty() @ValidateNested({ each: true }) @Type(() => Issuer) @@ -48,6 +67,11 @@ export class Credential { @Type(() => String) issuanceDate:string; + @ApiPropertyOptional() + @IsOptional() + @Type(() => PrettyVc) + prettyVc?: PrettyVc; + @ApiProperty() @IsString({ message: 'expiration date should be string' }) @IsNotEmpty({ message: 'expiration date is required' }) @@ -55,10 +79,10 @@ export class Credential { @IsOptional() expirationDate?:string; - @ApiProperty() - @IsNotEmpty({ message: ' credential subject required' }) - credentialSubject: SingleOrArray; - [key: string]: unknown + @ApiProperty() + @IsNotEmpty({ message: ' credential subject required' }) + credentialSubject: SingleOrArray; + [key: string]: unknown } @@ -458,16 +482,21 @@ export class OOBCredentialDtoWithEmail { proofPurpose: 'assertionMethod' } } - ] - }) - @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` - }) - @ValidateNested({ each: true }) - @Type(() => CredentialOffer) - credentialOffer: CredentialOffer[]; + ] + }) + @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` }) + @ValidateNested({ each: true }) + @Type(() => CredentialOffer) + credentialOffer: CredentialOffer[]; + + @ApiProperty({ example: 'string' }) + @IsNotEmpty({ message: 'Please provide valid credential definition id' }) + @IsString({ message: 'credential definition id should be string' }) + @IsOptional() + @Transform(({ value }) => value.trim()) + credentialDefinitionId?: string; @ApiProperty({ example: 'string' }) @IsNotEmpty({ message: 'Please provide valid credential definition id' }) @@ -559,26 +588,43 @@ export class ClientDetails { @Type(() => Boolean) isSelectiveIssuance?: boolean = false; - userId?: string; - - @ApiPropertyOptional({ example: 'https://example.com/logo.png' }) - @Transform(({ value }) => trim(value)) - @IsOptional() - @IsUrl({ - // eslint-disable-next-line camelcase - require_protocol: true, - // eslint-disable-next-line camelcase - require_tld: true - }, - { message: 'brandLogoUrl should be a valid URL' }) - organizationLogoUrl?: string; - - @ApiPropertyOptional({ example: 'MyPlatform' }) - @Transform(({ value }) => trim(value)) - @IsOptional() - @IsString({ message: 'platformName should be string' }) - platformName?: string; - + userId?: string; + +@ApiPropertyOptional({ example: 'https://example.com/logo.png' }) +@Transform(({ value }) => trim(value)) +@IsOptional() +@IsUrl({ + // eslint-disable-next-line camelcase + require_protocol: true, + // eslint-disable-next-line camelcase + require_tld: true + }, +{ message: 'brandLogoUrl should be a valid URL' }) +organizationLogoUrl?: string; + +@ApiPropertyOptional({ example: 'MyPlatform' }) +@Transform(({ value }) => trim(value)) +@IsOptional() +@IsString({ message: 'platformName should be string' }) +platformName?: string; + +@ApiPropertyOptional() +@Transform(({ value }) => trim(value)) +@IsOptional() +@IsString({ message: 'Certificate should be string' }) +certificate?: string; + +@ApiPropertyOptional({ example: 'a4' }) +@Transform(({ value }) => trim(value)) +@IsOptional() +@IsString({ message: 'Size should be string' }) +size?: string; + +@ApiPropertyOptional({ example: 'landscape' }) +@Transform(({ value }) => trim(value)) +@IsOptional() +@IsString({ message: 'Orientation should be string' }) +orientation?: string; } export class TemplateDetails { diff --git a/apps/api-gateway/src/issuance/issuance.controller.ts b/apps/api-gateway/src/issuance/issuance.controller.ts index f05d7ad4d..568a4f406 100644 --- a/apps/api-gateway/src/issuance/issuance.controller.ts +++ b/apps/api-gateway/src/issuance/issuance.controller.ts @@ -413,6 +413,7 @@ async downloadBulkIssuanceCSVTemplate( @Param('orgId') orgId: string, @User() user: user, @Query(new ValidationPipe({ transform: true })) query: CredentialQuery, + @Res() res: Response, @Body() fileDetails?: object, @UploadedFile() file?: Express.Multer.File ): Promise { @@ -737,7 +738,7 @@ if (id && 'default' === issueCredentialDto.contextCorrelationId) { const webhookUrl = await this.issueCredentialService._getWebhookUrl(issueCredentialDto.contextCorrelationId, id).catch(error => { this.logger.debug(`error in getting webhook url ::: ${JSON.stringify(error)}`); - }); + }); if (webhookUrl) { const plainIssuanceDto = JSON.parse(JSON.stringify(issueCredentialDto)); diff --git a/apps/api-gateway/src/issuance/issuance.service.ts b/apps/api-gateway/src/issuance/issuance.service.ts index f9e615ada..957317f99 100644 --- a/apps/api-gateway/src/issuance/issuance.service.ts +++ b/apps/api-gateway/src/issuance/issuance.service.ts @@ -122,9 +122,9 @@ export class IssuanceService extends BaseService { return this.sendNatsMessage(this.issuanceProxy, 'issue-bulk-credentials', payload); } - async retryBulkCredential(fileId: string, orgId: string, clientDetails: ClientDetails): Promise<{ response: object }> { + async retryBulkCredential(fileId: string, orgId: string, clientDetails: ClientDetails): Promise { const payload = { fileId, orgId, clientDetails }; - return this.sendNats(this.issuanceProxy, 'retry-bulk-credentials', payload); + return this.sendNatsMessage(this.issuanceProxy, 'retry-bulk-credentials', payload); } async _getWebhookUrl(tenantId?: string, orgId?: string): Promise { diff --git a/apps/api-gateway/src/main.ts b/apps/api-gateway/src/main.ts index e9a3fdd99..122572087 100644 --- a/apps/api-gateway/src/main.ts +++ b/apps/api-gateway/src/main.ts @@ -31,7 +31,7 @@ async function bootstrap(): Promise { const expressApp = app.getHttpAdapter().getInstance(); expressApp.set('x-powered-by', false); app.use(express.json({ limit: '50mb' })); - app.use(express.urlencoded({ limit: '50mb' })); + app.use(express.urlencoded({ limit: '50mb', extended: true })); app.use(function (req, res, next) { let err = null; diff --git a/apps/issuance/interfaces/issuance.interfaces.ts b/apps/issuance/interfaces/issuance.interfaces.ts index f70873cc0..b365d16c3 100644 --- a/apps/issuance/interfaces/issuance.interfaces.ts +++ b/apps/issuance/interfaces/issuance.interfaces.ts @@ -141,15 +141,6 @@ export interface ICredential{ '@context':[]; type: string[]; prettyVc?: IPrettyVc; - issuer?: { - id: string; - }; - issuanceDate?: string; - credentialSubject?: ICredentialSubject; -} - -interface ICredentialSubject { - [key: string]: string; } export interface IOptions{ @@ -242,6 +233,9 @@ export interface IClientDetails { fileName?: string; organizationLogoUrl?: string; platformName?: string; + certificate?: string; + size?: string; + orientation?: string; } export interface IIssuedCredentialsSearchInterface { issuedCredentialsSearchCriteria: IIssuedCredentialsSearchCriteria; @@ -292,6 +286,7 @@ export interface SendEmailCredentialOffer { organizationDetails: organisation; platformName?: string, organizationLogoUrl?: string; + prettyVc?: IPrettyVc; } export interface TemplateDetailsInterface { @@ -336,6 +331,9 @@ export interface IQueuePayload{ isLastData: boolean; organizationLogoUrl?: string; platformName?: string; + certificate?: string; + size?: string; + orientation?: string; } interface FileDetails { @@ -368,10 +366,13 @@ export interface IDeletedFileUploadRecords { } export interface BulkPayloadDetails { - clientId: string, - orgId: string, - requestId?: string, - isRetry: boolean - organizationLogoUrl?: string, + clientId: string; + orgId: string; + requestId?: string; + isRetry: boolean; + organizationLogoUrl?: string; platformName?: string; + certificate?: string; + size?: string; + orientation?: string; } diff --git a/apps/issuance/src/issuance.service.ts b/apps/issuance/src/issuance.service.ts index b84594261..31ab5454c 100644 --- a/apps/issuance/src/issuance.service.ts +++ b/apps/issuance/src/issuance.service.ts @@ -10,7 +10,7 @@ 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 { IssuanceProcessState, OrgAgentType, PromiseResult, SchemaType, TemplateIdentifier} from '@credebl/enum/enum'; +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'; import { EmailDto } from '@credebl/common/dtos/email.dto'; @@ -463,7 +463,7 @@ export class IssuanceService { } } -async outOfBandCredentialOffer(outOfBandCredential: OutOfBandCredentialOfferPayload, platformName?: string, organizationLogoUrl?: string): Promise { +async outOfBandCredentialOffer(outOfBandCredential: OutOfBandCredentialOfferPayload, platformName?: string, organizationLogoUrl?: string, prettyVc?: IPrettyVc): Promise { try { const { credentialOffer, @@ -566,6 +566,7 @@ async outOfBandCredentialOffer(outOfBandCredential: OutOfBandCredentialOfferPayl organizationDetails: organisation; platformName?: string; organizationLogoUrl?: string; + prettyVc?: IPrettyVc; } = { credentialType, protocolVersion, @@ -583,7 +584,12 @@ async outOfBandCredentialOffer(outOfBandCredential: OutOfBandCredentialOfferPayl emailId: emailId || '', index: 0, platformName: platformName || null, - organizationLogoUrl: organizationLogoUrl || null + organizationLogoUrl: organizationLogoUrl || null, + prettyVc: { + certificate: prettyVc?.certificate, + size: prettyVc?.size, + orientation: prettyVc?.orientation + } }; if (credentialOffer) { @@ -1229,7 +1235,10 @@ return newCacheKey; isRetry, isLastData: false, organizationLogoUrl: bulkPayloadDetails?.organizationLogoUrl, - platformName: bulkPayloadDetails?.platformName + platformName: bulkPayloadDetails?.platformName, + certificate: bulkPayloadDetails?.certificate, + size: bulkPayloadDetails?.size, + orientation: bulkPayloadDetails?.orientation } })); @@ -1344,7 +1353,10 @@ return newCacheKey; requestId, isRetry: false, organizationLogoUrl: clientDetails?.organizationLogoUrl, - platformName: clientDetails?.platformName + platformName: clientDetails?.platformName, + certificate: clientDetails?.certificate, + size: clientDetails?.size, + orientation: clientDetails?.orientation }; this.processInBatches(bulkPayload, bulkPayloadDetails); @@ -1384,7 +1396,10 @@ return newCacheKey; orgId, isRetry: true, organizationLogoUrl: clientDetails?.organizationLogoUrl, - platformName: clientDetails?.platformName + platformName: clientDetails?.platformName, + certificate: clientDetails?.certificate, + size: clientDetails?.size, + orientation: clientDetails?.orientation }; this.processInBatches(bulkpayloadRetry, bulkPayloadDetails); } catch (error) { @@ -1459,8 +1474,8 @@ return newCacheKey; schemaLedgerId, credentialData: jobDetails.credential_data, orgDid, - orgId, - isReuseConnection: true + orgId + }; prettyVc = { @@ -1471,11 +1486,8 @@ return newCacheKey; oobIssuancepayload = await createOobJsonldIssuancePayload(JsonldCredentialDetails, prettyVc); } - const oobCredentials = await this.outOfBandCredentialOffer( - oobIssuancepayload, jobDetails?.platformName, jobDetails?.organizationLogoUrl - ); - + oobIssuancepayload, jobDetails?.platformName, jobDetails?.organizationLogoUrl, prettyVc); if (oobCredentials) { await this.issuanceRepository.deleteFileDataByJobId(jobDetails.id); } diff --git a/libs/common/src/cast.helper.ts b/libs/common/src/cast.helper.ts index e77b08b47..c1e42fd91 100644 --- a/libs/common/src/cast.helper.ts +++ b/libs/common/src/cast.helper.ts @@ -12,6 +12,7 @@ import { registerDecorator } from 'class-validator'; import { ResponseMessages } from './response-messages'; +import { IJsonldCredential, IPrettyVc } from './interfaces/issuance.interface'; interface ToNumberOptions { default?: number; @@ -199,9 +200,9 @@ export const validateEmail = (email: string): boolean => { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/explicit-function-return-type -export const createOobJsonldIssuancePayload = (JsonldCredentialDetails: IJsonldCredential) => { +export const createOobJsonldIssuancePayload = (JsonldCredentialDetails: IJsonldCredential, prettyVc: IPrettyVc) => { const {credentialData, orgDid, orgId, schemaLedgerId, schemaName} = JsonldCredentialDetails; - const credentialSubject = { 'id': 'did:key:kdfJmG7pi1MnrX4y4nkJe' }; + const credentialSubject = { }; for (const key in credentialData) { if (credentialData.hasOwnProperty(key) && TemplateIdentifier.EMAIL_COLUMN !== key) { @@ -223,7 +224,8 @@ export const createOobJsonldIssuancePayload = (JsonldCredentialDetails: IJsonldC 'id': `${orgDid}` }, 'issuanceDate': new Date().toISOString(), - credentialSubject + credentialSubject, + prettyVc }, 'options': { 'proofType': 'Ed25519Signature2018', diff --git a/libs/common/src/interfaces/issuance.interface.ts b/libs/common/src/interfaces/issuance.interface.ts index ea8192510..4192ebc0d 100644 --- a/libs/common/src/interfaces/issuance.interface.ts +++ b/libs/common/src/interfaces/issuance.interface.ts @@ -86,30 +86,4 @@ export interface IIssuedCredential { size: string; orientation: string; } - - interface ICredentialSubject { - [key: string]: string; - } - - interface ICredential { - "@context": string[]; - type: string[]; - issuer?: { - id: string; - }; - issuanceDate?: string; - credentialSubject?: ICredentialSubject; - } - - interface IOptions { - proofType: string; - proofPurpose: string; - } - - export interface ICredentialData { - emailId?: string; - connectionId?: string; - credential?: ICredential; - options?: IOptions; - } \ No newline at end of file diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index a9ce39275..a4ac67c76 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -484,7 +484,7 @@ model file_upload { organisation organisation? @relation(fields: [orgId], references: [id]) orgId String? @db.Uuid credential_type String? - templateId String? @db.VarChar + templateId String? @db.VarChar } model file_data {