Skip to content

Commit 234213c

Browse files
committed
feat: student certificate download
1 parent de576dc commit 234213c

3 files changed

Lines changed: 81 additions & 40 deletions

File tree

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { Controller, Get, Res, UseGuards } from "@nestjs/common";
2-
import { AllowAnonymous } from "@thallesp/nestjs-better-auth";
1+
import { Controller, Get, Query, Res, UseGuards } from "@nestjs/common";
2+
import { AllowAnonymous, Session, type UserSession } from "@thallesp/nestjs-better-auth";
3+
import axios from "axios";
34
import { type Response } from "express";
45
import { UserParticipatedGuard } from "src/common/guards/user-particicated.guard";
56
import { StudentCertificateService } from "./student-certificate.service";
@@ -9,22 +10,35 @@ export class StudentCertificateController {
910
constructor(private readonly studentCertificateService: StudentCertificateService) {}
1011

1112
@Get("/preview")
12-
// @UseGuards(UserParticipatedGuard)
13-
@AllowAnonymous()
14-
async certificatePreview(@Res() res: Response) {
15-
const imageBuffer = await this.studentCertificateService.generateImage("asdasda");
13+
@UseGuards(UserParticipatedGuard)
14+
async certificatePreview(@Res() res: Response, @Session() session: UserSession) {
15+
const fileUrl = await this.studentCertificateService.getCertificatePreview(res, session.user.id);
16+
17+
const response = await axios.get(fileUrl, {
18+
responseType: "stream",
19+
});
1620

1721
res.set({
18-
"Content-Type": "image/png",
19-
"Content-Disposition": `attachment; filename=${"asdas"}.png`,
22+
"Content-Type": response.headers["content-type"],
2023
});
2124

22-
res.send(imageBuffer);
25+
response.data.pipe(res);
2326
}
2427

25-
@Get("/download")
28+
@Get("/")
2629
@UseGuards(UserParticipatedGuard)
27-
certificateDownload() {
28-
return this.studentCertificateService;
30+
async certificateFull(@Res() res: Response, @Session() session: UserSession) {
31+
const fileUrl = await this.studentCertificateService.getCertificateFull(res, session.user.id);
32+
33+
const response = await axios.get(fileUrl, {
34+
responseType: "stream",
35+
});
36+
37+
res.set({
38+
"Content-Type": "application/pdf",
39+
"Content-Disposition": `inline; filename="certificate-${session.user.id}.pdf"`,
40+
});
41+
42+
response.data.pipe(res);
2943
}
3044
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { Module } from "@nestjs/common";
2+
import { S3Module } from "src/core/s3/s3.module";
23
import { StudentCertificateController } from "./student-certificate.controller";
34
import { StudentCertificateService } from "./student-certificate.service";
45

56
@Module({
67
controllers: [StudentCertificateController],
78
providers: [StudentCertificateService],
9+
imports: [S3Module],
810
})
911
export class StudentCertificateModule {}
Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,68 @@
1-
import path from "node:path";
2-
import { Injectable } from "@nestjs/common";
3-
import { createCanvas, loadImage, registerFont } from "canvas";
1+
import { HttpException, Injectable, InternalServerErrorException, NotFoundException } from "@nestjs/common";
2+
import { Response } from "express";
3+
import { LoggerService } from "src/core/logger/logger.service";
44
import { PrismaService } from "src/core/prisma/prisma.service";
5+
import { S3Service } from "src/core/s3/s3.service";
56

67
@Injectable()
78
export class StudentCertificateService {
8-
constructor(private readonly prisma: PrismaService) {}
9+
constructor(
10+
private readonly prisma: PrismaService,
11+
private readonly s3: S3Service,
12+
private readonly logger: LoggerService,
13+
) {}
914

10-
async certificatePreview() {}
15+
async getCertificatePreview(res: Response, userId: string): Promise<string> {
16+
try {
17+
const application = await this.prisma.studentApplication.findMany({
18+
where: {
19+
std_user_id: userId,
20+
},
21+
});
1122

12-
async generateImage(name: string): Promise<Buffer> {
13-
// Load background
14-
const bgPath = path.join(__dirname, "./certificate_template.png");
15-
const background = await loadImage(bgPath);
23+
const fileKey = `${application[0].std_application_id}.jpg`;
1624

17-
// Register custom font
18-
const fontPath = path.join(__dirname, "./Arimo-VariableFont_wght.ttf");
19-
registerFont(fontPath, { family: "CustomFont" });
25+
const fileUrl = await this.s3.signedUrl(fileKey, "certificate");
2026

21-
// Create canvas
22-
const canvas = createCanvas(background.width, background.height);
23-
const ctx = canvas.getContext("2d");
27+
if (!fileUrl) {
28+
throw new NotFoundException("Cannot find this certificate in storage server");
29+
}
2430

25-
// Draw background
26-
ctx.drawImage(background, 0, 0);
31+
return fileUrl;
32+
} catch (e) {
33+
this.logger.error(e);
34+
if (e instanceof HttpException) {
35+
throw e;
36+
}
2737

28-
// Set font
29-
ctx.font = '40px "CustomFont"';
30-
ctx.fillStyle = "#000000";
38+
throw new InternalServerErrorException(e);
39+
}
40+
}
41+
42+
async getCertificateFull(res: Response, userId: string) {
43+
try {
44+
const application = await this.prisma.studentApplication.findMany({
45+
where: {
46+
std_user_id: userId,
47+
},
48+
});
49+
50+
const fileKey = `${application[0].std_application_id}.pdf`;
51+
52+
const fileUrl = await this.s3.signedUrl(fileKey, "certificate");
3153

32-
// Center text
33-
const textWidth = ctx.measureText(name).width;
34-
const x = (canvas.width - textWidth) / 2;
35-
const y = canvas.height / 2;
54+
if (!fileUrl) {
55+
throw new NotFoundException("Cannot find this certificate in storage server");
56+
}
3657

37-
// Draw name
38-
ctx.fillText(name, x, y);
58+
return fileUrl;
59+
} catch (e) {
60+
this.logger.error(e);
61+
if (e instanceof HttpException) {
62+
throw e;
63+
}
3964

40-
// Return image buffer
41-
return canvas.toBuffer("image/png");
65+
throw new InternalServerErrorException(e);
66+
}
4267
}
4368
}

0 commit comments

Comments
 (0)