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:
root
2026-02-06 11:32:58 +00:00
parent 5241144bc5
commit c809d049fe
8 changed files with 192 additions and 61 deletions

View File

@ -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> {

View File

@ -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;