Skip to content

Commit a1952af

Browse files
zhiburt6543lunnytechknowlogick
authored
Sendmail command (#13079)
* Add SendSync method Usefull to have when you need to be confident that message was sent. * Add sendmail command * add checks that if either title or content is empty then error out * Add a confirmation step * Add --force option to bypass confirm step * Move implementation of runSendMail to a different file * Add copyrighting comment * 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. * Fix import style Co-authored-by: 6543 <[email protected]> * Use batch when getting all users IterateUsers uses batching by default. Signed-off-by: Maxim Zhiburt <[email protected]> * Send emails one by one instead of as one chunck Signed-off-by: Maxim Zhiburt <[email protected]> * Send messages concurantly Signed-off-by: Maxim Zhiburt <[email protected]> * Use SendAsync+Flush instead of SendSync Signed-off-by: Maxim Zhiburt <[email protected]> * Add timeout parameter to sendemail command Signed-off-by: Maxim Zhiburt <[email protected]> * Fix spelling mistake Signed-off-by: Maxim Zhiburt <[email protected]> * Update cmd/admin.go Co-authored-by: 6543 <[email protected]> * Connect to a running Gitea instance * Fix mispelling * Add copyright comment Co-authored-by: 6543 <[email protected]> Co-authored-by: Lunny Xiao <[email protected]> Co-authored-by: techknowlogick <[email protected]>
1 parent c5020cf commit a1952af

File tree

6 files changed

+212
-0
lines changed

6 files changed

+212
-0
lines changed

cmd/admin.go

+23
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ var (
3434
subcmdRepoSyncReleases,
3535
subcmdRegenerate,
3636
subcmdAuth,
37+
subcmdSendMail,
3738
},
3839
}
3940

@@ -282,6 +283,28 @@ var (
282283
Action: runAddOauth,
283284
Flags: oauthCLIFlags,
284285
}
286+
287+
subcmdSendMail = cli.Command{
288+
Name: "sendmail",
289+
Usage: "Send a message to all users",
290+
Action: runSendMail,
291+
Flags: []cli.Flag{
292+
cli.StringFlag{
293+
Name: "title",
294+
Usage: `a title of a message`,
295+
Value: "",
296+
},
297+
cli.StringFlag{
298+
Name: "content",
299+
Usage: "a content of a message",
300+
Value: "",
301+
},
302+
cli.BoolFlag{
303+
Name: "force,f",
304+
Usage: "A flag to bypass a confirmation step",
305+
},
306+
},
307+
}
285308
)
286309

