30ed21ff7a
- 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
126 lines
4.1 KiB
Go
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))
|
|
}
|