From 016b35bfa2ff0002bbc4f8f32aa6587a6985559e Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Fri, 9 Oct 2020 01:21:12 +0300 Subject: [PATCH 01/19] Add SendSync method Usefull to have when you need to be confident that message was sent. --- services/mailer/mailer.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/services/mailer/mailer.go b/services/mailer/mailer.go index 2e7beffa151a..312b2439bfce 100644 --- a/services/mailer/mailer.go +++ b/services/mailer/mailer.go @@ -335,6 +335,12 @@ func NewContext() { go graceful.GetManager().RunWithShutdownFns(mailQueue.Run) } +// SendSync uses default sender to send a message with blocking +func SendSync(msg *Message) error { + gomailMsg := msg.ToMessage() + return gomail.Send(Sender, gomailMsg) +} + // SendAsync send mail asynchronously func SendAsync(msg *Message) { go func() { From 538d0dbd440822d5b64bc19f0dd1d780a2bf5f2d Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Fri, 9 Oct 2020 01:22:39 +0300 Subject: [PATCH 02/19] Add sendmail command --- cmd/admin.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/cmd/admin.go b/cmd/admin.go index 9f81f5284dd6..69911d15d890 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -20,6 +20,7 @@ import ( pwd "code.gitea.io/gitea/modules/password" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/mailer" "github.com/urfave/cli" ) @@ -35,6 +36,7 @@ var ( subcmdRepoSyncReleases, subcmdRegenerate, subcmdAuth, + subcmdSendMail, }, } @@ -253,6 +255,24 @@ var ( Action: runAddOauth, Flags: oauthCLIFlags, } + + subcmdSendMail = cli.Command{ + Name: "sendmail", + Usage: "Send a message to all users", + Action: runSendMail, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "title", + Usage: `a title of a message`, + Value: "", + }, + cli.StringFlag{ + Name: "content", + Usage: "a content of a message", + Value: "", + }, + }, + } ) func runChangePassword(c *cli.Context) error { @@ -606,3 +626,43 @@ func runDeleteAuth(c *cli.Context) error { return models.DeleteSource(source) } + +func runSendMail(c *cli.Context) error { + if !c.IsSet("title") { + return errors.New("--title flag is missing") + } + + if !c.IsSet("content") { + return errors.New("--content flag is missing") + } + + subject := c.String("title") + body := c.String("content") + + if err := initDB(); err != nil { + return err + } + + users, _, err := models.SearchUsers(&models.SearchUserOptions{ + Type: models.UserTypeIndividual, + OrderBy: models.SearchOrderByAlphabetically, + ListOptions: models.ListOptions{}, + }) + if err != nil { + return errors.New("Cann't find users") + } + + var emails []string + for _, user := range users { + emails = append(emails, user.Email) + } + + mailer.NewContext() + msg := mailer.NewMessage(emails, subject, body) + err = mailer.SendSync(msg) + if err != nil { + return err + } + + return nil +} From 8ecbcc7d698d90a81043d918613a7537b89f358b Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Fri, 9 Oct 2020 11:54:23 +0300 Subject: [PATCH 03/19] add checks that if either title or content is empty then error out --- cmd/admin.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/cmd/admin.go b/cmd/admin.go index 69911d15d890..85cdc63c8523 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -628,12 +628,8 @@ func runDeleteAuth(c *cli.Context) error { } func runSendMail(c *cli.Context) error { - if !c.IsSet("title") { - return errors.New("--title flag is missing") - } - - if !c.IsSet("content") { - return errors.New("--content flag is missing") + if err := argsSet(c, "title", "content"); err != nil { + return err } subject := c.String("title") From 5c0524a044b1c60c2323b18ec0ed4fd4cdc6bac0 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Fri, 9 Oct 2020 12:21:41 +0300 Subject: [PATCH 04/19] Add a confirmation step --- cmd/admin.go | 9 +++++++++ cmd/cmd.go | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/cmd/admin.go b/cmd/admin.go index 85cdc63c8523..8a4a7f65d99a 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -653,6 +653,15 @@ func runSendMail(c *cli.Context) error { emails = append(emails, user.Email) } + fmt.Print("Proceed with sending email? [Y/n] ") + isConfirmed, err := confirm() + if err != nil { + return err + } else if !isConfirmed { + fmt.Println("The mail was not sent") + return nil + } + mailer.NewContext() msg := mailer.NewMessage(emails, subject, body) err = mailer.SendSync(msg) diff --git a/cmd/cmd.go b/cmd/cmd.go index d05eb8b1a2c3..bb768cc159d6 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -9,6 +9,7 @@ package cmd import ( "errors" "fmt" + "strings" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/setting" @@ -32,6 +33,25 @@ func argsSet(c *cli.Context, args ...string) error { return nil } +// confirm waits for user input which confirms an action +func confirm() (bool, error) { + var response string + + _, err := fmt.Scanln(&response) + if err != nil { + return false, err + } + + switch strings.ToLower(response) { + case "y", "yes": + return true, nil + case "n", "no": + return false, nil + default: + return false, errors.New(response + " isn't a correct confirmation string") + } +} + func initDB() error { return initDBDisableConsole(false) } From 2abad3e9e2fc0b0d93ecc348f920a15271f82b6b Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Fri, 9 Oct 2020 12:32:22 +0300 Subject: [PATCH 05/19] Add --force option to bypass confirm step --- cmd/admin.go | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/cmd/admin.go b/cmd/admin.go index 8a4a7f65d99a..51d62c91c843 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -271,6 +271,10 @@ var ( Usage: "a content of a message", Value: "", }, + cli.BoolFlag{ + Name: "force,yes,y", + Usage: "A flag to bypass a confirmation step", + }, }, } ) @@ -634,6 +638,7 @@ func runSendMail(c *cli.Context) error { subject := c.String("title") body := c.String("content") + confirmSkiped := c.Bool("force") if err := initDB(); err != nil { return err @@ -653,13 +658,15 @@ func runSendMail(c *cli.Context) error { emails = append(emails, user.Email) } - fmt.Print("Proceed with sending email? [Y/n] ") - isConfirmed, err := confirm() - if err != nil { - return err - } else if !isConfirmed { - fmt.Println("The mail was not sent") - return nil + if !confirmSkiped { + fmt.Print("Proceed with sending email? [Y/n] ") + isConfirmed, err := confirm() + if err != nil { + return err + } else if !isConfirmed { + fmt.Println("The mail was not sent") + return nil + } } mailer.NewContext() From 9c98e1307230c2c2768004d9aef6ed42cb7a4b52 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Fri, 9 Oct 2020 12:34:29 +0300 Subject: [PATCH 06/19] Move implementation of runSendMail to a different file --- cmd/admin.go | 49 ------------------------------------------- cmd/mailer.go | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 49 deletions(-) create mode 100644 cmd/mailer.go diff --git a/cmd/admin.go b/cmd/admin.go index 51d62c91c843..fb2e60343a57 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -20,7 +20,6 @@ import ( pwd "code.gitea.io/gitea/modules/password" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/services/mailer" "github.com/urfave/cli" ) @@ -630,51 +629,3 @@ func runDeleteAuth(c *cli.Context) error { return models.DeleteSource(source) } - -func runSendMail(c *cli.Context) error { - if err := argsSet(c, "title", "content"); err != nil { - return err - } - - subject := c.String("title") - body := c.String("content") - confirmSkiped := c.Bool("force") - - if err := initDB(); err != nil { - return err - } - - users, _, err := models.SearchUsers(&models.SearchUserOptions{ - Type: models.UserTypeIndividual, - OrderBy: models.SearchOrderByAlphabetically, - ListOptions: models.ListOptions{}, - }) - if err != nil { - return errors.New("Cann't find users") - } - - var emails []string - for _, user := range users { - emails = append(emails, user.Email) - } - - if !confirmSkiped { - fmt.Print("Proceed with sending email? [Y/n] ") - isConfirmed, err := confirm() - if err != nil { - return err - } else if !isConfirmed { - fmt.Println("The mail was not sent") - return nil - } - } - - mailer.NewContext() - msg := mailer.NewMessage(emails, subject, body) - err = mailer.SendSync(msg) - if err != nil { - return err - } - - return nil -} diff --git a/cmd/mailer.go b/cmd/mailer.go new file mode 100644 index 000000000000..74a7e6076fe2 --- /dev/null +++ b/cmd/mailer.go @@ -0,0 +1,58 @@ +package cmd + +import ( + "errors" + "fmt" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/services/mailer" + "github.com/urfave/cli" +) + +func runSendMail(c *cli.Context) error { + if err := argsSet(c, "title", "content"); err != nil { + return err + } + + subject := c.String("title") + body := c.String("content") + confirmSkiped := c.Bool("force") + + if err := initDB(); err != nil { + return err + } + + users, _, err := models.SearchUsers(&models.SearchUserOptions{ + Type: models.UserTypeIndividual, + OrderBy: models.SearchOrderByAlphabetically, + ListOptions: models.ListOptions{}, + }) + if err != nil { + return errors.New("Cann't find users") + } + + var emails []string + for _, user := range users { + emails = append(emails, user.Email) + } + + if !confirmSkiped { + fmt.Print("Proceed with sending email? [Y/n] ") + isConfirmed, err := confirm() + if err != nil { + return err + } else if !isConfirmed { + fmt.Println("The mail was not sent") + return nil + } + } + + mailer.NewContext() + msg := mailer.NewMessage(emails, subject, body) + err = mailer.SendSync(msg) + if err != nil { + return err + } + + return nil +} From a750b18741741e68356625002805b91ed417cfc5 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Fri, 9 Oct 2020 12:47:11 +0300 Subject: [PATCH 07/19] Add copyrighting comment --- cmd/mailer.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/mailer.go b/cmd/mailer.go index 74a7e6076fe2..11a3d1fcc8f8 100644 --- a/cmd/mailer.go +++ b/cmd/mailer.go @@ -1,3 +1,7 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + package cmd import ( From 3cb7d18ef8f903c1d9b377c74281a5f08c06bbff Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Mon, 12 Oct 2020 16:59:46 +0300 Subject: [PATCH 08/19] Make content optional Print waring if it's empty or haven't been set up. The warning will be skiped if there's a `--force` flag. --- cmd/mailer.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/cmd/mailer.go b/cmd/mailer.go index 11a3d1fcc8f8..5630a4273847 100644 --- a/cmd/mailer.go +++ b/cmd/mailer.go @@ -14,14 +14,10 @@ import ( ) func runSendMail(c *cli.Context) error { - if err := argsSet(c, "title", "content"); err != nil { + if err := argsSet(c, "title"); err != nil { return err } - subject := c.String("title") - body := c.String("content") - confirmSkiped := c.Bool("force") - if err := initDB(); err != nil { return err } @@ -40,7 +36,15 @@ func runSendMail(c *cli.Context) error { emails = append(emails, user.Email) } + subject := c.String("title") + confirmSkiped := c.Bool("force") + body := c.String("content") + if !confirmSkiped { + if len(body) == 0 { + fmt.Print("warning: Content is empty") + } + fmt.Print("Proceed with sending email? [Y/n] ") isConfirmed, err := confirm() if err != nil { From d3723b55e2db0a25ecdf200405e05c3e76c39e6a Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Mon, 12 Oct 2020 17:28:52 +0300 Subject: [PATCH 09/19] Fix import style Co-authored-by: 6543 <6543@obermui.de> --- cmd/mailer.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/mailer.go b/cmd/mailer.go index 5630a4273847..2e13716c0258 100644 --- a/cmd/mailer.go +++ b/cmd/mailer.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/services/mailer" + "github.com/urfave/cli" ) From 2ed24b5dac93ec74993355ca3145db6f68d3bdcc Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Fri, 16 Oct 2020 00:00:53 +0300 Subject: [PATCH 10/19] Use batch when getting all users IterateUsers uses batching by default. Signed-off-by: Maxim Zhiburt --- cmd/mailer.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/cmd/mailer.go b/cmd/mailer.go index 5630a4273847..78cdd7881b15 100644 --- a/cmd/mailer.go +++ b/cmd/mailer.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/services/mailer" + "github.com/urfave/cli" ) @@ -22,20 +23,15 @@ func runSendMail(c *cli.Context) error { return err } - users, _, err := models.SearchUsers(&models.SearchUserOptions{ - Type: models.UserTypeIndividual, - OrderBy: models.SearchOrderByAlphabetically, - ListOptions: models.ListOptions{}, + var emails []string + err := models.IterateUser(func(user *models.User) error { + emails = append(emails, user.Email) + return nil }) if err != nil { return errors.New("Cann't find users") } - var emails []string - for _, user := range users { - emails = append(emails, user.Email) - } - subject := c.String("title") confirmSkiped := c.Bool("force") body := c.String("content") From 59a1bee26d5e60e45b0c348223dcc1cabdedef7c Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Fri, 16 Oct 2020 12:24:36 +0300 Subject: [PATCH 11/19] Send emails one by one instead of as one chunck Signed-off-by: Maxim Zhiburt --- cmd/mailer.go | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/cmd/mailer.go b/cmd/mailer.go index 78cdd7881b15..4f6d759d5d43 100644 --- a/cmd/mailer.go +++ b/cmd/mailer.go @@ -23,15 +23,6 @@ func runSendMail(c *cli.Context) error { return err } - var emails []string - err := models.IterateUser(func(user *models.User) error { - emails = append(emails, user.Email) - return nil - }) - if err != nil { - return errors.New("Cann't find users") - } - subject := c.String("title") confirmSkiped := c.Bool("force") body := c.String("content") @@ -51,11 +42,22 @@ func runSendMail(c *cli.Context) error { } } - mailer.NewContext() - msg := mailer.NewMessage(emails, subject, body) - err = mailer.SendSync(msg) + var emails []string + err := models.IterateUser(func(user *models.User) error { + emails = append(emails, user.Email) + return nil + }) if err != nil { - return err + return errors.New("Cann't find users") + } + + mailer.NewContext() + for _, email := range emails { + msg := mailer.NewMessage([]string{email}, subject, body) + err = mailer.SendSync(msg) + if err != nil { + return err + } } return nil From f5a108da12c4256dc386afd26c2f0d3f8e7508b9 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Fri, 16 Oct 2020 14:01:38 +0300 Subject: [PATCH 12/19] Send messages concurantly Signed-off-by: Maxim Zhiburt --- cmd/mailer.go | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/cmd/mailer.go b/cmd/mailer.go index 4f6d759d5d43..8741d84e8ce9 100644 --- a/cmd/mailer.go +++ b/cmd/mailer.go @@ -7,8 +7,11 @@ package cmd import ( "errors" "fmt" + "sync" + "sync/atomic" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/services/mailer" "github.com/urfave/cli" @@ -51,14 +54,30 @@ func runSendMail(c *cli.Context) error { return errors.New("Cann't find users") } + fmt.Printf("Sending %d emails", len(emails)) + mailer.NewContext() + var ops uint64 + var wg sync.WaitGroup for _, email := range emails { - msg := mailer.NewMessage([]string{email}, subject, body) - err = mailer.SendSync(msg) - if err != nil { - return err - } + wg.Add(1) + go func(email, subject, content string) { + defer wg.Done() + msg := mailer.NewMessage([]string{email}, subject, body) + err = mailer.SendSync(msg) + if err != nil { + log.Error("Failed to send email %s: %v", email, err) + } else { + atomic.AddUint64(&ops, 1) + } + }(email, subject, body) } + wg.Wait() + + opsFinal := atomic.LoadUint64(&ops) + + fmt.Printf("Was sent %d emails from %d", opsFinal, len(emails)) + return nil } From 5b5f8c57344d7dcffce730d6973fddd5e04ef465 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Mon, 19 Oct 2020 20:10:11 +0300 Subject: [PATCH 13/19] Use SendAsync+Flush instead of SendSync Signed-off-by: Maxim Zhiburt --- cmd/mailer.go | 29 ++++++++--------------------- services/mailer/mailer.go | 9 +++++---- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/cmd/mailer.go b/cmd/mailer.go index 8741d84e8ce9..9951711502ac 100644 --- a/cmd/mailer.go +++ b/cmd/mailer.go @@ -7,11 +7,9 @@ package cmd import ( "errors" "fmt" - "sync" - "sync/atomic" + "time" "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/services/mailer" "github.com/urfave/cli" @@ -57,27 +55,16 @@ func runSendMail(c *cli.Context) error { fmt.Printf("Sending %d emails", len(emails)) mailer.NewContext() - var ops uint64 - var wg sync.WaitGroup + for _, email := range emails { - wg.Add(1) - go func(email, subject, content string) { - defer wg.Done() - msg := mailer.NewMessage([]string{email}, subject, body) - err = mailer.SendSync(msg) - if err != nil { - log.Error("Failed to send email %s: %v", email, err) - } else { - atomic.AddUint64(&ops, 1) - } - }(email, subject, body) + msg := mailer.NewMessage([]string{email}, subject, body) + mailer.SendAsync(msg) } - wg.Wait() - - opsFinal := atomic.LoadUint64(&ops) - - fmt.Printf("Was sent %d emails from %d", opsFinal, len(emails)) + err = mailer.FlushMessages(time.Minute * 60) + if err != nil { + return err + } return nil } diff --git a/services/mailer/mailer.go b/services/mailer/mailer.go index 312b2439bfce..b433d7b7f923 100644 --- a/services/mailer/mailer.go +++ b/services/mailer/mailer.go @@ -335,10 +335,11 @@ func NewContext() { go graceful.GetManager().RunWithShutdownFns(mailQueue.Run) } -// SendSync uses default sender to send a message with blocking -func SendSync(msg *Message) error { - gomailMsg := msg.ToMessage() - return gomail.Send(Sender, gomailMsg) +// FlushMessages a messages which were published to the mailQueue +// +// Function blocks untill the queue become empty or an operation exceed timeout duration. +func FlushMessages(timeout time.Duration) error { + return mailQueue.Flush(timeout) } // SendAsync send mail asynchronously From 042c259c411eef4ac278ae6f8d2092f5f1f848fa Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Mon, 19 Oct 2020 20:15:44 +0300 Subject: [PATCH 14/19] Add timeout parameter to sendemail command Signed-off-by: Maxim Zhiburt --- cmd/admin.go | 7 ++++++- cmd/mailer.go | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/cmd/admin.go b/cmd/admin.go index fb2e60343a57..274d8720c2fc 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -271,9 +271,14 @@ var ( Value: "", }, cli.BoolFlag{ - Name: "force,yes,y", + Name: "force,f", Usage: "A flag to bypass a confirmation step", }, + cli.DurationFlag{ + Name: "timeout", + Usage: "A timeout of sending emails", + Value: 0, + }, }, } ) diff --git a/cmd/mailer.go b/cmd/mailer.go index 9951711502ac..a1f848c793b1 100644 --- a/cmd/mailer.go +++ b/cmd/mailer.go @@ -7,7 +7,6 @@ package cmd import ( "errors" "fmt" - "time" "code.gitea.io/gitea/models" "code.gitea.io/gitea/services/mailer" @@ -27,6 +26,7 @@ func runSendMail(c *cli.Context) error { subject := c.String("title") confirmSkiped := c.Bool("force") body := c.String("content") + timeout := c.Duration("timeout") if !confirmSkiped { if len(body) == 0 { @@ -61,7 +61,7 @@ func runSendMail(c *cli.Context) error { mailer.SendAsync(msg) } - err = mailer.FlushMessages(time.Minute * 60) + err = mailer.FlushMessages(timeout) if err != nil { return err } From a25d8be24185aa681e64d5fdddc126dfc8f80e5c Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Mon, 19 Oct 2020 20:21:34 +0300 Subject: [PATCH 15/19] Fix spelling mistake Signed-off-by: Maxim Zhiburt --- services/mailer/mailer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/mailer/mailer.go b/services/mailer/mailer.go index b433d7b7f923..c11cb3d373b3 100644 --- a/services/mailer/mailer.go +++ b/services/mailer/mailer.go @@ -337,7 +337,7 @@ func NewContext() { // FlushMessages a messages which were published to the mailQueue // -// Function blocks untill the queue become empty or an operation exceed timeout duration. +// Function blocks until the queue become empty or an operation exceed timeout duration. func FlushMessages(timeout time.Duration) error { return mailQueue.Flush(timeout) } From b350eea0cf54dbddc0bed21305d26ae4d05956f8 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Fri, 23 Oct 2020 09:45:02 +0300 Subject: [PATCH 16/19] Update cmd/admin.go Co-authored-by: 6543 <6543@obermui.de> --- cmd/admin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/admin.go b/cmd/admin.go index 274d8720c2fc..d4e518bf6aa2 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -276,7 +276,7 @@ var ( }, cli.DurationFlag{ Name: "timeout", - Usage: "A timeout of sending emails", + Usage: "A timeout of sending emails (3s, 10m, 1h, ...)", Value: 0, }, }, From 3d5b393f1ef5c18de9f3973cfbfbafdfd3e29d3c Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Sat, 24 Oct 2020 00:02:19 +0300 Subject: [PATCH 17/19] Connect to a running Gitea instance --- cmd/admin.go | 5 --- cmd/mailer.go | 34 ++++---------------- modules/private/mail.go | 49 +++++++++++++++++++++++++++++ routers/private/internal.go | 1 + routers/private/mail.go | 63 +++++++++++++++++++++++++++++++++++++ services/mailer/mailer.go | 7 ----- 6 files changed, 119 insertions(+), 40 deletions(-) create mode 100644 modules/private/mail.go create mode 100644 routers/private/mail.go diff --git a/cmd/admin.go b/cmd/admin.go index 274d8720c2fc..30cc8b970413 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -274,11 +274,6 @@ var ( Name: "force,f", Usage: "A flag to bypass a confirmation step", }, - cli.DurationFlag{ - Name: "timeout", - Usage: "A timeout of sending emails", - Value: 0, - }, }, } ) diff --git a/cmd/mailer.go b/cmd/mailer.go index a1f848c793b1..a9a9048a5e45 100644 --- a/cmd/mailer.go +++ b/cmd/mailer.go @@ -5,12 +5,10 @@ package cmd import ( - "errors" "fmt" + "net/http" - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/services/mailer" - + "code.gitea.io/gitea/modules/private" "github.com/urfave/cli" ) @@ -19,14 +17,9 @@ func runSendMail(c *cli.Context) error { return err } - if err := initDB(); err != nil { - return err - } - subject := c.String("title") confirmSkiped := c.Bool("force") body := c.String("content") - timeout := c.Duration("timeout") if !confirmSkiped { if len(body) == 0 { @@ -43,28 +36,13 @@ func runSendMail(c *cli.Context) error { } } - var emails []string - err := models.IterateUser(func(user *models.User) error { - emails = append(emails, user.Email) + status, message := private.SendEmail(subject, body, nil) + if status != http.StatusOK { + fmt.Printf("error: %s", message) return nil - }) - if err != nil { - return errors.New("Cann't find users") - } - - fmt.Printf("Sending %d emails", len(emails)) - - mailer.NewContext() - - for _, email := range emails { - msg := mailer.NewMessage([]string{email}, subject, body) - mailer.SendAsync(msg) } - err = mailer.FlushMessages(timeout) - if err != nil { - return err - } + fmt.Printf("Succseded: %s", message) return nil } diff --git a/modules/private/mail.go b/modules/private/mail.go new file mode 100644 index 000000000000..553f803a3e50 --- /dev/null +++ b/modules/private/mail.go @@ -0,0 +1,49 @@ +package private + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "code.gitea.io/gitea/modules/setting" +) + +// Email structure holds a data for sending general emails +type Email struct { + Subject string + Message string + To []string +} + +// SendEmail calls the internal SendEmail function +// +// It accepts a list of usernames. +// If DB contains these users it will send the email to them. +// +// If to list == nil its supposed to send an email to every +// user present in DB +func SendEmail(subject, message string, to []string) (int, string) { + reqURL := setting.LocalURL + "api/internal/mail/send" + + req := newInternalRequest(reqURL, "POST") + req = req.Header("Content-Type", "application/json") + jsonBytes, _ := json.Marshal(Email{ + Subject: subject, + Message: message, + To: to, + }) + req.Body(jsonBytes) + resp, err := req.Response() + if err != nil { + return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error()) + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return http.StatusInternalServerError, fmt.Sprintf("Responce body error: %v", err.Error()) + } + + return http.StatusOK, fmt.Sprintf("Was sent %s from %d", body, len(to)) +} diff --git a/routers/private/internal.go b/routers/private/internal.go index 821cf62a613c..4fb267a49ab2 100644 --- a/routers/private/internal.go +++ b/routers/private/internal.go @@ -47,5 +47,6 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/manager/release-and-reopen-logging", ReleaseReopenLogging) m.Post("/manager/add-logger", bind(private.LoggerOptions{}), AddLogger) m.Post("/manager/remove-logger/:group/:name", RemoveLogger) + m.Post("/mail/send", SendEmail) }, CheckInternalToken) } diff --git a/routers/private/mail.go b/routers/private/mail.go new file mode 100644 index 000000000000..d16066e5b8ff --- /dev/null +++ b/routers/private/mail.go @@ -0,0 +1,63 @@ +package private + +import ( + "fmt" + "net/http" + "strconv" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/private" + "code.gitea.io/gitea/services/mailer" + "gitea.com/macaron/macaron" +) + +// SendEmail pushes messages to mail queue +// +// It doesn't wait before each message will be processed +func SendEmail(ctx *macaron.Context, mail private.Email) { + var emails []string + if len(mail.To) > 0 { + for _, uname := range mail.To { + user, err := models.GetUserByName(uname) + if err != nil { + err := fmt.Sprintf("Failed to get user information: %v", err) + log.Error(err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "err": err, + }) + return + } + + if user != nil { + emails = append(emails, user.Email) + } + } + } else { + err := models.IterateUser(func(user *models.User) error { + emails = append(emails, user.Email) + return nil + }) + if err != nil { + err := fmt.Sprintf("Failed to find users: %v", err) + log.Error(err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "err": err, + }) + return + } + } + + sendEmail(ctx, mail.Subject, mail.Message, emails) +} + +func sendEmail(ctx *macaron.Context, subject, message string, to []string) { + for _, email := range to { + msg := mailer.NewMessage([]string{email}, subject, message) + mailer.SendAsync(msg) + } + + wasSent := strconv.Itoa(len(to)) + + ctx.PlainText(http.StatusOK, []byte(wasSent)) +} diff --git a/services/mailer/mailer.go b/services/mailer/mailer.go index c11cb3d373b3..2e7beffa151a 100644 --- a/services/mailer/mailer.go +++ b/services/mailer/mailer.go @@ -335,13 +335,6 @@ func NewContext() { go graceful.GetManager().RunWithShutdownFns(mailQueue.Run) } -// FlushMessages a messages which were published to the mailQueue -// -// Function blocks until the queue become empty or an operation exceed timeout duration. -func FlushMessages(timeout time.Duration) error { - return mailQueue.Flush(timeout) -} - // SendAsync send mail asynchronously func SendAsync(msg *Message) { go func() { From 6968eb935403fed5bfd665840eda50bc829fdf28 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Sat, 24 Oct 2020 00:06:45 +0300 Subject: [PATCH 18/19] Fix mispelling --- modules/private/mail.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/private/mail.go b/modules/private/mail.go index 553f803a3e50..07d1cf947e05 100644 --- a/modules/private/mail.go +++ b/modules/private/mail.go @@ -42,7 +42,7 @@ func SendEmail(subject, message string, to []string) (int, string) { body, err := ioutil.ReadAll(resp.Body) if err != nil { - return http.StatusInternalServerError, fmt.Sprintf("Responce body error: %v", err.Error()) + return http.StatusInternalServerError, fmt.Sprintf("Response body error: %v", err.Error()) } return http.StatusOK, fmt.Sprintf("Was sent %s from %d", body, len(to)) From af3cff6e1f999da27481327bbf1d876f5344041c Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Sat, 24 Oct 2020 00:17:25 +0300 Subject: [PATCH 19/19] Add copyright comment --- modules/private/mail.go | 4 ++++ routers/private/mail.go | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/modules/private/mail.go b/modules/private/mail.go index 07d1cf947e05..db56009bb97c 100644 --- a/modules/private/mail.go +++ b/modules/private/mail.go @@ -1,3 +1,7 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + package private import ( diff --git a/routers/private/mail.go b/routers/private/mail.go index d16066e5b8ff..8d0975248784 100644 --- a/routers/private/mail.go +++ b/routers/private/mail.go @@ -1,3 +1,7 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + package private import (