Refactor notifications to use a cleaner interface
authorJan Dittberner <jandd@cacert.org>
Fri, 21 Apr 2017 10:50:29 +0000 (12:50 +0200)
committerJan Dittberner <jan@dittberner.info>
Fri, 21 Apr 2017 22:14:11 +0000 (00:14 +0200)
notifications.go

index 4a76710..d4c405a 100644 (file)
@@ -8,12 +8,27 @@ import (
        "text/template"
 )
 
        "text/template"
 )
 
+type headerData struct {
+       name  string
+       value []string
+}
+
+type headerList []headerData
+
+type recipientData struct {
+       field, address, name string
+}
+
+type notificationContent struct {
+       template   string
+       data       interface{}
+       subject    string
+       headers    headerList
+       recipients []recipientData
+}
+
 type NotificationMail interface {
 type NotificationMail interface {
-       GetData() interface{}
-       GetTemplate() string
-       GetSubject() string
-       GetHeaders() map[string]string
-       GetRecipient() (string, string)
+       GetNotificationContent() *notificationContent
 }
 
 var NotifyMailChannel = make(chan NotificationMail, 1)
 }
 
 var NotifyMailChannel = make(chan NotificationMail, 1)
@@ -23,19 +38,21 @@ func MailNotifier(quitMailNotifier chan int) {
        for {
                select {
                case notification := <-NotifyMailChannel:
        for {
                select {
                case notification := <-NotifyMailChannel:
-                       mailText, err := buildMail(notification.GetTemplate(), notification.GetData())
+                       content := notification.GetNotificationContent()
+                       mailText, err := buildMail(content.template, content.data)
                        if err != nil {
                                logger.Println("ERROR building mail:", err)
                                continue
                        }
 
                        m := gomail.NewMessage()
                        if err != nil {
                                logger.Println("ERROR building mail:", err)
                                continue
                        }
 
                        m := gomail.NewMessage()
-                       m.SetHeader("From", config.NotificationSenderAddress)
-                       address, name := notification.GetRecipient()
-                       m.SetAddressHeader("To", address, name)
-                       m.SetHeader("Subject", notification.GetSubject())
-                       for header, value := range notification.GetHeaders() {
-                               m.SetHeader(header, value)
+                       m.SetAddressHeader("From", config.NotificationSenderAddress, "CAcert board voting system")
+                       for _, recipient := range content.recipients {
+                               m.SetAddressHeader(recipient.field, recipient.address, recipient.name)
+                       }
+                       m.SetHeader("Subject", content.subject)
+                       for _, header := range content.headers {
+                               m.SetHeader(header.name, header.value...)
                        }
                        m.SetBody("text/plain", mailText.String())
 
                        }
                        m.SetBody("text/plain", mailText.String())
 
@@ -65,45 +82,52 @@ func buildMail(templateName string, context interface{}) (mailText *bytes.Buffer
 
 type notificationBase struct{}
 
 
 type notificationBase struct{}
 
-func (n *notificationBase) GetRecipient() (address string, name string) {
-       address, name = config.BoardMailAddress, "CAcert board mailing list"
-       return
+func (n *notificationBase) getRecipient() recipientData {
+       return recipientData{field: "To", address: config.BoardMailAddress, name: "CAcert board mailing list"}
 }
 
 type decisionReplyBase struct {
        decision Decision
 }
 
 }
 
 type decisionReplyBase struct {
        decision Decision
 }
 
-func (n *decisionReplyBase) GetHeaders() map[string]string {
-       return map[string]string{
-               "References":  fmt.Sprintf("<%s>", n.decision.Tag),
-               "In-Reply-To": fmt.Sprintf("<%s>", n.decision.Tag),
-       }
+func (n *decisionReplyBase) getHeaders() headerList {
+       headers := make(headerList, 0)
+       headers = append(headers, headerData{
+               name: "References", value: []string{fmt.Sprintf("<%s>", n.decision.Tag)},
+       })
+       headers = append(headers, headerData{
+               name: "In-Reply-To", value: []string{fmt.Sprintf("<%s>", n.decision.Tag)},
+       })
+       return headers
+}
+
+func (n *decisionReplyBase) getSubject() string {
+       return fmt.Sprintf("Re: %s - %s", n.decision.Tag, n.decision.Title)
 }
 
 }
 
-type NotificationClosedDecision struct {
+type notificationClosedDecision struct {
        notificationBase
        decisionReplyBase
        voteSums VoteSums
 }
 
        notificationBase
        decisionReplyBase
        voteSums VoteSums
 }
 
-func NewNotificationClosedDecision(decision *Decision, voteSums *VoteSums) *NotificationClosedDecision {
-       notification := &NotificationClosedDecision{voteSums: *voteSums}
+func NewNotificationClosedDecision(decision *Decision, voteSums *VoteSums) NotificationMail {
+       notification := &notificationClosedDecision{voteSums: *voteSums}
        notification.decision = *decision
        return notification
 }
 
        notification.decision = *decision
        return notification
 }
 
-func (n *NotificationClosedDecision) GetData() interface{} {
-       return struct {
-               *Decision
-               *VoteSums
-       }{&n.decision, &n.voteSums}
-}
-
-func (n *NotificationClosedDecision) GetTemplate() string { return "closed_motion_mail.txt" }
-
-func (n *NotificationClosedDecision) GetSubject() string {
-       return fmt.Sprintf("Re: %s - %s - finalised", n.decision.Tag, n.decision.Title)
+func (n *notificationClosedDecision) GetNotificationContent() *notificationContent {
+       return &notificationContent{
+               template: "closed_motion_mail.txt",
+               data: struct {
+                       *Decision
+                       *VoteSums
+               }{&n.decision, &n.voteSums},
+               subject:    fmt.Sprintf("Re: %s - %s - finalised", n.decision.Tag, n.decision.Title),
+               headers:    n.decisionReplyBase.getHeaders(),
+               recipients: []recipientData{n.notificationBase.getRecipient()},
+       }
 }
 
 type NotificationCreateMotion struct {
 }
 
 type NotificationCreateMotion struct {
@@ -112,79 +136,75 @@ type NotificationCreateMotion struct {
        voter    Voter
 }
 
        voter    Voter
 }
 
-func (n *NotificationCreateMotion) GetData() interface{} {
+func (n *NotificationCreateMotion) GetNotificationContent() *notificationContent {
        voteURL := fmt.Sprintf("%s/vote/%s", config.BaseURL, n.decision.Tag)
        unvotedURL := fmt.Sprintf("%s/motions/?unvoted=1", config.BaseURL)
        voteURL := fmt.Sprintf("%s/vote/%s", config.BaseURL, n.decision.Tag)
        unvotedURL := fmt.Sprintf("%s/motions/?unvoted=1", config.BaseURL)
-       return struct {
-               *Decision
-               Name       string
-               VoteURL    string
-               UnvotedURL string
-       }{&n.decision, n.voter.Name, voteURL, unvotedURL}
-}
-
-func (n *NotificationCreateMotion) GetTemplate() string { return "create_motion_mail.txt" }
-
-func (n *NotificationCreateMotion) GetSubject() string {
-       return fmt.Sprintf("%s - %s", n.decision.Tag, n.decision.Title)
-}
-
-func (n *NotificationCreateMotion) GetHeaders() map[string]string {
-       return map[string]string{"Message-ID": fmt.Sprintf("<%s>", n.decision.Tag)}
+       return &notificationContent{
+               template: "create_motion_mail.txt",
+               data: struct {
+                       *Decision
+                       Name       string
+                       VoteURL    string
+                       UnvotedURL string
+               }{&n.decision, n.voter.Name, voteURL, unvotedURL},
+               subject:    fmt.Sprintf("%s - %s", n.decision.Tag, n.decision.Title),
+               headers:    headerList{headerData{"Message-ID", []string{fmt.Sprintf("<%s>", n.decision.Tag)}}},
+               recipients: []recipientData{n.notificationBase.getRecipient()},
+       }
 }
 
 }
 
-type NotificationUpdateMotion struct {
+type notificationUpdateMotion struct {
        notificationBase
        decisionReplyBase
        voter Voter
 }
 
        notificationBase
        decisionReplyBase
        voter Voter
 }
 
-func NewNotificationUpdateMotion(decision Decision, voter Voter) *NotificationUpdateMotion {
-       notification := NotificationUpdateMotion{voter: voter}
+func NewNotificationUpdateMotion(decision Decision, voter Voter) NotificationMail {
+       notification := notificationUpdateMotion{voter: voter}
        notification.decision = decision
        return &notification
 }
 
        notification.decision = decision
        return &notification
 }
 
-func (n *NotificationUpdateMotion) GetData() interface{} {
+func (n *notificationUpdateMotion) GetNotificationContent() *notificationContent {
        voteURL := fmt.Sprintf("%s/vote/%s", config.BaseURL, n.decision.Tag)
        unvotedURL := fmt.Sprintf("%s/motions/?unvoted=1", config.BaseURL)
        voteURL := fmt.Sprintf("%s/vote/%s", config.BaseURL, n.decision.Tag)
        unvotedURL := fmt.Sprintf("%s/motions/?unvoted=1", config.BaseURL)
-       return struct {
-               *Decision
-               Name       string
-               VoteURL    string
-               UnvotedURL string
-       }{&n.decision, n.voter.Name, voteURL, unvotedURL}
-}
-
-func (n *NotificationUpdateMotion) GetTemplate() string { return "update_motion_mail.txt" }
-
-func (n *NotificationUpdateMotion) GetSubject() string {
-       return fmt.Sprintf("Re: %s - %s", n.decision.Tag, n.decision.Title)
+       return &notificationContent{
+               template: "update_motion_mail.txt",
+               data: struct {
+                       *Decision
+                       Name       string
+                       VoteURL    string
+                       UnvotedURL string
+               }{&n.decision, n.voter.Name, voteURL, unvotedURL},
+               subject:    n.decisionReplyBase.getSubject(),
+               headers:    n.decisionReplyBase.getHeaders(),
+               recipients: []recipientData{n.notificationBase.getRecipient()},
+       }
 }
 
 }
 
-type NotificationWithDrawMotion struct {
+type notificationWithDrawMotion struct {
        notificationBase
        decisionReplyBase
        voter Voter
 }
 
        notificationBase
        decisionReplyBase
        voter Voter
 }
 
-func NewNotificationWithDrawMotion(decision *Decision, voter *Voter) *NotificationWithDrawMotion {
-       notification := &NotificationWithDrawMotion{voter: *voter}
+func NewNotificationWithDrawMotion(decision *Decision, voter *Voter) NotificationMail {
+       notification := &notificationWithDrawMotion{voter: *voter}
        notification.decision = *decision
        return notification
 }
 
        notification.decision = *decision
        return notification
 }
 
-func (n *NotificationWithDrawMotion) GetData() interface{} {
-       return struct {
-               *Decision
-               Name string
-       }{&n.decision, n.voter.Name}
-}
-
-func (n *NotificationWithDrawMotion) GetTemplate() string { return "withdraw_motion_mail.txt" }
-
-func (n *NotificationWithDrawMotion) GetSubject() string {
-       return fmt.Sprintf("Re: %s - %s - withdrawn", n.decision.Tag, n.decision.Title)
+func (n *notificationWithDrawMotion) GetNotificationContent() *notificationContent {
+       return &notificationContent{
+               template: "withdraw_motion_mail.txt",
+               data: struct {
+                       *Decision
+                       Name string
+               }{&n.decision, n.voter.Name},
+               subject:    fmt.Sprintf("Re: %s - %s - withdrawn", n.decision.Tag, n.decision.Title),
+               headers:    n.decisionReplyBase.getHeaders(),
+               recipients: []recipientData{n.notificationBase.getRecipient()},
+       }
 }
 
 type RemindVoterNotification struct {
 }
 
 type RemindVoterNotification struct {
@@ -192,35 +212,26 @@ type RemindVoterNotification struct {
        decisions []Decision
 }
 
        decisions []Decision
 }
 
-func (n *RemindVoterNotification) GetData() interface{} {
-       return struct {
-               Decisions []Decision
-               Name      string
-               BaseURL   string
-       }{n.decisions, n.voter.Name, config.BaseURL}
-}
-
-func (n *RemindVoterNotification) GetTemplate() string { return "remind_voter_mail.txt" }
-
-func (n *RemindVoterNotification) GetSubject() string { return "Outstanding CAcert board votes" }
-
-func (n *RemindVoterNotification) GetHeaders() map[string]string {
-       return map[string]string{}
-}
-
-func (n *RemindVoterNotification) GetRecipient() (address string, name string) {
-       address, name = n.voter.Reminder, n.voter.Name
-       return
+func (n *RemindVoterNotification) GetNotificationContent() *notificationContent {
+       return &notificationContent{
+               template: "remind_voter_mail.txt",
+               data: struct {
+                       Decisions []Decision
+                       Name      string
+                       BaseURL   string
+               }{n.decisions, n.voter.Name, config.BaseURL},
+               subject:    "Outstanding CAcert board votes",
+               recipients: []recipientData{{"To", n.voter.Reminder, n.voter.Name}},
+       }
 }
 
 type voteNotificationBase struct{}
 
 }
 
 type voteNotificationBase struct{}
 
-func (n *voteNotificationBase) GetRecipient() (address string, name string) {
-       address, name = config.VoteNoticeAddress, "CAcert board votes mailing list"
-       return
+func (n *voteNotificationBase) getRecipient() recipientData {
+       return recipientData{"To", config.VoteNoticeAddress, "CAcert board votes mailing list"}
 }
 
 }
 
-type NotificationProxyVote struct {
+type notificationProxyVote struct {
        voteNotificationBase
        decisionReplyBase
        proxy         Voter
        voteNotificationBase
        decisionReplyBase
        proxy         Voter
@@ -229,51 +240,51 @@ type NotificationProxyVote struct {
        justification string
 }
 
        justification string
 }
 
-func NewNotificationProxyVote(decision *Decision, proxy *Voter, voter *Voter, vote *Vote, justification string) *NotificationProxyVote {
-       notification := &NotificationProxyVote{proxy: *proxy, voter: *voter, vote: *vote, justification: justification}
+func NewNotificationProxyVote(decision *Decision, proxy *Voter, voter *Voter, vote *Vote, justification string) NotificationMail {
+       notification := &notificationProxyVote{proxy: *proxy, voter: *voter, vote: *vote, justification: justification}
        notification.decision = *decision
        return notification
 }
 
        notification.decision = *decision
        return notification
 }
 
-func (n *NotificationProxyVote) GetData() interface{} {
-       return struct {
-               Proxy         string
-               Vote          VoteChoice
-               Voter         string
-               Decision      *Decision
-               Justification string
-       }{n.proxy.Name, n.vote.Vote, n.voter.Name, &n.decision, n.justification}
-}
-
-func (n *NotificationProxyVote) GetTemplate() string { return "proxy_vote_mail.txt" }
-
-func (n *NotificationProxyVote) GetSubject() string {
-       return fmt.Sprintf("Re: %s - %s", n.decision.Tag, n.decision.Title)
+func (n *notificationProxyVote) GetNotificationContent() *notificationContent {
+       return &notificationContent{
+               template: "proxy_vote_mail.txt",
+               data: struct {
+                       Proxy         string
+                       Vote          VoteChoice
+                       Voter         string
+                       Decision      *Decision
+                       Justification string
+               }{n.proxy.Name, n.vote.Vote, n.voter.Name, &n.decision, n.justification},
+               subject:    n.decisionReplyBase.getSubject(),
+               headers:    n.decisionReplyBase.getHeaders(),
+               recipients: []recipientData{n.voteNotificationBase.getRecipient()},
+       }
 }
 
 }
 
-type NotificationDirectVote struct {
+type notificationDirectVote struct {
        voteNotificationBase
        decisionReplyBase
        voter Voter
        vote  Vote
 }
 
        voteNotificationBase
        decisionReplyBase
        voter Voter
        vote  Vote
 }
 
-func NewNotificationDirectVote(decision *Decision, voter *Voter, vote *Vote) *NotificationDirectVote {
-       notification := &NotificationDirectVote{voter: *voter, vote: *vote}
+func NewNotificationDirectVote(decision *Decision, voter *Voter, vote *Vote) NotificationMail {
+       notification := &notificationDirectVote{voter: *voter, vote: *vote}
        notification.decision = *decision
        return notification
 }
 
        notification.decision = *decision
        return notification
 }
 
-func (n *NotificationDirectVote) GetData() interface{} {
-       return struct {
-               Vote     VoteChoice
-               Voter    string
-               Decision *Decision
-       }{n.vote.Vote, n.voter.Name, &n.decision}
-}
-
-func (n *NotificationDirectVote) GetTemplate() string { return "direct_vote_mail.txt" }
-
-func (n *NotificationDirectVote) GetSubject() string {
-       return fmt.Sprintf("Re: %s - %s", n.decision.Tag, n.decision.Title)
+func (n *notificationDirectVote) GetNotificationContent() *notificationContent {
+       return &notificationContent{
+               template: "direct_vote_mail.txt",
+               data: struct {
+                       Vote     VoteChoice
+                       Voter    string
+                       Decision *Decision
+               }{n.vote.Vote, n.voter.Name, &n.decision},
+               subject:    n.decisionReplyBase.getSubject(),
+               headers:    n.decisionReplyBase.getHeaders(),
+               recipients: []recipientData{n.voteNotificationBase.getRecipient()},
+       }
 }
 }