Files
cv-site/internal/handlers/cv_pdf.go
T
juanatsap 30ed21ff7a refactor: use 'c' alias for constants package
- Update all imports from 'constants' to 'c' for brevity
- Replace all 'constants.' references with 'c.'
- Fix remaining hardcoded content-type headers in httputil
- Fix remaining hardcoded User-Agent and Accept headers
- Rename CSRF receiver from 'c' to 'csrf' to avoid conflict
- Add ContentTypePlainSimple constant for Accept header matching
- Fix JSONCached to use proper integer formatting
2025-12-06 16:31:42 +00:00

126 lines
4.1 KiB
Go

package handlers
import (
"fmt"
"log"
"net/http"
"strings"
"time"
c "github.com/juanatsap/cv-site/internal/constants"
cvmodel "github.com/juanatsap/cv-site/internal/models/cv"
"github.com/juanatsap/cv-site/internal/pdf"
)
// ==============================================================================
// PDF EXPORT HANDLER
// Handles PDF generation with customizable options (length, icons, version, theme)
// ==============================================================================
// ExportPDF handles PDF export requests using chromedp
func (h *CVHandler) ExportPDF(w http.ResponseWriter, r *http.Request) {
// Parse and validate request parameters
req, err := ParsePDFExportRequest(r)
if err != nil {
HandleError(w, r, BadRequestError(err.Error()))
return
}
log.Printf("PDF export requested: lang=%s, length=%s, icons=%s, version=%s",
req.Lang, req.Length, req.Icons, req.Version)
// Load CV data to get name for filename
cv, err := cvmodel.LoadCV(req.Lang)
if err != nil {
HandleError(w, r, DataLoadError(err, "CV"))
return
}
// Prepare cookies to set preferences
cookies := map[string]string{
c.CookieCVLength: req.Length,
c.CookieCVIcons: req.Icons,
c.CookieCVLanguage: req.Lang,
}
// Set theme cookie based on version parameter
if req.Version == c.CVThemeClean {
cookies[c.CookieCVTheme] = c.CVThemeClean
} else {
cookies[c.CookieCVTheme] = c.CVThemeDefault
}
// CRITICAL: ALWAYS force light mode for PDF generation (print-friendly)
// This ensures PDFs are NEVER generated in dark mode, regardless of user's preference
cookies[c.CookieColorTheme] = c.ColorThemeLight
// Construct URL for PDF generation (navigate to home page)
targetURL := fmt.Sprintf("http://%s/?lang=%s", h.serverAddr, req.Lang)
// Determine render mode based on version parameter
// Clean version: use @media print CSS (print-friendly, no sidebars)
// Extended version: use @media screen CSS (full layout with sidebars)
var renderMode pdf.RenderMode
if req.Version == "clean" {
renderMode = pdf.RenderModePrint
} else {
renderMode = pdf.RenderModeScreen
}
// Generate PDF with cookies and appropriate render mode
ctx := r.Context()
pdfData, err := h.pdfGenerator.GenerateFromURLWithOptions(ctx, targetURL, cookies, renderMode)
if err != nil {
log.Printf("PDF generation failed: %v", err)
HandleError(w, r, InternalError(err))
return
}
// Generate filename based on parameters
// Format: cv-{length}[-{version}]-{initials}-{year}-{lang}.pdf
// Note: {version} is OMITTED when it's "clean"
// Examples:
// - cv-short-jamr-2025-es.pdf (clean version, no skills)
// - cv-short-with_skills-jamr-2025-es.pdf (with skills sidebar)
// - cv-long-jamr-2025-en.pdf (clean version, no skills)
// - cv-long-with_skills-jamr-2025-en.pdf (with skills sidebar)
// Generate initials from name
nameParts := strings.Fields(cv.Personal.Name)
initials := ""
for _, part := range nameParts {
if len(part) > 0 {
// Take first letter of each name part
initials += string([]rune(part)[0])
}
}
initials = strings.ToLower(initials)
// Get current year
currentYear := time.Now().Year()
// Build filename: cv-{length}[-{version}]-{initials}-{year}-{lang}.pdf
// Omit version if it's "clean"
// Replace underscores with hyphens in version for filename (with_skills → with-skills)
var filename string
if req.Version == "clean" {
filename = fmt.Sprintf("cv-%s-%s-%d-%s.pdf", req.Length, initials, currentYear, req.Lang)
} else {
versionForFilename := strings.ReplaceAll(req.Version, "_", "-")
filename = fmt.Sprintf("cv-%s-%s-%s-%d-%s.pdf", req.Length, versionForFilename, initials, currentYear, req.Lang)
}
// Set response headers
w.Header().Set(c.HeaderContentType, c.ContentTypePDF)
w.Header().Set(c.HeaderContentDisposition, fmt.Sprintf("attachment; filename=%s", filename))
w.Header().Set(c.HeaderContentLength, fmt.Sprintf("%d", len(pdfData)))
// Write PDF data
if _, err := w.Write(pdfData); err != nil {
log.Printf("Error writing PDF response: %v", err)
return
}
log.Printf("PDF generated successfully: %s (%d bytes)", filename, len(pdfData))
}