feat: add Udemy courses and fix footer i18n + golangci-lint errors
- Add 5 Udemy courses with PDF certificate links (Go, Fyne, HTMX) - Fix cv-footer.html to use CV data instead of hardcoded values - Add i18n labels for footer (linkedin, github, domestika, email, phone) - Fix 11 golangci-lint errors: - errcheck: proper Close() error handling in email/security/tests - staticcheck: simplify return and merge variable declaration - unused: remove legacy sendEmail and formatMessage functions
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user