Skip to content

Move all mail related codes from models to services/mailer #7200

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Sep 24, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
move all mail related codes from models to modules/mailer
  • Loading branch information
lunny committed Sep 24, 2019
commit 9dbf3ca4ef120261775e21b534ea9685704db05b
35 changes: 0 additions & 35 deletions models/issue_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (

"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
Expand Down Expand Up @@ -395,40 +394,6 @@ func (c *Comment) LoadDepIssueDetails() (err error) {
return err
}

// MailParticipants sends new comment emails to repository watchers
// and mentioned people.
func (c *Comment) MailParticipants(opType ActionType, issue *Issue) (err error) {
return c.mailParticipants(x, opType, issue)
}

func (c *Comment) mailParticipants(e Engine, opType ActionType, issue *Issue) (err error) {
mentions := markup.FindAllMentions(c.Content)
if err = UpdateIssueMentions(e, c.IssueID, mentions); err != nil {
return fmt.Errorf("UpdateIssueMentions [%d]: %v", c.IssueID, err)
}

if len(c.Content) > 0 {
if err = mailIssueCommentToParticipants(e, issue, c.Poster, c.Content, c, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
}

switch opType {
case ActionCloseIssue:
ct := fmt.Sprintf("Closed #%d.", issue.Index)
if err = mailIssueCommentToParticipants(e, issue, c.Poster, ct, c, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
case ActionReopenIssue:
ct := fmt.Sprintf("Reopened #%d.", issue.Index)
if err = mailIssueCommentToParticipants(e, issue, c.Poster, ct, c, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
}

return nil
}

func (c *Comment) loadReactions(e Engine) (err error) {
if c.Reactions != nil {
return nil
Expand Down
20 changes: 20 additions & 0 deletions models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,26 @@ func init() {
}
}

// WithEngine represents executing database operations
func WithEngine(f func(e Engine) error) error {
Copy link
Contributor

@zeripath zeripath Jun 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, although this is kind of what I was thinking we should do - it's not quite what I was thinking.

By passing in a models.Engine you're allowing arbitrary use of the xorm API outside of models - which I don't think we should permit. Use of xorm and the db probably should be restricted to functions in models.

I think we should be passing in a context (or at least a context provider). Something like:

type DBContext struct {
  e Engine
}

then we could have WithoutTransaction and WithTransaction callback functions that set the DBContext appropriately. Then everywhere that uses models.x would be called within at least a WithoutTransaction.

A better alternative would be through the use of a DBContextProvider interface:

type DBContextProvider interface {
    GetDBContext() DBContext
}

We make our Gitea and API contexts a DBContextProvider by setting their DBContext to be say the models.DefaultDBContext - which simply returns models.x as the little e Engine. We then make any call to an exported function in models expect a DBContextProvider as the first argument.

models.WithTransaction() then provides a callback with a different DBContextProvider which has a transaction in it.

return f(x)
}

// WithTransaction represents executing database operations on a trasaction
func WithTransaction(f func(e Engine) error) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}

if err := f(sess); err != nil {
return err
}

return sess.Commit()
}

func getEngine() (*xorm.Engine, error) {
connStr, err := setting.DBConnStr()
if err != nil {
Expand Down
55 changes: 29 additions & 26 deletions models/mail.go → modules/mailer/mail.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package models
package mailer

import (
"bytes"
"fmt"
"html/template"
"path"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/mailer"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
Expand Down Expand Up @@ -42,11 +42,11 @@ func InitMailRender(tmpls *template.Template) {

// SendTestMail sends a test mail
func SendTestMail(email string) error {
return gomail.Send(mailer.Sender, mailer.NewMessage([]string{email}, "Gitea Test Email!", "Gitea Test Email!").Message)
return gomail.Send(Sender, NewMessage([]string{email}, "Gitea Test Email!", "Gitea Test Email!").Message)
}

// SendUserMail sends a mail to the user
func SendUserMail(language string, u *User, tpl base.TplName, code, subject, info string) {
func SendUserMail(language string, u *models.User, tpl base.TplName, code, subject, info string) {
data := map[string]interface{}{
"DisplayName": u.DisplayName(),
"ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, language),
Expand All @@ -61,10 +61,10 @@ func SendUserMail(language string, u *User, tpl base.TplName, code, subject, inf
return
}

msg := mailer.NewMessage([]string{u.Email}, subject, content.String())
msg := NewMessage([]string{u.Email}, subject, content.String())
msg.Info = fmt.Sprintf("UID: %d, %s", u.ID, info)

mailer.SendAsync(msg)
SendAsync(msg)
}

// Locale represents an interface to translation
Expand All @@ -74,17 +74,17 @@ type Locale interface {
}

// SendActivateAccountMail sends an activation mail to the user (new user registration)
func SendActivateAccountMail(locale Locale, u *User) {
func SendActivateAccountMail(locale Locale, u *models.User) {
SendUserMail(locale.Language(), u, mailAuthActivate, u.GenerateActivateCode(), locale.Tr("mail.activate_account"), "activate account")
}

// SendResetPasswordMail sends a password reset mail to the user
func SendResetPasswordMail(locale Locale, u *User) {
func SendResetPasswordMail(locale Locale, u *models.User) {
SendUserMail(locale.Language(), u, mailAuthResetPassword, u.GenerateActivateCode(), locale.Tr("mail.reset_password"), "recover account")
}

// SendActivateEmailMail sends confirmation email to confirm new email address
func SendActivateEmailMail(locale Locale, u *User, email *EmailAddress) {
func SendActivateEmailMail(locale Locale, u *models.User, email *models.EmailAddress) {
data := map[string]interface{}{
"DisplayName": u.DisplayName(),
"ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, locale.Language()),
Expand All @@ -99,14 +99,18 @@ func SendActivateEmailMail(locale Locale, u *User, email *EmailAddress) {
return
}

msg := mailer.NewMessage([]string{email.Email}, locale.Tr("mail.activate_email"), content.String())
msg := NewMessage([]string{email.Email}, locale.Tr("mail.activate_email"), content.String())
msg.Info = fmt.Sprintf("UID: %d, activate email", u.ID)

mailer.SendAsync(msg)
SendAsync(msg)
}

// SendRegisterNotifyMail triggers a notify e-mail by admin created a account.
func SendRegisterNotifyMail(locale Locale, u *User) {
func SendRegisterNotifyMail(locale Locale, u *models.User) {
if setting.MailService == nil {
return
}

data := map[string]interface{}{
"DisplayName": u.DisplayName(),
"Username": u.Name,
Expand All @@ -119,14 +123,14 @@ func SendRegisterNotifyMail(locale Locale, u *User) {
return
}

msg := mailer.NewMessage([]string{u.Email}, locale.Tr("mail.register_notify"), content.String())
msg := NewMessage([]string{u.Email}, locale.Tr("mail.register_notify"), content.String())
msg.Info = fmt.Sprintf("UID: %d, registration notify", u.ID)

mailer.SendAsync(msg)
SendAsync(msg)
}

// SendCollaboratorMail sends mail notification to new collaborator.
func SendCollaboratorMail(u, doer *User, repo *Repository) {
func SendCollaboratorMail(u, doer *models.User, repo *models.Repository) {
repoName := path.Join(repo.Owner.Name, repo.Name)
subject := fmt.Sprintf("%s added you to %s", doer.DisplayName(), repoName)

Expand All @@ -143,10 +147,10 @@ func SendCollaboratorMail(u, doer *User, repo *Repository) {
return
}

msg := mailer.NewMessage([]string{u.Email}, subject, content.String())
msg := NewMessage([]string{u.Email}, subject, content.String())
msg.Info = fmt.Sprintf("UID: %d, add collaborator", u.ID)

mailer.SendAsync(msg)
SendAsync(msg)
}

func composeTplData(subject, body, link string) map[string]interface{} {
Expand All @@ -157,14 +161,13 @@ func composeTplData(subject, body, link string) map[string]interface{} {
return data
}

func composeIssueCommentMessage(issue *Issue, doer *User, content string, comment *Comment, tplName base.TplName, tos []string, info string) *mailer.Message {
func composeIssueCommentMessage(issue *models.Issue, doer *models.User, content string, comment *models.Comment, tplName base.TplName, tos []string, info string) *Message {
var subject string
if comment != nil {
subject = "Re: " + issue.mailSubject()
subject = "Re: " + mailSubject(issue)
} else {
subject = issue.mailSubject()
subject = mailSubject(issue)
}

err := issue.LoadRepo()
if err != nil {
log.Error("LoadRepo: %v", err)
Expand All @@ -185,7 +188,7 @@ func composeIssueCommentMessage(issue *Issue, doer *User, content string, commen
log.Error("Template: %v", err)
}

msg := mailer.NewMessageFrom(tos, doer.DisplayName(), setting.MailService.FromEmail, subject, mailBody.String())
msg := NewMessageFrom(tos, doer.DisplayName(), setting.MailService.FromEmail, subject, mailBody.String())
msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info)

// Set Message-ID on first message so replies know what to reference
Expand All @@ -200,18 +203,18 @@ func composeIssueCommentMessage(issue *Issue, doer *User, content string, commen
}

// SendIssueCommentMail composes and sends issue comment emails to target receivers.
func SendIssueCommentMail(issue *Issue, doer *User, content string, comment *Comment, tos []string) {
func SendIssueCommentMail(issue *models.Issue, doer *models.User, content string, comment *models.Comment, tos []string) {
if len(tos) == 0 {
return
}

mailer.SendAsync(composeIssueCommentMessage(issue, doer, content, comment, mailIssueComment, tos, "issue comment"))
SendAsync(composeIssueCommentMessage(issue, doer, content, comment, mailIssueComment, tos, "issue comment"))
}

// SendIssueMentionMail composes and sends issue mention emails to target receivers.
func SendIssueMentionMail(issue *Issue, doer *User, content string, comment *Comment, tos []string) {
func SendIssueMentionMail(issue *models.Issue, doer *models.User, content string, comment *models.Comment, tos []string) {
if len(tos) == 0 {
return
}
mailer.SendAsync(composeIssueCommentMessage(issue, doer, content, comment, mailIssueMention, tos, "issue mention"))
SendAsync(composeIssueCommentMessage(issue, doer, content, comment, mailIssueMention, tos, "issue mention"))
}
49 changes: 49 additions & 0 deletions modules/mailer/mail_comment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2019 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 mailer

import (
"fmt"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
)

// MailParticipants sends new comment emails to repository watchers
// and mentioned people.
func MailParticipantsComment(c *models.Comment, opType models.ActionType, issue *models.Issue) (err error) {
return models.WithEngine(func(e models.Engine) error {
return mailParticipantsComment(c, e, opType, issue)
})
}

func mailParticipantsComment(c *models.Comment, e models.Engine, opType models.ActionType, issue *models.Issue) (err error) {
mentions := markup.FindAllMentions(c.Content)
if err = models.UpdateIssueMentions(e, c.IssueID, mentions); err != nil {
return fmt.Errorf("UpdateIssueMentions [%d]: %v", c.IssueID, err)
}

if len(c.Content) > 0 {
if err = mailIssueCommentToParticipants(issue, c.Poster, c.Content, c, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
}

switch opType {
case models.ActionCloseIssue:
ct := fmt.Sprintf("Closed #%d.", issue.Index)
if err = mailIssueCommentToParticipants(issue, c.Poster, ct, c, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
case models.ActionReopenIssue:
ct := fmt.Sprintf("Reopened #%d.", issue.Index)
if err = mailIssueCommentToParticipants(issue, c.Poster, ct, c, mentions); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
}

return nil
}
Loading