diff --git a/README.md b/README.md index c05aa57..b4e1edf 100644 --- a/README.md +++ b/README.md @@ -64,3 +64,4 @@ export EMAIL_FROM=test@example.org export EMAIL_USER=”” export EMAIL_PASSWORD=”” ``` +You can access the MailHog UI at `localhost:8025` \ No newline at end of file diff --git a/cmd/root.go b/cmd/root.go index 05d34dd..8d27735 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -2,9 +2,8 @@ package cmd import ( "encoding/json" - "errors" + "fmt" "html/template" - "log" "math/rand" "os" "path/filepath" @@ -12,10 +11,12 @@ import ( "strings" "github.com/spf13/cobra" + + "github.com/pkg/errors" "gopkg.in/gomail.v2" ) -var version string = "0.0.1" +var version string = "0.1.0" type Data struct { Name string `json:"name"` @@ -66,13 +67,13 @@ func sendEmail(to, subject, body string) error { port, err := strconv.Atoi(strPort) if err != nil { - return err + return errors.Wrap(err, "error parsing email server port") } d := gomail.NewDialer(host, port, user, password) s, err := d.Dial() if err != nil { - return err + return errors.Wrap(err, "error dialing email server") } m := gomail.NewMessage() @@ -84,36 +85,44 @@ func sendEmail(to, subject, body string) error { return gomail.Send(s, m) } -func sendEmails(matches []MatchPair) { +func sendEmails(matches []MatchPair) ([]string, error) { + // TODO: add retry logic for failed emails (maybe use a queue) + // instead of sending each email individually, + // generate all the emails and send them in bulk to avoid individual errors + // or prompt the user to retry + emailIssues := make([]string, 0, len(matches)) subject := "Your Secret Santa Match!" templateFile := "./templates/email_template.html" tmpl, err := template.ParseFiles(templateFile) if err != nil { - log.Fatalf("Error parsing email template: %v", err) + return emailIssues, errors.Wrap(err, "failed to parse email template") } for _, match := range matches { var emailBodyBuffer = &strings.Builder{} err := tmpl.Execute(emailBodyBuffer, match) if err != nil { - log.Printf("Error executing template for %s: %v", match.From.Email, err) + emailIssues = append(emailIssues, fmt.Sprintf("failed to execute template for %s: %v", match.From.Email, err)) continue + } emailBody := emailBodyBuffer.String() if err := sendEmail(match.From.Email, subject, emailBody); err != nil { - log.Printf("Error sending email to %s: %v", match.From.Email, err) + emailIssues = append(emailIssues, fmt.Sprintf("failed to send email to %s: %v", match.From.Email, err)) } } + return emailIssues, nil } var rootCmd = &cobra.Command{ - Use: "run [path]", - Short: "A cli tool that generates secret santa matches and notifies the participants by email", - ArgAliases: []string{"path"}, - Version: version, - Run: func(cmd *cobra.Command, args []string) { + Use: "run [path]", + Short: "A cli tool that generates secret santa matches and notifies the participants by email", + ArgAliases: []string{"path"}, + Version: version, + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { var filePath string if len(args) == 0 { filePath = "data.json" @@ -125,31 +134,42 @@ var rootCmd = &cobra.Command{ } if !checkFileExists(filePath) { - log.Fatal("File ", filePath, " does not exist") + return fmt.Errorf("file %s does not exist", filePath) + } if !checkIsJson(filePath) { - log.Fatal("File ", filePath, " is not a json file") + return fmt.Errorf("file %s is not a json file", filePath) } file, err := os.ReadFile(filePath) if err != nil { - log.Fatal("Error when opening file: ", err) + return errors.Wrap(err, "error when opening file") } var payload []Data err = json.Unmarshal(file, &payload) if err != nil { - log.Fatal("Error reading file content: ", err) + return errors.Wrap(err, "error reading file content") } if len(payload) == 0 { - log.Fatal("File is empty") + return errors.New("file is empty") } matches := generateSecretSantaMatches(payload) + emailIssues, err := sendEmails(matches) + if err != nil { + return errors.Wrap(err, "error sending emails") + } + if len(emailIssues) > 0 { + for _, issue := range emailIssues { + fmt.Println(issue) + } + fmt.Println("Some emails failed to send, please check the logs for more details") + } - sendEmails(matches) + return nil }, } @@ -158,4 +178,5 @@ func Execute() { if err != nil { os.Exit(1) } + os.Exit(0) } diff --git a/cmd/root_test.go b/cmd/root_test.go index 0f6a1ba..cedd481 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -34,7 +34,7 @@ func TestGenerateSecretSantaMachesReturnsAppropriateLenghtOfMatchPairs(t *testin var payload []Data payload = append(payload, Data{Name: "A", Email: "testa@test.org"}) payload = append(payload, Data{Name: "B", Email: "testb@test.org"}) - payload = append(payload, Data{Name: "C", Email: "testb@test.org"}) + payload = append(payload, Data{Name: "C", Email: "testc@test.org"}) matches := generateSecretSantaMatches(payload) @@ -47,7 +47,7 @@ func TestCircularMatchingAlgorithmReturnsExpectedMatchPairs(t *testing.T) { var payload []Data payload = append(payload, Data{Name: "A", Email: "testa@test.org"}) payload = append(payload, Data{Name: "B", Email: "testb@test.org"}) - payload = append(payload, Data{Name: "C", Email: "testb@test.org"}) + payload = append(payload, Data{Name: "C", Email: "testc@test.org"}) var expected []MatchPair diff --git a/go.mod b/go.mod index 26d105f..1208adb 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,11 @@ go 1.21.4 require github.com/spf13/cobra v1.8.0 +require github.com/pkg/errors v0.9.1 // indirect + require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect - gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect + gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df ) diff --git a/go.sum b/go.sum index 55318e8..5e348d7 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=