fix: References section link corruption and download filename issues

**Issue 1: URL corruption in "See this CV in..." links**
- Bug: replaceYearPlaceholder used fmt.Sprintf on ALL URLs
- URLs like "/?lang=es" were corrupted to "/?lang=es%!(EXTRA string=2025)"
- Fix: Changed to strings.ReplaceAll("{{YEAR}}", year)
- Result: Only replaces actual {{YEAR}} placeholders, leaves other URLs intact

**Issue 2: Download filename not respected**
- Bug: Shortcut URLs (cv-jamr-2025-en.pdf) redirected with HTTP 301
- Browsers used original URL filename instead of Content-Disposition header
- Fix: Generate PDF directly in DefaultCVShortcut handler
- Result: Returns PDF with correct filename in Content-Disposition header

Files changed:
- internal/models/cv.go: Fixed replaceYearPlaceholder function
- internal/handlers/cv.go: Changed redirect to direct PDF generation

Both fixes verified:
- "See this CV in Spanish" link: href="/?lang=es" ✓
- Download link: filename=cv-jamr-2025-en.pdf ✓
This commit is contained in:
juanatsap
2025-11-20 13:00:06 +00:00
parent c88879b180
commit 810ee7955b
8 changed files with 407 additions and 9 deletions
+38 -5
View File
@@ -260,13 +260,46 @@ func (h *CVHandler) DefaultCVShortcut(w http.ResponseWriter, r *http.Request) {
return
}
// Build redirect URL with default parameters (short + with_skills)
redirectURL := fmt.Sprintf("/export/pdf?lang=%s&length=short&icons=show&version=with_skills", lang)
// Generate PDF directly instead of redirecting
// This ensures the Content-Disposition filename is respected by browsers
log.Printf("Shortcut URL: %s → generating PDF (short + with_skills)", path)
log.Printf("Shortcut URL: %s → %s", path, redirectURL)
// Prepare cookies for PDF generation (short, with_skills, light mode)
cookies := map[string]string{
"cv-length": "short",
"cv-icons": "show",
"cv-language": lang,
"cv-theme": "default", // with_skills = default theme
"color-theme": "light", // Always light for PDFs
}
// Redirect to PDF export endpoint
http.Redirect(w, r, redirectURL, http.StatusMovedPermanently)
// Construct URL for PDF generation
targetURL := fmt.Sprintf("http://%s/?lang=%s", h.serverAddr, lang)
// Generate PDF with screen render mode (for sidebar layout)
ctx := r.Context()
pdfData, err := h.pdfGenerator.GenerateFromURLWithOptions(ctx, targetURL, cookies, pdf.RenderModeScreen)
if err != nil {
log.Printf("PDF generation failed for shortcut URL: %v", err)
HandleError(w, r, InternalError(err))
return
}
// Use the shortcut filename directly (simple, user-friendly)
filename := filepath.Base(path) // cv-jamr-2025-en.pdf
// Set response headers with shortcut filename
w.Header().Set("Content-Type", "application/pdf")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
w.Header().Set("Content-Length", 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))
}
// ExportPDF handles PDF export requests using chromedp
+2 -1
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"html/template"
"os"
"strings"
"time"
)
@@ -241,7 +242,7 @@ func LoadCV(lang string) (*CV, error) {
// replaceYearPlaceholder replaces {{YEAR}} with the current year
func replaceYearPlaceholder(url string, year string) string {
return fmt.Sprintf(url, year)
return strings.ReplaceAll(url, "{{YEAR}}", year)
}
// LoadUI loads UI translations from a JSON file for the specified language