From 12dd0717ad0bb2389b38161f9fd6ff71e4cc0c62 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Fri, 21 Apr 2017 12:50:29 +0200 Subject: [PATCH] Refactor notifications to use a cleaner interface --- notifications.go | 281 ++++++++++++++++++++++++----------------------- 1 file changed, 146 insertions(+), 135 deletions(-) diff --git a/notifications.go b/notifications.go index 4a76710..d4c405a 100644 --- a/notifications.go +++ b/notifications.go @@ -8,12 +8,27 @@ import ( "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 { - GetData() interface{} - GetTemplate() string - GetSubject() string - GetHeaders() map[string]string - GetRecipient() (string, string) + GetNotificationContent() *notificationContent } var NotifyMailChannel = make(chan NotificationMail, 1) @@ -23,19 +38,21 @@ func MailNotifier(quitMailNotifier chan int) { 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() - 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()) @@ -65,45 +82,52 @@ func buildMail(templateName string, context interface{}) (mailText *bytes.Buffer 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 } -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 } -func NewNotificationClosedDecision(decision *Decision, voteSums *VoteSums) *NotificationClosedDecision { - notification := &NotificationClosedDecision{voteSums: *voteSums} +func NewNotificationClosedDecision(decision *Decision, voteSums *VoteSums) NotificationMail { + notification := ¬ificationClosedDecision{voteSums: *voteSums} 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 ¬ificationContent{ + 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 { @@ -112,79 +136,75 @@ type NotificationCreateMotion struct { 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) - 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 ¬ificationContent{ + 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 } -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 ¬ification } -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) - 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 ¬ificationContent{ + 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 } -func NewNotificationWithDrawMotion(decision *Decision, voter *Voter) *NotificationWithDrawMotion { - notification := &NotificationWithDrawMotion{voter: *voter} +func NewNotificationWithDrawMotion(decision *Decision, voter *Voter) NotificationMail { + notification := ¬ificationWithDrawMotion{voter: *voter} 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 ¬ificationContent{ + 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 { @@ -192,35 +212,26 @@ type RemindVoterNotification struct { 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 ¬ificationContent{ + 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{} -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 @@ -229,51 +240,51 @@ type NotificationProxyVote struct { 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 := ¬ificationProxyVote{proxy: *proxy, voter: *voter, vote: *vote, justification: justification} 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 ¬ificationContent{ + 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 } -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 := ¬ificationDirectVote{voter: *voter, vote: *vote} 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 ¬ificationContent{ + 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()}, + } }