287310
func runChangePassword(c *cli.Context) error {

cmd/cmd.go

+20
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package cmd
99
import (
1010
"errors"
1111
"fmt"
12+
"strings"
1213

1314
"code.gitea.io/gitea/models"
1415
"code.gitea.io/gitea/modules/setting"
@@ -32,6 +33,25 @@ func argsSet(c *cli.Context, args ...string) error {
3233
return nil
3334
}
3435

36+
// confirm waits for user input which confirms an action
37+
func confirm() (bool, error) {
38+
var response string
39+
40+
_, err := fmt.Scanln(&response)
41+
if err != nil {
42+
return false, err
43+
}
44+
45+
switch strings.ToLower(response) {
46+
case "y", "yes":
47+
return true, nil
48+
case "n", "no":
49+
return false, nil
50+
default:
51+
return false, errors.New(response + " isn't a correct confirmation string")
52+
}
53+
}
54+
3555
func initDB() error {
3656
return initDBDisableConsole(false)
3757
}

cmd/mailer.go

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package cmd
6+
7+
import (
8+
"fmt"
9+
"net/http"
10+
11+
"code.gitea.io/gitea/modules/private"
12+
"github.com/urfave/cli"
13+
)
14+
15+
func runSendMail(c *cli.Context) error {
16+
if err := argsSet(c, "title"); err != nil {
17+
return err
18+
}
19+
20+
subject := c.String("title")
21+
confirmSkiped := c.Bool("force")
22+
body := c.String("content")
23+
24+
if !confirmSkiped {
25+
if len(body) == 0 {
26+
fmt.Print("warning: Content is empty")
27+
}
28+
29+
fmt.Print("Proceed with sending email? [Y/n] ")
30+
isConfirmed, err := confirm()
31+
if err != nil {
32+
return err
33+
} else if !isConfirmed {
34+
fmt.Println("The mail was not sent")
35+
return nil
36+
}
37+
}
38+
39+
status, message := private.SendEmail(subject, body, nil)
40+
if status != http.StatusOK {
41+
fmt.Printf("error: %s", message)
42+
return nil
43+
}
44+
45+
fmt.Printf("Succseded: %s", message)
46+
47+
return nil
48+
}

modules/private/mail.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package private
6+
7+
import (
8+
"encoding/json"
9+
"fmt"
10+
"io/ioutil"
11+
"net/http"
12+
13+
"code.gitea.io/gitea/modules/setting"
14+
)
15+
16+
// Email structure holds a data for sending general emails
17+
type Email struct {
18+
Subject string
19+
Message string
20+
To []string
21+
}
22+
23+
// SendEmail calls the internal SendEmail function
24+
//
25+
// It accepts a list of usernames.
26+
// If DB contains these users it will send the email to them.
27+
//
28+
// If to list == nil its supposed to send an email to every
29+
// user present in DB
30+
func SendEmail(subject, message string, to []string) (int, string) {
31+
reqURL := setting.LocalURL + "api/internal/mail/send"
32+
33+
req := newInternalRequest(reqURL, "POST")
34+
req = req.Header("Content-Type", "application/json")
35+
jsonBytes, _ := json.Marshal(Email{
36+
Subject: subject,
37+
Message: message,
38+
To: to,
39+
})
40+
req.Body(jsonBytes)
41+
resp, err := req.Response()
42+
if err != nil {
43+
return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error())
44+
}
45+
defer resp.Body.Close()
46+
47+
body, err := ioutil.ReadAll(resp.Body)
48+
if err != nil {
49+
return http.StatusInternalServerError, fmt.Sprintf("Response body error: %v", err.Error())
50+
}
51+
52+
return http.StatusOK, fmt.Sprintf("Was sent %s from %d", body, len(to))
53+
}

routers/private/internal.go

+1
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,6 @@ func RegisterRoutes(m *macaron.Macaron) {
4747
m.Post("/manager/release-and-reopen-logging", ReleaseReopenLogging)
4848
m.Post("/manager/add-logger", bind(private.LoggerOptions{}), AddLogger)
4949
m.Post("/manager/remove-logger/:group/:name", RemoveLogger)
50+
m.Post("/mail/send", SendEmail)
5051
}, CheckInternalToken)
5152
}

routers/private/mail.go

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package private
6+
7+
import (
8+
"fmt"
9+
"net/http"
10+
"strconv"
11+
12+
"code.gitea.io/gitea/models"
13+
"code.gitea.io/gitea/modules/log"
14+
"code.gitea.io/gitea/modules/private"
15+
"code.gitea.io/gitea/services/mailer"
16+
"gitea.com/macaron/macaron"
17+
)
18+
19+
// SendEmail pushes messages to mail queue
20+
//
21+
// It doesn't wait before each message will be processed
22+
func SendEmail(ctx *macaron.Context, mail private.Email) {
23+
var emails []string
24+
if len(mail.To) > 0 {
25+
for _, uname := range mail.To {
26+
user, err := models.GetUserByName(uname)
27+
if err != nil {
28+
err := fmt.Sprintf("Failed to get user information: %v", err)
29+
log.Error(err)
30+
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
31+
"err": err,
32+
})
33+
return
34+
}
35+
36+
if user != nil {
37+
emails = append(emails, user.Email)
38+
}
39+
}
40+
} else {
41+
err := models.IterateUser(func(user *models.User) error {
42+
emails = append(emails, user.Email)
43+
return nil
44+
})
45+
if err != nil {
46+
err := fmt.Sprintf("Failed to find users: %v", err)
47+
log.Error(err)
48+
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
49+
"err": err,
50+
})
51+
return
52+
}
53+
}
54+
55+
sendEmail(ctx, mail.Subject, mail.Message, emails)
56+
}
57+
58+
func sendEmail(ctx *macaron.Context, subject, message string, to []string) {
59+
for _, email := range to {
60+
msg := mailer.NewMessage([]string{email}, subject, message)
61+
mailer.SendAsync(msg)
62+
}
63+
64+
wasSent := strconv.Itoa(len(to))
65+
66+
ctx.PlainText(http.StatusOK, []byte(wasSent))
67+
}

0 commit comments

Comments
 (0)