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
+35 -1
View File
@@ -159,6 +159,34 @@
"viewSource": "View Project in Github",
"viewSourceSubtext": "Want to know how it's built?"
},
"contactModal": {
"title": "Get in Touch",
"subtitle": "Let's connect!",
"description": "Have a question or interested in working together? Fill out the form below and I'll get back to you as soon as possible.",
"close": "Close",
"form": {
"email": "Email",
"emailPlaceholder": "your.email@example.com",
"name": "Name",
"namePlaceholder": "Your name",
"company": "Company",
"companyPlaceholder": "Company",
"subject": "Subject",
"subjectPlaceholder": "Subject",
"message": "Message",
"messagePlaceholder": "Your message...",
"submit": "Send Message",
"sending": "Sending...",
"note": "* Required fields"
},
"success": {
"title": "Message Sent!",
"message": "Thank you for your message. I'll get back to you soon."
},
"error": {
"title": "Error"
}
},
"widgets": {
"backToTop": {
"ariaLabel": "Back to top",
@@ -196,9 +224,15 @@
"title": "Preparing PDF",
"closeLabel": "Close notification"
},
"contact": {
"ariaLabel": "Contact me",
"tooltip": "Contact me"
},
"actionButtons": {
"downloadPdf": "Download as PDF",
"printFriendly": "Print Friendly"
"printFriendly": "Print Friendly",
"plainText": "Plain Text",
"contact": "Contact"
}
}
}
+35 -1
View File
@@ -159,6 +159,34 @@
"viewSource": "Ver proyecto en Github",
"viewSourceSubtext": "¿Quieres saber cómo está hecho?"
},
"contactModal": {
"title": "Ponerse en contacto",
"subtitle": "¡Conectemos!",
"description": "¿Tienes alguna pregunta o estás interesado en trabajar juntos? Rellena el formulario a continuación y me pondré en contacto contigo lo antes posible.",
"close": "Cerrar",
"form": {
"email": "Correo electrónico",
"emailPlaceholder": "tu.email@ejemplo.com",
"name": "Nombre",
"namePlaceholder": "Tu nombre",
"company": "Empresa",
"companyPlaceholder": "Empresa",
"subject": "Asunto",
"subjectPlaceholder": "Asunto",
"message": "Mensaje",
"messagePlaceholder": "Tu mensaje...",
"submit": "Enviar mensaje",
"sending": "Enviando...",
"note": "* Campos obligatorios"
},
"success": {
"title": "¡Mensaje enviado!",
"message": "Gracias por tu mensaje. Me pondré en contacto contigo pronto."
},
"error": {
"title": "Error"
}
},
"widgets": {
"backToTop": {
"ariaLabel": "Volver arriba",
@@ -196,9 +224,15 @@
"title": "Preparando PDF",
"closeLabel": "Cerrar notificación"
},
"contact": {
"ariaLabel": "Contáctame",
"tooltip": "Contáctame"
},
"actionButtons": {
"downloadPdf": "Descargar como PDF",
"printFriendly": "Imprimir amigable"
"printFriendly": "Imprimir amigable",
"plainText": "Texto Plano",
"contact": "Contacto"
}
}
}