rss email digests over ssh because you're a cool kid herald.dunkirk.sh
go rss rss-reader ssh charm

fix: address golangci-lint errors

dunkirk.sh cf8e46b9 5df1b3b8

verified
+27 -22
+7 -7
scheduler/scheduler.go
··· 20 20 emailsPerSecondPerUser = emailsPerMinutePerUser / 60.0 21 21 22 22 // Cleanup intervals 23 - cleanupInterval = 24 * time.Hour 24 - seenItemsRetention = 6 * 30 * 24 * time.Hour // 6 months 25 - itemMaxAge = 3 * 30 * 24 * time.Hour // 3 months 26 - emailSendsRetention = 6 * 30 // 6 months in days 23 + cleanupInterval = 24 * time.Hour 24 + seenItemsRetention = 6 * 30 * 24 * time.Hour // 6 months 25 + itemMaxAge = 3 * 30 * 24 * time.Hour // 3 months 26 + emailSendsRetention = 6 * 30 // 6 months in days 27 27 28 28 // Item limits 29 29 minItemsForDigest = 5 30 30 31 31 // Engagement tracking 32 - inactivityThreshold = 90 // days without opens 32 + inactivityThreshold = 90 // days without opens 33 33 minSendsBeforeDeactivate = 3 // minimum sends before considering deactivation 34 34 ) 35 35 ··· 425 425 s.logger.Warn("failed to record email send", "err", err) 426 426 } 427 427 s.logger.Debug("sendDigestAndMarkSeen: recorded email send") 428 - 428 + 429 429 // Build keep-alive URL 430 430 keepAliveURL := "" 431 431 if trackingToken != "" { 432 432 keepAliveURL = s.originURL + "/keep-alive/" + trackingToken 433 433 } 434 - 434 + 435 435 // Send email - if this fails, transaction will rollback 436 436 s.logger.Debug("sendDigestAndMarkSeen: calling mailer.Send", "to", cfg.Email) 437 437 if err := s.mailer.Send(cfg.Email, subject, htmlBody, textBody, unsubToken, dashboardURL, keepAliveURL); err != nil {
+11 -12
store/tracking.go
··· 9 9 ) 10 10 11 11 type EmailSend struct { 12 - ID int64 13 - ConfigID int64 14 - Recipient string 15 - Subject string 16 - TrackingToken string 17 - SentAt time.Time 18 - Bounced bool 19 - BounceReason sql.NullString 20 - Opened bool 21 - OpenedAt sql.NullTime 12 + ID int64 13 + ConfigID int64 14 + Recipient string 15 + Subject string 16 + TrackingToken string 17 + SentAt time.Time 18 + Bounced bool 19 + BounceReason sql.NullString 20 + Opened bool 21 + OpenedAt sql.NullTime 22 22 } 23 23 24 24 // RecordEmailSend records an email send with optional tracking token ··· 111 111 if err != nil { 112 112 return nil, fmt.Errorf("query inactive configs: %w", err) 113 113 } 114 - defer rows.Close() 114 + defer func() { _ = rows.Close() }() 115 115 116 116 var configIDs []int64 117 117 for rows.Next() { ··· 220 220 221 221 return nil 222 222 } 223 -
+5 -1
store/tracking_test.go
··· 8 8 9 9 func TestEmailTracking(t *testing.T) { 10 10 db := setupTestDB(t) 11 - defer db.Close() 11 + defer func() { 12 + if err := db.Close(); err != nil { 13 + t.Errorf("failed to close db: %v", err) 14 + } 15 + }() 12 16 13 17 ctx := context.Background() 14 18
+4 -2
web/handlers.go
··· 704 704 // Return success message 705 705 w.Header().Set("Content-Type", "text/html; charset=utf-8") 706 706 w.WriteHeader(http.StatusOK) 707 - fmt.Fprintf(w, `<!DOCTYPE html> 707 + if _, err := fmt.Fprintf(w, `<!DOCTYPE html> 708 708 <html> 709 709 <head> 710 710 <meta charset="utf-8"> ··· 720 720 <div class="success">✓ Success!</div> 721 721 <div class="details">Your digest will stay active until <strong>%s</strong>.</div> 722 722 </body> 723 - </html>`, expiresAt) 723 + </html>`, expiresAt); err != nil { 724 + s.logger.Error("failed to write response", "error", err) 725 + } 724 726 } 725 727 726 728 func (s *Server) handle404(w http.ResponseWriter, r *http.Request) {