feat: certificate page for print, API certificate data, AI prompts for full lessons
- Certificate: page /certificate/[courseId] with print-friendly design, GET /certificates/:courseId/data - Buttons 'Получить сертификат' open certificate page instead of data-URL - AI: prompts for longer courses (more chapters/lessons), full detailed lesson content with examples (1000–1500+ words) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -10,6 +10,12 @@ import { User } from '@coursecraft/database';
|
||||
export class CertificatesController {
|
||||
constructor(private certificatesService: CertificatesService) {}
|
||||
|
||||
@Get(':courseId/data')
|
||||
@ApiOperation({ summary: 'Get certificate data for display/print page' })
|
||||
async getCertificateData(@Param('courseId') courseId: string, @CurrentUser() user: User) {
|
||||
return this.certificatesService.getCertificateData(user.id, courseId);
|
||||
}
|
||||
|
||||
@Get(':courseId')
|
||||
@ApiOperation({ summary: 'Generate certificate for completed course' })
|
||||
async getCertificate(@Param('courseId') courseId: string, @CurrentUser() user: User): Promise<any> {
|
||||
|
||||
@ -5,7 +5,11 @@ import { PrismaService } from '../common/prisma/prisma.service';
|
||||
export class CertificatesService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async generateCertificate(userId: string, courseId: string): Promise<any> {
|
||||
async getCertificateData(userId: string, courseId: string): Promise<{
|
||||
userName: string;
|
||||
courseTitle: string;
|
||||
completedAt: string;
|
||||
}> {
|
||||
const enrollment = await this.prisma.enrollment.findUnique({
|
||||
where: { userId_courseId: { userId, courseId } },
|
||||
include: { course: true, user: true },
|
||||
@ -14,19 +18,27 @@ export class CertificatesService {
|
||||
if (!enrollment) throw new NotFoundException('Not enrolled');
|
||||
if (!enrollment.completedAt) throw new Error('Course not completed yet');
|
||||
|
||||
// Generate certificate HTML (in production, render to PDF using puppeteer or similar)
|
||||
return {
|
||||
userName: enrollment.user.name || enrollment.user.email || 'Слушатель',
|
||||
courseTitle: enrollment.course.title,
|
||||
completedAt: new Date(enrollment.completedAt).toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
async generateCertificate(userId: string, courseId: string): Promise<any> {
|
||||
const data = await this.getCertificateData(userId, courseId);
|
||||
const completionDate = new Date(data.completedAt);
|
||||
|
||||
const certificateHtml = this.renderCertificateHTML(
|
||||
enrollment.user.name || enrollment.user.email,
|
||||
enrollment.course.title,
|
||||
new Date(enrollment.completedAt)
|
||||
data.userName,
|
||||
data.courseTitle,
|
||||
completionDate
|
||||
);
|
||||
|
||||
// In production: save to S3/R2 and return URL
|
||||
// For now, return inline HTML
|
||||
const certificateUrl = `data:text/html;base64,${Buffer.from(certificateHtml).toString('base64')}`;
|
||||
|
||||
await this.prisma.enrollment.update({
|
||||
where: { id: enrollment.id },
|
||||
where: { userId_courseId: { userId, courseId } },
|
||||
data: { certificateUrl },
|
||||
});
|
||||
|
||||
@ -48,7 +60,7 @@ export class CertificatesService {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-center;
|
||||
justify-content: center;
|
||||
}
|
||||
.certificate {
|
||||
background: white;
|
||||
|
||||
Reference in New Issue
Block a user