f91a24ea9b
Plain text endpoint: - Add /text route for plain text CV (for curl/AI crawlers) - Use k3a/html2text library for HTML-to-text conversion - Add Plain Text button to hamburger menu with UI translations Contact form feature: - Add ContactHandler with proper email service integration - Add CSRF protection middleware - Add rate limiting (5 submissions/hour per IP) - Add honeypot and timing-based bot protection - Add input validation with detailed error messages - Add security logging middleware - Add browser-only middleware for API protection Code quality: - Fix all golangci-lint errcheck warnings for w.Write calls - Remove duplicate getClientIP functions - Wire up ContactHandler in routes.Setup
69 lines
2.0 KiB
Go
69 lines
2.0 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"log"
|
|
"net/http"
|
|
|
|
"github.com/k3a/html2text"
|
|
)
|
|
|
|
// ==============================================================================
|
|
// PLAIN TEXT HANDLER
|
|
// Converts HTML CV to readable plain text for terminal/AI consumption
|
|
// ==============================================================================
|
|
|
|
// PlainText renders the CV as plain text
|
|
// Useful for: curl users, AI crawlers, accessibility, copy-paste
|
|
func (h *CVHandler) PlainText(w http.ResponseWriter, r *http.Request) {
|
|
// Get language from query parameter, default to English
|
|
lang := r.URL.Query().Get("lang")
|
|
if lang == "" {
|
|
lang = "en"
|
|
}
|
|
|
|
// Validate language
|
|
if lang != "en" && lang != "es" {
|
|
http.Error(w, "Unsupported language. Use 'en' or 'es'", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Prepare template data using shared helper
|
|
data, err := h.prepareTemplateData(lang)
|
|
if err != nil {
|
|
log.Printf("PlainText: Failed to load CV data: %v", err)
|
|
http.Error(w, "Failed to load CV data", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Add preferences for full CV display (show everything)
|
|
data["CVLengthClass"] = "cv-long"
|
|
data["ShowIcons"] = false // Icons don't render in text
|
|
data["ThemeClean"] = false
|
|
|
|
// Render HTML template to buffer
|
|
tmpl, err := h.templates.Render("index.html")
|
|
if err != nil {
|
|
log.Printf("PlainText: Failed to load template: %v", err)
|
|
http.Error(w, "Failed to load template", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
var htmlBuffer bytes.Buffer
|
|
if err := tmpl.Execute(&htmlBuffer, data); err != nil {
|
|
log.Printf("PlainText: Failed to execute template: %v", err)
|
|
http.Error(w, "Failed to render template: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Convert HTML to plain text
|
|
text := html2text.HTML2Text(htmlBuffer.String())
|
|
|
|
// Set response headers
|
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
|
|
|
// Write plain text response
|
|
_, _ = w.Write([]byte(text))
|
|
}
|