diff --git a/data/cv-en.json b/data/cv-en.json index b81f988..31ba804 100644 --- a/data/cv-en.json +++ b/data/cv-en.json @@ -736,6 +736,23 @@ ], "courseID": "codecademy-certifications" }, + { + "title": "Udemy Certifications", + "institution": "Udemy", + "courseLogo": "udemy.png", + "location": "Online", + "date": "2024-2025", + "duration": "Various", + "shortDescription": "Professional development courses in Go programming and modern web technologies through Udemy's comprehensive learning platform.", + "responsibilities": [ + "
Go - The Complete Guide 2024: Comprehensive Go programming course covering fundamentals, concurrency, testing, and building production-ready applications
", + "
Building a Module in Go 2024: Deep dive into Go modules, dependency management, versioning, and creating reusable packages
", + "
Up and Running with Concurrency in Go 2024: Advanced Go concurrency patterns including goroutines, channels, mutexes, and building concurrent applications
", + "
Building GUI Applications with Fyne and Go 2024: Desktop application development using the Fyne toolkit, creating cross-platform GUI applications with Go
", + "
HTMX - The Practical Guide 2024: Modern web development with HTMX, building dynamic web applications with minimal JavaScript using hypermedia patterns
" + ], + "courseID": "udemy-certifications" + }, { "title": "LinkedIn Learning Certifications", "institution": "LinkedIn Learning", diff --git a/data/cv-es.json b/data/cv-es.json index 6555c1a..00d6e70 100644 --- a/data/cv-es.json +++ b/data/cv-es.json @@ -741,6 +741,23 @@ ], "courseID": "certificaciones-codecademy" }, + { + "title": "Certificaciones Udemy", + "institution": "Udemy", + "courseLogo": "udemy.png", + "location": "Online", + "date": "2024-2025", + "duration": "Varios", + "shortDescription": "Cursos de desarrollo profesional en programación Go y tecnologías web modernas a través de la plataforma de aprendizaje integral de Udemy.", + "responsibilities": [ + "
Go - The Complete Guide 2024: Curso completo de programación Go cubriendo fundamentos, concurrencia, testing y construcción de aplicaciones listas para producción
", + "
Building a Module in Go 2024: Profundización en módulos Go, gestión de dependencias, versionado y creación de paquetes reutilizables
", + "
Up and Running with Concurrency in Go 2024: Patrones avanzados de concurrencia en Go incluyendo goroutines, channels, mutexes y construcción de aplicaciones concurrentes
", + "
Building GUI Applications with Fyne and Go 2024: Desarrollo de aplicaciones de escritorio usando el toolkit Fyne, creando aplicaciones GUI multiplataforma con Go
", + "
HTMX - The Practical Guide 2024: Desarrollo web moderno con HTMX, construyendo aplicaciones web dinámicas con JavaScript mínimo usando patrones hypermedia
" + ], + "courseID": "certificaciones-udemy" + }, { "title": "Certificaciones LinkedIn Learning", "institution": "LinkedIn Learning", diff --git a/data/ui-en.json b/data/ui-en.json index d8e4c02..230dd71 100644 --- a/data/ui-en.json +++ b/data/ui-en.json @@ -36,7 +36,12 @@ }, "footer": { "viewOnGithub": "View this project on GitHub", - "lastUpdated": "Last updated" + "lastUpdated": "Last updated", + "linkedin": "linkedin_", + "github": "github_", + "domestika": "domestika_", + "email": "email@", + "phone": "phone#" }, "portfolio": { "seeAllProjects": "See all projects on my", diff --git a/data/ui-es.json b/data/ui-es.json index 17fda97..8625a3e 100644 --- a/data/ui-es.json +++ b/data/ui-es.json @@ -36,7 +36,12 @@ }, "footer": { "viewOnGithub": "Ver este proyecto en GitHub", - "lastUpdated": "Última actualización" + "lastUpdated": "Última actualización", + "linkedin": "linkedin_", + "github": "github_", + "domestika": "domestika_", + "email": "email@", + "phone": "teléfono#" }, "portfolio": { "seeAllProjects": "Ver todos los proyectos en mi", diff --git a/internal/handlers/cv_text.go b/internal/handlers/cv_text.go index 9cb46f5..f38b0bf 100644 --- a/internal/handlers/cv_text.go +++ b/internal/handlers/cv_text.go @@ -44,11 +44,7 @@ func isTextBrowser(r *http.Request) bool { // Check Accept header - if client prefers text/plain accept := r.Header.Get("Accept") - if strings.HasPrefix(accept, "text/plain") { - return true - } - - return false + return strings.HasPrefix(accept, "text/plain") } // ============================================================================== @@ -72,10 +68,7 @@ func (h *CVHandler) PlainText(w http.ResponseWriter, r *http.Request) { } // Check icons parameter (default: true) - showIcons := true - if r.URL.Query().Get("icons") == "false" { - showIcons = false - } + showIcons := r.URL.Query().Get("icons") != "false" // Prepare template data using shared helper (loads CV data) data, err := h.prepareTemplateData(langCode) diff --git a/internal/middleware/security_logger.go b/internal/middleware/security_logger.go index ccab2fd..67b6053 100644 --- a/internal/middleware/security_logger.go +++ b/internal/middleware/security_logger.go @@ -132,7 +132,11 @@ func logToSecurityFile(eventJSON []byte) { log.Printf("WARNING: Failed to open security log file: %v", err) return } - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + log.Printf("WARNING: Failed to close security log file: %v", err) + } + }() // Write event with newline if _, err := f.Write(eventJSON); err != nil { diff --git a/internal/models/ui/ui.go b/internal/models/ui/ui.go index 223e09e..0b646b4 100644 --- a/internal/models/ui/ui.go +++ b/internal/models/ui/ui.go @@ -102,6 +102,11 @@ type Sections struct { type Footer struct { ViewOnGithub string `json:"viewOnGithub"` LastUpdated string `json:"lastUpdated"` + Linkedin string `json:"linkedin"` + Github string `json:"github"` + Domestika string `json:"domestika"` + Email string `json:"email"` + Phone string `json:"phone"` } // Portfolio labels diff --git a/internal/services/email.go b/internal/services/email.go index 9397b4a..f8d3a0e 100644 --- a/internal/services/email.go +++ b/internal/services/email.go @@ -224,74 +224,7 @@ func (e *EmailService) sendMultipartEmail(subject, htmlBody, textBody, replyTo s if err != nil { return fmt.Errorf("failed to connect to SMTP server: %w", err) } - defer client.Close() - - // Authenticate - if err = client.Auth(auth); err != nil { - return fmt.Errorf("SMTP authentication failed: %w", err) - } - - // Set sender and recipient - if err = client.Mail(from); err != nil { - return fmt.Errorf("failed to set sender: %w", err) - } - if err = client.Rcpt(to); err != nil { - return fmt.Errorf("failed to set recipient: %w", err) - } - - // Send message - w, err := client.Data() - if err != nil { - return fmt.Errorf("failed to get data writer: %w", err) - } - - _, err = w.Write([]byte(message)) - if err != nil { - return fmt.Errorf("failed to write message: %w", err) - } - - err = w.Close() - if err != nil { - return fmt.Errorf("failed to close writer: %w", err) - } - - return client.Quit() -} - -// sendEmail sends an email using SMTP (plain text only - legacy) -func (e *EmailService) sendEmail(subject, body string) error { - // Validate config - if e.config.SMTPHost == "" || e.config.SMTPPort == "" { - return fmt.Errorf("SMTP configuration incomplete") - } - if e.config.SMTPUser == "" || e.config.SMTPPassword == "" { - return fmt.Errorf("SMTP credentials missing") - } - if e.config.ToEmail == "" { - return fmt.Errorf("recipient email not configured") - } - - // Build email message - from := e.config.FromEmail - if from == "" { - from = e.config.SMTPUser - } - - to := e.config.ToEmail - message := e.formatMessage(from, to, subject, body) - - // SMTP server address - addr := fmt.Sprintf("%s:%s", e.config.SMTPHost, e.config.SMTPPort) - - // Setup authentication - auth := smtp.PlainAuth("", e.config.SMTPUser, e.config.SMTPPassword, e.config.SMTPHost) - - // Connect to SMTP server with TLS - client, err := e.connectSMTP(addr) - if err != nil { - return fmt.Errorf("failed to connect to SMTP server: %w", err) - } - defer client.Close() + defer func() { _ = client.Close() }() // Authenticate if err = client.Auth(auth); err != nil { @@ -342,7 +275,7 @@ func (e *EmailService) connectSMTP(addr string) (*smtp.Client, error) { } client, err := smtp.NewClient(conn, e.config.SMTPHost) if err != nil { - conn.Close() + _ = conn.Close() return nil, fmt.Errorf("SMTP client creation failed: %w", err) } return client, nil @@ -411,23 +344,3 @@ func (e *EmailService) formatMultipartMessage(from, to, replyTo, subject, htmlBo return message.String() } - -// formatMessage formats an email message with proper headers (plain text only) -func (e *EmailService) formatMessage(from, to, subject, body string) string { - headers := make(map[string]string) - headers["From"] = from - headers["To"] = to - headers["Subject"] = subject - headers["MIME-Version"] = "1.0" - headers["Content-Type"] = "text/plain; charset=\"utf-8\"" - headers["Date"] = time.Now().Format(time.RFC1123Z) - - var message strings.Builder - for k, v := range headers { - message.WriteString(fmt.Sprintf("%s: %s\r\n", k, v)) - } - message.WriteString("\r\n") - message.WriteString(body) - - return message.String() -} diff --git a/static/pdf/udemy/Building GUI Applications with Fyne and Go.pdf b/static/pdf/udemy/Building GUI Applications with Fyne and Go.pdf new file mode 100644 index 0000000..c5eed05 Binary files /dev/null and b/static/pdf/udemy/Building GUI Applications with Fyne and Go.pdf differ diff --git a/static/pdf/udemy/Building a module in Go.pdf b/static/pdf/udemy/Building a module in Go.pdf new file mode 100644 index 0000000..f20d94b Binary files /dev/null and b/static/pdf/udemy/Building a module in Go.pdf differ diff --git a/static/pdf/udemy/Go - The Complete Guide.pdf b/static/pdf/udemy/Go - The Complete Guide.pdf new file mode 100644 index 0000000..1dc41ee Binary files /dev/null and b/static/pdf/udemy/Go - The Complete Guide.pdf differ diff --git a/static/pdf/udemy/HTMX - The Practical Guide.pdf b/static/pdf/udemy/HTMX - The Practical Guide.pdf new file mode 100644 index 0000000..83e199d Binary files /dev/null and b/static/pdf/udemy/HTMX - The Practical Guide.pdf differ diff --git a/static/pdf/udemy/Up and Running with Concurrency in Go.pdf b/static/pdf/udemy/Up and Running with Concurrency in Go.pdf new file mode 100644 index 0000000..5549aca Binary files /dev/null and b/static/pdf/udemy/Up and Running with Concurrency in Go.pdf differ diff --git a/templates/partials/cv/cv-footer.html b/templates/partials/cv/cv-footer.html index e63d4c4..50646ae 100644 --- a/templates/partials/cv/cv-footer.html +++ b/templates/partials/cv/cv-footer.html @@ -5,38 +5,38 @@
diff --git a/tests/integration/email_test.go b/tests/integration/email_test.go index 7dca28b..2d424c7 100644 --- a/tests/integration/email_test.go +++ b/tests/integration/email_test.go @@ -39,7 +39,7 @@ func TestSMTPConnection(t *testing.T) { if err != nil { t.Fatalf("TLS dial failed: %v", err) } - defer conn.Close() + defer func() { _ = conn.Close() }() t.Log("TLS connection established (port 465 - implicit SSL)") } else { // STARTTLS @@ -47,7 +47,7 @@ func TestSMTPConnection(t *testing.T) { if err != nil { t.Fatalf("SMTP dial failed: %v", err) } - defer client.Close() + defer func() { _ = client.Close() }() if err := client.StartTLS(tlsConfig); err != nil { t.Fatalf("STARTTLS failed: %v", err) @@ -71,7 +71,7 @@ func TestSMTPConnection(t *testing.T) { if err != nil { t.Fatalf("TLS dial failed: %v", err) } - defer conn.Close() + defer func() { _ = conn.Close() }() client, err = smtp.NewClient(conn, host) if err != nil { @@ -88,7 +88,7 @@ func TestSMTPConnection(t *testing.T) { t.Fatalf("STARTTLS failed: %v", err) } } - defer client.Close() + defer func() { _ = client.Close() }() auth := smtp.PlainAuth("", user, pass, host) if err := client.Auth(auth); err != nil {