From e74b44dc3526e4992dadcc9f83ef9840bfb1ce35 Mon Sep 17 00:00:00 2001 From: Dhruvil Mehta <68022411+dhruvilmehta@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:12:22 -0700 Subject: [PATCH] Merged generate PDF/PNG logic into 1 file --- .../certificate/generateCertificate.ts | 123 ++++++++++++++++++ src/actions/certificate/generatePdf.ts | 55 -------- src/actions/certificate/generatePng.ts | 57 -------- src/app/api/certificate/get/route.ts | 21 ++- src/app/api/certificate/share/route.ts | 104 --------------- src/components/Certificate.tsx | 2 +- 6 files changed, 140 insertions(+), 222 deletions(-) create mode 100644 src/actions/certificate/generateCertificate.ts delete mode 100644 src/actions/certificate/generatePdf.ts delete mode 100644 src/actions/certificate/generatePng.ts delete mode 100644 src/app/api/certificate/share/route.ts diff --git a/src/actions/certificate/generateCertificate.ts b/src/actions/certificate/generateCertificate.ts new file mode 100644 index 000000000..0b60b1537 --- /dev/null +++ b/src/actions/certificate/generateCertificate.ts @@ -0,0 +1,123 @@ +import { createCanvas, loadImage } from 'canvas'; +import { PDFDocument, rgb } from 'pdf-lib'; +import fs from 'fs'; + +const getTextProperties = ( + certificateId: string, + userName: string, + hostname: string, +) => { + return [ + { text: 'CERTIFICATE', fontSize: 80, offsetY: 450 }, + { text: 'OF COMPLETION', fontSize: 35, offsetY: 400 }, + { + text: 'This certificate is proudly presented to', + fontSize: 30, + offsetY: 100, + }, + { text: userName, fontSize: 65, offsetY: 0 }, + { + text: "For Successfully Completing the '0-1' cohort offered by 100xdevs", + fontSize: 30, + offsetY: -100, + }, + { text: 'HARKIRAT SINGH', fontSize: 30, offsetY: -400 }, + { text: 'Instructor', fontSize: 25, offsetY: -450 }, + { text: `Certificate id: ${certificateId}`, fontSize: 20, offsetY: -500 }, + { + text: `Verify at: http://${hostname}/certificate/verify/${certificateId}`, + fontSize: 20, + offsetY: -530, + }, + ]; +}; +export const generatePng = async ( + certificateId: string, + userName: string, + hostname: string, +) => { + const textProperties = getTextProperties(certificateId, userName, hostname); + + const certiTemplate = await loadImage( + 'src/app/api/certificate/certitemplate.png', + ); + const canvas = createCanvas(certiTemplate.width, certiTemplate.height); + const ctx = canvas.getContext('2d'); + + ctx.drawImage(certiTemplate, 0, 0, canvas.width, canvas.height); + + ctx.fillStyle = 'black'; + ctx.textAlign = 'center'; + + for (const { text, fontSize, offsetY } of textProperties) { + ctx.font = `${fontSize}px "Times New Roman"`; + ctx.fillText(text, canvas.width / 2, canvas.height / 2 - offsetY); + } + + const sign = await loadImage('src/app/api/certificate/sign.png'); + + ctx.drawImage( + sign, + canvas.width / 2 - sign.width / 2, + canvas.height / 2 + 350 - sign.height, + ); + + const buffer = canvas.toBuffer('image/png'); + return buffer; +}; + +export const generatePdf = async ( + certificateId: string, + userName: string, + hostname: string, +) => { + const textProperties = getTextProperties(certificateId, userName, hostname); + + const [imageData, signData] = await Promise.all([ + fs.promises.readFile('src/app/api/certificate/certitemplate.png'), + fs.promises.readFile('src/app/api/certificate/sign.png'), + ]); + + const pdfDoc = await PDFDocument.create(); + const page = pdfDoc.addPage([2000, 1545]); + + const image = await pdfDoc.embedPng(imageData); + const { width: imgWidth, height: imgHeight } = image.scaleToFit( + page.getWidth(), + page.getHeight(), + ); + page.drawImage(image, { + x: page.getWidth() / 2 - imgWidth / 2, + y: page.getHeight() / 2 - imgHeight / 2, + width: imgWidth, + height: imgHeight, + }); + const font = await pdfDoc.embedFont('Times-Roman'); + for (const { text, fontSize, offsetY } of textProperties) { + const textWidth = font.widthOfTextAtSize(text, fontSize); + const textX = (page.getWidth() - textWidth) / 2; + const textY = page.getHeight() / 2 + offsetY; + page.drawText(text, { + x: textX, + y: textY, + size: fontSize, + color: rgb(0, 0, 0), + font, + }); + } + + const sign = await pdfDoc.embedPng(signData); + const { width: signWidth, height: signHeight } = sign.scaleToFit( + page.getWidth() * 0.5, + page.getHeight() * 0.1, + ); + page.drawImage(sign, { + x: page.getWidth() / 2 - signWidth / 2, + y: page.getHeight() / 2 - 350, + width: signWidth, + height: signHeight, + }); + + const pdfBytes = await pdfDoc.save(); + return pdfBytes; +}; diff --git a/src/actions/certificate/generatePdf.ts b/src/actions/certificate/generatePdf.ts deleted file mode 100644 index 0310142cd..000000000 --- a/src/actions/certificate/generatePdf.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { PDFDocument, rgb } from 'pdf-lib'; -import fs from 'fs'; - -import { getTextProperties } from './generatePng'; -export const generatePdf = async (certificateId: string, userName: string) => { - const textProperties = getTextProperties(certificateId, userName); - - const [imageData, signData] = await Promise.all([ - fs.promises.readFile('src/app/api/certificate/certitemplate.png'), - fs.promises.readFile('src/app/api/certificate/sign.png'), - ]); - - const pdfDoc = await PDFDocument.create(); - const page = pdfDoc.addPage([2000, 1545]); - - const image = await pdfDoc.embedPng(imageData); - const { width: imgWidth, height: imgHeight } = image.scaleToFit( - page.getWidth(), - page.getHeight(), - ); - page.drawImage(image, { - x: page.getWidth() / 2 - imgWidth / 2, - y: page.getHeight() / 2 - imgHeight / 2, - width: imgWidth, - height: imgHeight, - }); - const font = await pdfDoc.embedFont('Times-Roman'); - for (const { text, fontSize, offsetY } of textProperties) { - const textWidth = font.widthOfTextAtSize(text, fontSize); - const textX = (page.getWidth() - textWidth) / 2; - const textY = page.getHeight() / 2 + offsetY; - page.drawText(text, { - x: textX, - y: textY, - size: fontSize, - color: rgb(0, 0, 0), - font, - }); - } - - const sign = await pdfDoc.embedPng(signData); - const { width: signWidth, height: signHeight } = sign.scaleToFit( - page.getWidth() * 0.5, - page.getHeight() * 0.1, - ); - page.drawImage(sign, { - x: page.getWidth() / 2 - signWidth / 2, - y: page.getHeight() / 2 - 350, - width: signWidth, - height: signHeight, - }); - - const pdfBytes = await pdfDoc.save(); - return pdfBytes; -}; diff --git a/src/actions/certificate/generatePng.ts b/src/actions/certificate/generatePng.ts deleted file mode 100644 index ebd6d84e3..000000000 --- a/src/actions/certificate/generatePng.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { createCanvas, loadImage } from 'canvas'; - -export const getTextProperties = (certificateId: string, userName: string) => { - return [ - { text: 'CERTIFICATE', fontSize: 80, offsetY: 450 }, - { text: 'OF COMPLETION', fontSize: 35, offsetY: 400 }, - { - text: 'This certificate is proudly presented to', - fontSize: 30, - offsetY: 100, - }, - { text: userName, fontSize: 65, offsetY: 0 }, - { - text: "For Successfully Completing the '0-1' cohort offered by 100xdevs", - fontSize: 30, - offsetY: -100, - }, - { text: 'HARKIRAT SINGH', fontSize: 30, offsetY: -400 }, - { text: 'Instructor', fontSize: 25, offsetY: -450 }, - { text: `Certificate id: ${certificateId}`, fontSize: 20, offsetY: -500 }, - { - text: `Verify at: https://app.100xdevs.com/certificate/verify/${certificateId}`, - fontSize: 20, - offsetY: -530, - }, - ]; -}; -export const generatePng = async (certificateId: string, userName: string) => { - const textProperties = getTextProperties(certificateId, userName); - - const certiTemplate = await loadImage( - 'src/app/api/certificate/certitemplate.png', - ); - const canvas = createCanvas(certiTemplate.width, certiTemplate.height); - const ctx = canvas.getContext('2d'); - - ctx.drawImage(certiTemplate, 0, 0, canvas.width, canvas.height); - - ctx.fillStyle = 'black'; - ctx.textAlign = 'center'; - - for (const { text, fontSize, offsetY } of textProperties) { - ctx.font = `${fontSize}px "Times New Roman"`; - ctx.fillText(text, canvas.width / 2, canvas.height / 2 - offsetY); - } - - const sign = await loadImage('src/app/api/certificate/sign.png'); - - ctx.drawImage( - sign, - canvas.width / 2 - sign.width / 2, - canvas.height / 2 + 350 - sign.height, - ); - - const buffer = canvas.toBuffer('image/png'); - return buffer; -}; diff --git a/src/app/api/certificate/get/route.ts b/src/app/api/certificate/get/route.ts index 8a3c3f944..c27bc6f53 100644 --- a/src/app/api/certificate/get/route.ts +++ b/src/app/api/certificate/get/route.ts @@ -2,10 +2,17 @@ import { NextRequest, NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from '@/lib/auth'; import db from '@/db'; -import { generatePdf } from '@/actions/certificate/generatePdf'; -import { generatePng } from '@/actions/certificate/generatePng'; +import { + generatePng, + generatePdf, +} from '@/actions/certificate/generateCertificate'; +import { headers } from 'next/headers'; export async function GET(req: NextRequest) { + const headersList = headers(); + const hostname = headersList.get('x-forwarded-host'); + if (!hostname) return new NextResponse('No host found', { status: 400 }); + const url = new URL(req.url); const searchParams = new URLSearchParams(url.search); @@ -21,7 +28,11 @@ export async function GET(req: NextRequest) { }); if (!certificate) return new NextResponse('Cannot find certificate', { status: 400 }); - const data = await generatePng(certId, certificate.user.name || ''); + const data = await generatePng( + certId, + certificate.user.name || '', + hostname, + ); return new NextResponse(data, { headers: { 'Content-Type': 'image/png', @@ -68,7 +79,7 @@ export async function GET(req: NextRequest) { try { if (type === 'pdf') { - const data = await generatePdf(certificateId, userName); + const data = await generatePdf(certificateId, userName, hostname); return new NextResponse(data, { headers: { 'Content-Type': 'application/pdf', @@ -78,7 +89,7 @@ export async function GET(req: NextRequest) { } if (type === 'png') { - const data = await generatePng(certificateId, userName); + const data = await generatePng(certificateId, userName, hostname); return new NextResponse(data, { headers: { 'Content-Type': 'image/png', diff --git a/src/app/api/certificate/share/route.ts b/src/app/api/certificate/share/route.ts deleted file mode 100644 index 345079059..000000000 --- a/src/app/api/certificate/share/route.ts +++ /dev/null @@ -1,104 +0,0 @@ -import db from '@/db'; -import { authOptions } from '@/lib/auth'; -import { createCanvas, loadImage } from 'canvas'; -import fs from 'fs'; -import { getServerSession } from 'next-auth'; -import { NextRequest, NextResponse } from 'next/server'; - -export async function GET(req: NextRequest) { - const session = await getServerSession(authOptions); - const user = session.user; - if (!user) return new NextResponse('Login required', { status: 400 }); - - const url = new URL(req.url); - const searchParams = new URLSearchParams(url.search); - const courseId = searchParams.get('courseId'); - - if (!courseId) return new NextResponse('No course id found', { status: 400 }); - - const purchase = await db.userPurchases.findFirst({ - where: { - userId: user.id, - courseId: parseInt(courseId, 10), - }, - }); - - if (!purchase) return new NextResponse('No Purchase found', { status: 400 }); - let certificate = await db.certificate.findFirst({ - where: { - userId: user.id, - courseId: parseInt(courseId, 10), - }, - }); - if (!certificate) { - certificate = await db.certificate.create({ - data: { - userId: user.id, - courseId: parseInt(courseId, 10), - }, - }); - } - - const certificateId = certificate.id; - const userName = session.user.name; - - const certiTemplate = await loadImage( - 'src/app/api/certificate/certitemplate.png', - ); - - const canvas = createCanvas(certiTemplate.width, certiTemplate.height); - const ctx = canvas.getContext('2d'); - - ctx.drawImage(certiTemplate, 0, 0, canvas.width, canvas.height); - - const textProperties = [ - { text: 'CERTIFICATE', fontSize: 80, offsetY: 450 }, - { text: 'OF COMPLETION', fontSize: 35, offsetY: 400 }, - { - text: 'This certificate is proudly presented to', - fontSize: 30, - offsetY: 100, - }, - { text: userName, fontSize: 65, offsetY: 0 }, - { - text: "For Successfully Completing the '0-1' cohort offered by 100xdevs", - fontSize: 30, - offsetY: -100, - }, - { text: 'HARKIRAT SINGH', fontSize: 30, offsetY: -400 }, - { text: 'Instructor', fontSize: 25, offsetY: -450 }, - { text: `Certificate id: ${certificateId}`, fontSize: 20, offsetY: -500 }, - { - text: 'Verify at: https://app.100xdevs.com/verify', - fontSize: 20, - offsetY: -530, - }, - ]; - - ctx.fillStyle = 'black'; - ctx.textAlign = 'center'; - - for (const { text, fontSize, offsetY } of textProperties) { - ctx.font = `${fontSize}px "Times New Roman"`; - ctx.fillText(text, canvas.width / 2, canvas.height / 2 - offsetY); - } - - const sign = await loadImage('src/app/api/certificate/sign.png'); - - ctx.drawImage( - sign, - canvas.width / 2 - sign.width / 2, - canvas.height / 2 + 350 - sign.height, - ); - - const buffer = canvas.toBuffer('image/png'); - - fs.writeFileSync('certificate.png', buffer); - - return new NextResponse(buffer, { - headers: { - 'Content-Type': 'image/png', - 'Content-Disposition': 'attachment; filename="certificate.png"', - }, - }); -} diff --git a/src/components/Certificate.tsx b/src/components/Certificate.tsx index 83604172e..1e6962de1 100644 --- a/src/components/Certificate.tsx +++ b/src/components/Certificate.tsx @@ -44,7 +44,7 @@ export const CertificateComponent = ({ course, certificateId }: any) => { const handleShareLinkedIn = async () => { const certificateUrl = `${window.location.origin}/certificate/verify/${certificateId}`; - const shareUrl = `https://www.linkedin.com/sharing/share-offsite/?url={"${certificateUrl}"}`; + const shareUrl = `https://www.linkedin.com/shareArticle?mini=true&url="${certificateUrl}"`; window.open(shareUrl); };