feat(academy): add PDF certificate generation and download endpoint

Add gofpdf-based certificate PDF generation for the Compliance Academy.
Landscape A4 certificates with company branding, course details, and
verification URL. New route: GET /sdk/v1/academy/certificates/:id/pdf

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Boenisch
2026-02-14 22:05:31 +01:00
parent 9a7c3bf4d9
commit 85d2362724
5 changed files with 198 additions and 41 deletions

View File

@@ -585,3 +585,48 @@ func (h *AcademyHandlers) GetStatistics(c *gin.Context) {
c.JSON(http.StatusOK, stats)
}
// ============================================================================
// Certificate PDF Download
// ============================================================================
// DownloadCertificatePDF generates and downloads a certificate as PDF
// GET /sdk/v1/academy/certificates/:id/pdf
func (h *AcademyHandlers) DownloadCertificatePDF(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid certificate ID"})
return
}
cert, err := h.store.GetCertificate(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if cert == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "certificate not found"})
return
}
validUntil := time.Now().UTC().AddDate(1, 0, 0)
if cert.ValidUntil != nil {
validUntil = *cert.ValidUntil
}
pdfBytes, err := academy.GenerateCertificatePDF(academy.CertificateData{
CertificateID: cert.ID.String(),
UserName: cert.UserName,
CourseName: cert.CourseTitle,
IssuedAt: cert.IssuedAt,
ValidUntil: validUntil,
})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate PDF: " + err.Error()})
return
}
shortID := cert.ID.String()[:8]
c.Header("Content-Disposition", "attachment; filename=zertifikat-"+shortID+".pdf")
c.Data(http.StatusOK, "application/pdf", pdfBytes)
}