feat: Add plain text CV endpoint and contact form with security

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
This commit is contained in:
juanatsap
2025-11-30 13:47:49 +00:00
parent ae430e6ea7
commit f91a24ea9b
26 changed files with 3213 additions and 5 deletions
+9 -1
View File
@@ -9,7 +9,7 @@ import (
)
// Setup configures all application routes and middleware
func Setup(cvHandler *handlers.CVHandler, healthHandler *handlers.HealthHandler) http.Handler {
func Setup(cvHandler *handlers.CVHandler, healthHandler *handlers.HealthHandler, contactHandler *handlers.ContactHandler) http.Handler {
mux := http.NewServeMux()
// Shortcut routes for default CV (year-aware) - MUST be before "/" route
@@ -19,6 +19,7 @@ func Setup(cvHandler *handlers.CVHandler, healthHandler *handlers.HealthHandler)
// Public routes
mux.HandleFunc("/", cvHandler.Home)
mux.HandleFunc("/cv", cvHandler.CVContent)
mux.HandleFunc("/text", cvHandler.PlainText) // Plain text version for curl/AI
mux.HandleFunc("/health", healthHandler.Check)
// HTMX endpoints for interactive controls
@@ -27,6 +28,13 @@ func Setup(cvHandler *handlers.CVHandler, healthHandler *handlers.HealthHandler)
mux.HandleFunc("/toggle/icons", cvHandler.ToggleIcons)
mux.HandleFunc("/toggle/theme", cvHandler.ToggleTheme)
// Contact form endpoint (simple rate limiting)
contactRateLimiter := middleware.NewRateLimiter(5, 1*time.Hour)
protectedContactHandler := contactRateLimiter.Middleware(
http.HandlerFunc(contactHandler.Submit),
)
mux.Handle("/api/contact", protectedContactHandler)
// Protected PDF endpoint with rate limiting (3 requests/minute per IP)
pdfRateLimiter := middleware.NewRateLimiter(3, 1*time.Minute)
protectedPDFHandler := middleware.OriginChecker(