Files
cv-site/internal/handlers/cv_pages.go
T

185 lines
5.4 KiB
Go
Raw Normal View History

package handlers
import (
"fmt"
"log"
"net/http"
"path/filepath"
"strings"
"time"
c "github.com/juanatsap/cv-site/internal/constants"
"github.com/juanatsap/cv-site/internal/httputil"
"github.com/juanatsap/cv-site/internal/middleware"
"github.com/juanatsap/cv-site/internal/pdf"
)
// ==============================================================================
// PAGE HANDLERS
// These handlers render full HTML pages or page sections
// ==============================================================================
// Home renders the full CV page
func (h *CVHandler) Home(w http.ResponseWriter, r *http.Request) {
// Check if this is a shortcut URL request (cv-jamr-{year}-{lang}.pdf)
if strings.HasPrefix(r.URL.Path, "/cv-jamr-") && strings.HasSuffix(r.URL.Path, ".pdf") {
h.DefaultCVShortcut(w, r)
return
}
// Detect text-based browsers and serve plain text version
if isTextBrowser(r) {
h.PlainText(w, r)
return
}
lang, ok := httputil.LangOrError(r)
if !ok {
HandleError(w, r, BadRequestError("Unsupported language. Use 'en' or 'es'"))
return
}
// Prepare template data using shared helper
data, err := h.prepareTemplateData(lang)
if err != nil {
HandleError(w, r, DataLoadError(err, "CV"))
return
}
// Get user preferences from context (set by PreferencesMiddleware)
prefs := middleware.GetPreferences(r)
// Use preferences from context (already migrated by middleware)
cvLength := prefs.CVLength
cvIcons := prefs.CVIcons
cvTheme := prefs.CVTheme
// Add preference-specific fields to template data
cvLengthClass := "cv-short"
if cvLength == "long" {
cvLengthClass = "cv-long"
}
data["CVLengthClass"] = cvLengthClass
data["ShowIcons"] = (cvIcons == "show")
data["ThemeClean"] = (cvTheme == "clean")
// Render template
tmpl, err := h.templates.Render("index.html")
if err != nil {
HandleError(w, r, TemplateError(err, "index.html"))
return
}
w.Header().Set(c.HeaderContentType, c.ContentTypeHTML)
if err := tmpl.Execute(w, data); err != nil {
HandleError(w, r, TemplateError(err, "index.html"))
return
}
}
// CVContent renders just the CV content for HTMX swaps
func (h *CVHandler) CVContent(w http.ResponseWriter, r *http.Request) {
lang, ok := httputil.LangOrError(r)
if !ok {
HandleError(w, r, BadRequestError("Unsupported language. Use 'en' or 'es'"))
return
}
// Prepare template data using shared helper
data, err := h.prepareTemplateData(lang)
if err != nil {
HandleError(w, r, DataLoadError(err, "CV"))
return
}
// Render template
tmpl, err := h.templates.Render("cv-content.html")
if err != nil {
HandleError(w, r, TemplateError(err, "cv-content.html"))
return
}
w.Header().Set(c.HeaderContentType, c.ContentTypeHTML)
if err := tmpl.Execute(w, data); err != nil {
HandleError(w, r, TemplateError(err, "cv-content.html"))
return
}
}
// DefaultCVShortcut handles shortcut URLs for default CV downloads
// Pattern: /cv-jamr-{year}-{lang}.pdf (e.g., /cv-jamr-2025-en.pdf)
// Validates year matches current year and redirects to default PDF export
func (h *CVHandler) DefaultCVShortcut(w http.ResponseWriter, r *http.Request) {
// Extract path (e.g., "/cv-jamr-2025-en.pdf")
path := r.URL.Path
log.Printf("DefaultCVShortcut called with path: %s", path)
// Parse filename pattern: cv-jamr-{year}-{lang}.pdf
parts := strings.Split(strings.TrimPrefix(path, "/"), "-")
if len(parts) != 4 {
http.NotFound(w, r)
return
}
// Extract year and language
yearStr := parts[2]
langWithExt := parts[3] // "en.pdf" or "es.pdf"
lang := strings.TrimSuffix(langWithExt, ".pdf")
// Validate language
if !c.SupportedLanguages[lang] {
http.NotFound(w, r)
return
}
// Validate year matches current year
currentYear := fmt.Sprintf("%d", time.Now().Year())
if yearStr != currentYear {
// Return 404 if year doesn't match (old or future URLs)
log.Printf("Invalid year in shortcut URL: %s (current: %s)", yearStr, currentYear)
http.NotFound(w, r)
return
}
// 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)
// Prepare cookies for PDF generation (short, with_skills, light mode)
cookies := map[string]string{
c.CookieCVLength: c.CVLengthShort,
c.CookieCVIcons: c.CVIconsShow,
c.CookieCVLanguage: lang,
c.CookieCVTheme: c.CVThemeDefault, // with_skills = default theme
c.CookieColorTheme: c.ColorThemeLight, // Always light for PDFs
}
// 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(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))
}