Skip to content

Commit

Permalink
Sendmail command (#13079)
Browse files Browse the repository at this point in the history
* 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 <6543@obermui.de>

* Use batch when getting all users

IterateUsers uses batching by default.

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Send emails one by one instead of as one chunck

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Send messages concurantly

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Use SendAsync+Flush instead of SendSync

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Add timeout parameter to sendemail command

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Fix spelling mistake

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Update cmd/admin.go

Co-authored-by: 6543 <6543@obermui.de>

* Connect to a running Gitea instance

* Fix mispelling

* Add copyright comment

Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
  • Loading branch information
4 people committed Oct 24, 2020
1 parent c5020cf commit a1952af
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 0 deletions.
23 changes: 23 additions & 0 deletions cmd/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ var (
subcmdRepoSyncReleases,
subcmdRegenerate,
subcmdAuth,
subcmdSendMail,
},
}

Expand Down Expand Up @@ -282,6 +283,28 @@ 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: "",
},
cli.BoolFlag{
Name: "force,f",
Usage: "A flag to bypass a confirmation step",
},
},
}
)

func runChangePassword(c *cli.Context) error {
Expand Down
20 changes: 20 additions & 0 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package cmd
import (
"errors"
"fmt"
"strings"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/setting"
Expand All @@ -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)
}
Expand Down
48 changes: 48 additions & 0 deletions cmd/mailer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// 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 (
"fmt"
"net/http"

"code.gitea.io/gitea/modules/private"
"github.com/urfave/cli"
)

func runSendMail(c *cli.Context) error {
if err := argsSet(c, "title"); err != nil {
return err
}

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 {
return err
} else if !isConfirmed {
fmt.Println("The mail was not sent")
return nil
}
}

status, message := private.SendEmail(subject, body, nil)
if status != http.StatusOK {
fmt.Printf("error: %s", message)
return nil
}

fmt.Printf("Succseded: %s", message)

return nil
}
53 changes: 53 additions & 0 deletions modules/private/mail.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// 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 (
"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("Response body error: %v", err.Error())
}

return http.StatusOK, fmt.Sprintf("Was sent %s from %d", body, len(to))
}
1 change: 1 addition & 0 deletions routers/private/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
67 changes: 67 additions & 0 deletions routers/private/mail.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// 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 (
"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))
}

0 comments on commit a1952af

Please sign in to comment.