Skip to content

Commit

Permalink
feat: event certificate (#665)
Browse files Browse the repository at this point in the history
Signed-off-by: tipusinghaw <[email protected]>
  • Loading branch information
tipusinghaw authored Apr 15, 2024
1 parent 812d205 commit c295851
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 23 deletions.
11 changes: 3 additions & 8 deletions apps/api-gateway/src/user/dto/share-certificate.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,19 @@ interface Attribute {
value: string;
}
export class CreateCertificateDto {

@ApiProperty()
@IsNotEmpty({ message: 'Please provide valid schemaId' })
@IsNotEmpty({ message: 'Please provide valid credentialId' })
@Transform(({ value }) => trim(value))
@IsString({ message: 'credentialId should be string' })
credentialId: string;

@ApiProperty({ example: 'SchemaId' })
@ApiProperty({ example: 'schemaId' })
@IsNotEmpty({ message: 'Please provide valid schemaId' })
@Transform(({ value }) => trim(value))
@IsString({ message: 'schemaId should be string' })
schemaId: string;

@ApiProperty({ example: 'CredDefId' })
@IsNotEmpty({ message: 'Please provide valid schemaId' })
@Transform(({ value }) => trim(value))
@IsString({ message: 'credDefId should be string' })
credDefId?: string;

@ApiProperty({
example: [
{
Expand Down
1 change: 0 additions & 1 deletion apps/user/interfaces/user.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ export interface ISendVerificationEmail {

export interface IShareUserCertificate {
schemaId: string;
credDefId: string;
credentialId: string;
attributes: Attribute[];
invitationUrl?: string;
Expand Down
45 changes: 38 additions & 7 deletions apps/user/src/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import { UserActivityService } from '@credebl/user-activity';
import { SupabaseService } from '@credebl/supabase';
import { UserDevicesRepository } from '../repositories/user-device.repository';
import { v4 as uuidv4 } from 'uuid';
import { EcosystemConfigSettings, UserCertificateId } from '@credebl/enum/enum';
import { EcosystemConfigSettings, UserCertificateId, CertificateDetails} from '@credebl/enum/enum';
import { WinnerTemplate } from '../templates/winner-template';
import { ParticipantTemplate } from '../templates/participant-template';
import { ArbiterTemplate } from '../templates/arbiter-template';
Expand All @@ -60,6 +60,9 @@ import { IUsersActivity } from 'libs/user-activity/interface';
import { ISendVerificationEmail, ISignInUser, IVerifyUserEmail, IUserInvitations, IResetPasswordResponse } from '@credebl/common/interfaces/user.interface';
import { AddPasskeyDetailsDto } from 'apps/api-gateway/src/user/dto/add-user.dto';
import { URLUserResetPasswordTemplate } from '../templates/reset-password-template';
import { EventPinnacle } from '../templates/event-pinnacle';
import { EventCertificate } from '../templates/event-certificates';
import * as QRCode from 'qrcode';

@Injectable()
export class UserService {
Expand Down Expand Up @@ -832,6 +835,7 @@ export class UserService {

async shareUserCertificate(shareUserCertificate: IShareUserCertificate): Promise<string> {

let template;
const attributeArray = [];
let attributeJson = {};
const attributePromises = shareUserCertificate.attributes.map(async (iterator: Attribute) => {
Expand All @@ -841,8 +845,6 @@ export class UserService {
attributeArray.push(attributeJson);
});
await Promise.all(attributePromises);
let template;

switch (shareUserCertificate.schemaId.split(':')[2]) {
case UserCertificateId.WINNER:
// eslint-disable-next-line no-case-declarations
Expand All @@ -864,22 +866,35 @@ export class UserService {
const userWorldRecordTemplate = new WorldRecordTemplate();
template = await userWorldRecordTemplate.getWorldRecordTemplate(attributeArray);
break;
case UserCertificateId.AYANWORKS_EVENT:
// eslint-disable-next-line no-case-declarations
const QRDetails = await this.getShorteningURL(shareUserCertificate, attributeArray);

if (shareUserCertificate.attributes.some(item => item.value.toLocaleLowerCase().includes("pinnacle"))) {
const userPinnacleTemplate = new EventPinnacle();
template = await userPinnacleTemplate.getPinnacleWinner(attributeArray, QRDetails);
} else {
const userCertificateTemplate = new EventCertificate();
template = await userCertificateTemplate.getCertificateWinner(attributeArray, QRDetails);
}
break;
default:
throw new NotFoundException('error in get attributes');
}

const option: IPuppeteerOption = {height: 0, width: 1000};
//Need to handle the option for all type of certificate
const option: IPuppeteerOption = {height: 974, width: 1606};

const imageBuffer =
await this.convertHtmlToImage(template, shareUserCertificate.credentialId, option);
const verifyCode = uuidv4();

const imageUrl = await this.awsService.uploadUserCertificate(
imageBuffer,
'svg',
'certificates',
process.env.AWS_PUBLIC_BUCKET_NAME,
'base64'
'base64',
'certificates'
);
const existCredentialId = await this.userRepository.getUserCredentialsById(shareUserCertificate.credentialId);

Expand All @@ -905,7 +920,7 @@ export class UserService {
const browser = await puppeteer.launch({
executablePath: '/usr/bin/google-chrome',
args: ['--no-sandbox', '--disable-setuid-sandbox'],
protocolTimeout: 200000,
protocolTimeout: 800000, //initial - 200000
headless: true
});

Expand All @@ -919,6 +934,22 @@ export class UserService {
return screenshot;
}

//Need to add interface
async getShorteningURL(shareUserCertificate, attributeArray): Promise<unknown> {
const urlObject = {
schemaId: shareUserCertificate.schemaId,
credDefId: shareUserCertificate.credDefId,
attribute: attributeArray,
credentialId:shareUserCertificate.credentialId,
email:attributeArray.find((attr) => "email" in attr).email
};

const qrCodeOptions = { type: 'image/png' };
const encodedData = Buffer.from(JSON.stringify(shareUserCertificate)).toString('base64');
const qrCode = await QRCode.toDataURL(`https://credebl.id/c_v?${encodedData}`, qrCodeOptions);

return qrCode;
}
/**
*
* @param acceptRejectInvitation
Expand Down
82 changes: 82 additions & 0 deletions apps/user/templates/event-certificates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Attribute } from '../interfaces/user.interface';

export class EventCertificate {
findAttributeByName(attributes: Attribute[], name: string): Attribute {
return attributes.find((attr) => name in attr);
}

async getCertificateWinner(attributes: Attribute[], QRDetails): Promise<string> {
try {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [name, description, category] = await Promise.all(attributes).then((attributes) => {
const name = this.findAttributeByName(attributes, 'name').name ?? '';
const description = this.findAttributeByName(attributes, 'description').description ?? '';
const category = this.findAttributeByName(attributes, 'category').category ?? '';
return [name, description, category];
});
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Federo&family=Gentium+Book+Plus:ital,wght@0,400;0,700;1,400;1,700&family=MonteCarlo&display=swap" rel="stylesheet">
<title>Document</title>
<style>
p, h1, h2, h3 {
margin: 0;
}
</style>
</head>
<body style="font-family: 'Gentium Book Plus', serif; font-weight: 400; font-style: normal;">
<div style="position: relative; margin: auto; width: fit-content;">
<div style="width: 1591px;min-width: 1591px;">
<img style="width: 1591px;min-width: 1591px;" src="https://credebl-dev-user-certificate.s3.ap-south-1.amazonaws.com/certificates/cert-background.png" />
</div>
<div style="box-shadow: 0px 0px 25px -10px black;padding: 2rem 6rem;position: absolute;width: 1372px;min-width: 1398px;height: calc(100% - 4.45rem + 1.5px);top: 0px;">
<div style="position: absolute; right: 4rem; top: 3rem">
<img style="height: 65px;" src="https://credebl-dev-user-certificate.s3.ap-south-1.amazonaws.com/certificates/anniversary.svg" />
</div>
<div style="display: flex; flex-direction: column; justify-content: center; align-items: center; margin: 0 auto;">
<div>
<img style="height: 68px;" src="https://credebl-dev-user-certificate.s3.ap-south-1.amazonaws.com/certificates/ayanworks-logo.svg" />
</div>
<h2 style="color: #10005F; font-size: 60px; font-weight: 400;">Certificate of Appreciation</h2>
<h1 style="color: #10005F; font-size: 86px; font-family: 'Federo', sans-serif; font-weight: 600;">- ${category} -</h1>
<div style="font-size: 32px; text-align: center; max-width: 1082px;">
<p style="color: #342094;">this certificate is proudly presented to</p>
<h1 style="color: #02205F; font-size: 120px; font-family: 'MonteCarlo', cursive; font-weight: 400;">${name}</h1>
<p style="color: #342094;">${description}</p>
<p style="font-weight: bolder; color: #342094;">~ 23rd March 2024 ~</p>
</div>
<div style="position: absolute;
left: 9.5rem;
bottom: 2.5rem;">
<img src="${QRDetails}" style="width: 220px; height: 220px;" alt="QR Code" flex; margin-right: 10px;/>
</div>
<div style="display: flex; justify-content: center; gap: 3rem; font-size: 32px; text-align: center; margin-top: 1rem; position: absolute; bottom: 3rem; color: #342094;">
<div style="display: flex; flex-direction: column; align-items: center;">
<img style="height: 99px" src="https://credebl-dev-user-certificate.s3.ap-south-1.amazonaws.com/certificates/kk-sign.svg" />
<div style="border-top: 1px solid #000000; margin-top: -1rem; padding: 1rem 2rem 0 2rem;">
<p>Kirankalyan Kulkarni</p>
<p>CEO & Co-Founder</p>
</div>
</div>
<div style="display: flex; flex-direction: column; align-items: center;">
<img style="height: 99px" src="https://credebl-dev-user-certificate.s3.ap-south-1.amazonaws.com/certificates/ak-sign.svg" />
<div style="border-top: 1px solid #000000; margin-top: -1rem; padding: 1rem 2rem 0 2rem;">
<p>Ajay Jadhav</p>
<p>CTO & Co-Founder</p>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
`;
} catch { }
}
}
80 changes: 80 additions & 0 deletions apps/user/templates/event-pinnacle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Attribute } from '../interfaces/user.interface';

export class EventPinnacle {
findAttributeByName(attributes: Attribute[], name: string): Attribute {
return attributes.find((attr) => name in attr);
}

async getPinnacleWinner(attributes: Attribute[], qrCode): Promise<string> {
try {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [name, description] = await Promise.all(attributes).then((attributes) => {
const name = this.findAttributeByName(attributes, 'name').name ?? '';
const description = this.findAttributeByName(attributes, 'description').description ?? '';
return [name, description];
});
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Federo&family=Gentium+Book+Plus:ital,wght@0,400;0,700;1,400;1,700&family=MonteCarlo&display=swap" rel="stylesheet">
<title>Document</title>
<style>
p, h1, h2, h3 {
margin: 0;
}
</style>
</head>
<body style="font-family: 'Gentium Book Plus', serif; font-weight: 400; font-style: normal;">
<div style="position: relative; margin: auto; width: fit-content;">
<div style="width: 1591px;min-width: 1591px;">
<img style="width: 1591px;min-width: 1591px;" src="https://credebl-dev-user-certificate.s3.ap-south-1.amazonaws.com/certificates/pinnacle-background.png" />
</div>
<div style="box-shadow: 0px 0px 25px -10px black;padding: 2rem 6rem;position: absolute;width: 1372px;min-width: 1398px;height: calc(100% - 4.45rem + 1.5px);top: 0px;">
<div style="position: absolute; right: 4rem; bottom: 3rem">
<img style="height: 70px;" src="https://credebl-dev-user-certificate.s3.ap-south-1.amazonaws.com/certificates/anniversary.svg" />
</div>
<div style="position: absolute;left: 8rem;bottom: 3rem">
<img style="height: 230px; width: 230px;" src="${qrCode}" />
</div>
<div style="display: flex; flex-direction: column; justify-content: center; align-items: center; margin: 0 auto;">
<div>
<img style="height: 68px;" src="https://credebl-dev-user-certificate.s3.ap-south-1.amazonaws.com/certificates/ayanworks-logo-invert.svg" />
</div>
<h2 style="color: #FFFFFF; font-size: 60px; font-weight: 400;">Certificate of Appreciation</h2>
<h1 style="color: #FFFFFF; font-size: 86px; font-family: 'Federo', sans-serif; font-weight: 600;">- Pinnacle Performer -</h1>
<div style="font-size: 32px; text-align: center; max-width: 1082px;">
<p style="color: #342094;">this certificate is proudly presented to</p>
<h1 style="font-size: 120px;font-family: 'MonteCarlo', cursive;font-weight: 400;background-image: url(https://credebl-dev-user-certificate.s3.ap-south-1.amazonaws.com/certificates/golden-name.svg);color: transparent;background-repeat: no-repeat;background-size: cover;background-position: top;background-clip: text;-webkit-background-clip: text;">${name}</h1>
<p style="color: #342094;">${description}</p>
<p style="font-weight: bolder; color: #342094;">~ 23rd March 2024 ~</p>
</div>
<div style="display: flex; justify-content: flex-end; gap: 3rem; font-size: 32px; text-align: center; margin-top: 1rem; position: absolute; bottom: 3rem; color: #342094;">
<div style="display: flex; flex-direction: column; align-items: center;">
<img style="height: 99px" src="https://credebl-dev-user-certificate.s3.ap-south-1.amazonaws.com/certificates/kk-sign.svg" />
<div style="border-top: 1px solid #000000; margin-top: -1rem; padding: 1rem 2rem 0 2rem;">
<p>Kirankalyan Kulkarni</p>
<p>CEO & Co-Founder</p>
</div>
</div>
<div style="display: flex; flex-direction: column; align-items: center;">
<img style="height: 99px" src="https://credebl-dev-user-certificate.s3.ap-south-1.amazonaws.com/certificates/ak-sign.svg" />
<div style="border-top: 1px solid #000000; margin-top: -1rem; padding: 1rem 2rem 0 2rem;">
<p>Ajay Jadhav</p>
<p>CTO & Co-Founder</p>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
`;
} catch { }
}
}
6 changes: 3 additions & 3 deletions libs/aws/src/aws.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ export class AwsService {

try {
await putObjectAsync({
Bucket: `${process.env.AWS_ORG_LOGO_BUCKET_NAME}`,
Key: `${pathAWS}/${encodeURIComponent(filename)}-${timestamp}.png`,
Bucket: `${bucketName}`,
Key: `${pathAWS}/${encodeURIComponent(filename)}-${timestamp}.${ext}`,
Body: fileBuffer,
ContentEncoding: encoding,
ContentType: `image/png`
});

const imageUrl = `https://${process.env.AWS_ORG_LOGO_BUCKET_NAME}.s3.${process.env.AWS_PUBLIC_REGION}.amazonaws.com/${pathAWS}/${encodeURIComponent(filename)}-${timestamp}.${ext}`;
const imageUrl = `https://${bucketName}.s3.${process.env.AWS_PUBLIC_REGION}.amazonaws.com/${pathAWS}/${encodeURIComponent(filename)}-${timestamp}.${ext}`;
return imageUrl;
} catch (error) {
throw new HttpException(error, HttpStatus.SERVICE_UNAVAILABLE);
Expand Down
14 changes: 10 additions & 4 deletions libs/enum/src/enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,13 @@ export enum AgentSpinUpStatus {
COMPLETED = 2
}


export enum UserCertificateId {
WINNER = 'Winner',
PARTICIPANT = 'Participant',
ARBITER = 'Arbiter',
WORLD_RECORD = 'WorldRecord'
WINNER = 'Winner',
PARTICIPANT = 'Participant',
ARBITER = 'Arbiter',
WORLD_RECORD = 'WorldRecord',
AYANWORKS_EVENT ='Appreciation Certificate'
}

export enum NodeEnvironment {
Expand All @@ -102,4 +104,8 @@ const transitionMap: { [key in Invitation]: Invitation[] } = {
[Invitation.REJECTED]: []
};

export enum CertificateDetails {
PINNACLE_CRED_DEF = 'PKDMuYSzJE22Jkh4B1EMiX:3:CL:826:Pinnacle Certificate'
}

export const transition = (currentStatus: Invitation, nextStatus: Invitation): boolean => (transitionMap[currentStatus].includes(nextStatus));

0 comments on commit c295851

Please sign in to comment.