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
+36
View File
@@ -12,6 +12,7 @@ type UI struct {
PdfModal PdfModal `json:"pdfModal"`
ShortcutsModal ShortcutsModal `json:"shortcutsModal"`
InfoModal InfoModal `json:"infoModal"`
ContactModal ContactModal `json:"contactModal"`
Widgets Widgets `json:"widgets"`
}
@@ -142,6 +143,38 @@ type TechStack struct {
CSS3 string `json:"css3"`
}
// ContactModal labels
type ContactModal struct {
Title string `json:"title"`
Subtitle string `json:"subtitle"`
Description string `json:"description"`
Close string `json:"close"`
Form ContactFormLabel `json:"form"`
Success ContactResult `json:"success"`
Error ContactResult `json:"error"`
}
type ContactFormLabel struct {
Email string `json:"email"`
EmailPlaceholder string `json:"emailPlaceholder"`
Name string `json:"name"`
NamePlaceholder string `json:"namePlaceholder"`
Company string `json:"company"`
CompanyPlaceholder string `json:"companyPlaceholder"`
Subject string `json:"subject"`
SubjectPlaceholder string `json:"subjectPlaceholder"`
Message string `json:"message"`
MessagePlaceholder string `json:"messagePlaceholder"`
Submit string `json:"submit"`
Sending string `json:"sending"`
Note string `json:"note"`
}
type ContactResult struct {
Title string `json:"title"`
Message string `json:"message,omitempty"`
}
// Widget label types
type Widgets struct {
BackToTop WidgetLabel `json:"backToTop"`
@@ -152,6 +185,7 @@ type Widgets struct {
ZoomToggle WidgetLabel `json:"zoomToggle"`
ZoomControl ZoomControlLabel `json:"zoomControl"`
PdfToast PdfToastLabel `json:"pdfToast"`
Contact WidgetLabel `json:"contact"`
ActionButtons ActionButtonsLabel `json:"actionButtons"`
}
@@ -177,4 +211,6 @@ type PdfToastLabel struct {
type ActionButtonsLabel struct {
DownloadPdf string `json:"downloadPdf"`
PrintFriendly string `json:"printFriendly"`
PlainText string `json:"plainText"`
Contact string `json:"contact"`
}