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
185 lines
5.4 KiB
Go
185 lines
5.4 KiB
Go
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))
|
|
}
|