Refactor notifications to use a cleaner interface
[cacert-boardvoting.git] / notifications.go
1 package main
2
3 import (
4 "bytes"
5 "fmt"
6 "github.com/Masterminds/sprig"
7 "gopkg.in/gomail.v2"
8 "text/template"
9 )
10
11 type headerData struct {
12 name string
13 value []string
14 }
15
16 type headerList []headerData
17
18 type recipientData struct {
19 field, address, name string
20 }
21
22 type notificationContent struct {
23 template string
24 data interface{}
25 subject string
26 headers headerList
27 recipients []recipientData
28 }
29
30 type NotificationMail interface {
31 GetNotificationContent() *notificationContent
32 }
33
34 var NotifyMailChannel = make(chan NotificationMail, 1)
35
36 func MailNotifier(quitMailNotifier chan int) {
37 logger.Println("Launched mail notifier")
38 for {
39 select {
40 case notification := <-NotifyMailChannel:
41 content := notification.GetNotificationContent()
42 mailText, err := buildMail(content.template, content.data)
43 if err != nil {
44 logger.Println("ERROR building mail:", err)
45 continue
46 }
47
48 m := gomail.NewMessage()
49 m.SetAddressHeader("From", config.NotificationSenderAddress, "CAcert board voting system")
50 for _, recipient := range content.recipients {
51 m.SetAddressHeader(recipient.field, recipient.address, recipient.name)
52 }
53 m.SetHeader("Subject", content.subject)
54 for _, header := range content.headers {
55 m.SetHeader(header.name, header.value...)
56 }
57 m.SetBody("text/plain", mailText.String())
58
59 d := gomail.NewDialer(config.MailServer.Host, config.MailServer.Port, "", "")
60 if err := d.DialAndSend(m); err != nil {
61 logger.Println("ERROR sending mail:", err)
62 }
63 case <-quitMailNotifier:
64 fmt.Println("Ending mail notifier")
65 return
66 }
67 }
68 }
69
70 func buildMail(templateName string, context interface{}) (mailText *bytes.Buffer, err error) {
71 t, err := template.New(templateName).Funcs(
72 sprig.GenericFuncMap()).ParseFiles(fmt.Sprintf("templates/%s", templateName))
73 if err != nil {
74 return
75 }
76
77 mailText = bytes.NewBufferString("")
78 t.Execute(mailText, context)
79
80 return
81 }
82
83 type notificationBase struct{}
84
85 func (n *notificationBase) getRecipient() recipientData {
86 return recipientData{field: "To", address: config.BoardMailAddress, name: "CAcert board mailing list"}
87 }
88
89 type decisionReplyBase struct {
90 decision Decision
91 }
92
93 func (n *decisionReplyBase) getHeaders() headerList {
94 headers := make(headerList, 0)
95 headers = append(headers, headerData{
96 name: "References", value: []string{fmt.Sprintf("<%s>", n.decision.Tag)},
97 })
98 headers = append(headers, headerData{
99 name: "In-Reply-To", value: []string{fmt.Sprintf("<%s>", n.decision.Tag)},
100 })
101 return headers
102 }
103
104 func (n *decisionReplyBase) getSubject() string {
105 return fmt.Sprintf("Re: %s - %s", n.decision.Tag, n.decision.Title)
106 }
107
108 type notificationClosedDecision struct {
109 notificationBase
110 decisionReplyBase
111 voteSums VoteSums
112 }
113
114 func NewNotificationClosedDecision(decision *Decision, voteSums *VoteSums) NotificationMail {
115 notification := &notificationClosedDecision{voteSums: *voteSums}
116 notification.decision = *decision
117 return notification
118 }
119
120 func (n *notificationClosedDecision) GetNotificationContent() *notificationContent {
121 return &notificationContent{
122 template: "closed_motion_mail.txt",
123 data: struct {
124 *Decision
125 *VoteSums
126 }{&n.decision, &n.voteSums},
127 subject: fmt.Sprintf("Re: %s - %s - finalised", n.decision.Tag, n.decision.Title),
128 headers: n.decisionReplyBase.getHeaders(),
129 recipients: []recipientData{n.notificationBase.getRecipient()},
130 }
131 }
132
133 type NotificationCreateMotion struct {
134 notificationBase
135 decision Decision
136 voter Voter
137 }
138
139 func (n *NotificationCreateMotion) GetNotificationContent() *notificationContent {
140 voteURL := fmt.Sprintf("%s/vote/%s", config.BaseURL, n.decision.Tag)
141 unvotedURL := fmt.Sprintf("%s/motions/?unvoted=1", config.BaseURL)
142 return &notificationContent{
143 template: "create_motion_mail.txt",
144 data: struct {
145 *Decision
146 Name string
147 VoteURL string
148 UnvotedURL string
149 }{&n.decision, n.voter.Name, voteURL, unvotedURL},
150 subject: fmt.Sprintf("%s - %s", n.decision.Tag, n.decision.Title),
151 headers: headerList{headerData{"Message-ID", []string{fmt.Sprintf("<%s>", n.decision.Tag)}}},
152 recipients: []recipientData{n.notificationBase.getRecipient()},
153 }
154 }
155
156 type notificationUpdateMotion struct {
157 notificationBase
158 decisionReplyBase
159 voter Voter
160 }
161
162 func NewNotificationUpdateMotion(decision Decision, voter Voter) NotificationMail {
163 notification := notificationUpdateMotion{voter: voter}
164 notification.decision = decision
165 return &notification
166 }
167
168 func (n *notificationUpdateMotion) GetNotificationContent() *notificationContent {
169 voteURL := fmt.Sprintf("%s/vote/%s", config.BaseURL, n.decision.Tag)
170 unvotedURL := fmt.Sprintf("%s/motions/?unvoted=1", config.BaseURL)
171 return &notificationContent{
172 template: "update_motion_mail.txt",
173 data: struct {
174 *Decision
175 Name string
176 VoteURL string
177 UnvotedURL string
178 }{&n.decision, n.voter.Name, voteURL, unvotedURL},
179 subject: n.decisionReplyBase.getSubject(),
180 headers: n.decisionReplyBase.getHeaders(),
181 recipients: []recipientData{n.notificationBase.getRecipient()},
182 }
183 }
184
185 type notificationWithDrawMotion struct {
186 notificationBase
187 decisionReplyBase
188 voter Voter
189 }
190
191 func NewNotificationWithDrawMotion(decision *Decision, voter *Voter) NotificationMail {
192 notification := &notificationWithDrawMotion{voter: *voter}
193 notification.decision = *decision
194 return notification
195 }
196
197 func (n *notificationWithDrawMotion) GetNotificationContent() *notificationContent {
198 return &notificationContent{
199 template: "withdraw_motion_mail.txt",
200 data: struct {
201 *Decision
202 Name string
203 }{&n.decision, n.voter.Name},
204 subject: fmt.Sprintf("Re: %s - %s - withdrawn", n.decision.Tag, n.decision.Title),
205 headers: n.decisionReplyBase.getHeaders(),
206 recipients: []recipientData{n.notificationBase.getRecipient()},
207 }
208 }
209
210 type RemindVoterNotification struct {
211 voter Voter
212 decisions []Decision
213 }
214
215 func (n *RemindVoterNotification) GetNotificationContent() *notificationContent {
216 return &notificationContent{
217 template: "remind_voter_mail.txt",
218 data: struct {
219 Decisions []Decision
220 Name string
221 BaseURL string
222 }{n.decisions, n.voter.Name, config.BaseURL},
223 subject: "Outstanding CAcert board votes",
224 recipients: []recipientData{{"To", n.voter.Reminder, n.voter.Name}},
225 }
226 }
227
228 type voteNotificationBase struct{}
229
230 func (n *voteNotificationBase) getRecipient() recipientData {
231 return recipientData{"To", config.VoteNoticeAddress, "CAcert board votes mailing list"}
232 }
233
234 type notificationProxyVote struct {
235 voteNotificationBase
236 decisionReplyBase
237 proxy Voter
238 voter Voter
239 vote Vote
240 justification string
241 }
242
243 func NewNotificationProxyVote(decision *Decision, proxy *Voter, voter *Voter, vote *Vote, justification string) NotificationMail {
244 notification := &notificationProxyVote{proxy: *proxy, voter: *voter, vote: *vote, justification: justification}
245 notification.decision = *decision
246 return notification
247 }
248
249 func (n *notificationProxyVote) GetNotificationContent() *notificationContent {
250 return &notificationContent{
251 template: "proxy_vote_mail.txt",
252 data: struct {
253 Proxy string
254 Vote VoteChoice
255 Voter string
256 Decision *Decision
257 Justification string
258 }{n.proxy.Name, n.vote.Vote, n.voter.Name, &n.decision, n.justification},
259 subject: n.decisionReplyBase.getSubject(),
260 headers: n.decisionReplyBase.getHeaders(),
261 recipients: []recipientData{n.voteNotificationBase.getRecipient()},
262 }
263 }
264
265 type notificationDirectVote struct {
266 voteNotificationBase
267 decisionReplyBase
268 voter Voter
269 vote Vote
270 }
271
272 func NewNotificationDirectVote(decision *Decision, voter *Voter, vote *Vote) NotificationMail {
273 notification := &notificationDirectVote{voter: *voter, vote: *vote}
274 notification.decision = *decision
275 return notification
276 }
277
278 func (n *notificationDirectVote) GetNotificationContent() *notificationContent {
279 return &notificationContent{
280 template: "direct_vote_mail.txt",
281 data: struct {
282 Vote VoteChoice
283 Voter string
284 Decision *Decision
285 }{n.vote.Vote, n.voter.Name, &n.decision},
286 subject: n.decisionReplyBase.getSubject(),
287 headers: n.decisionReplyBase.getHeaders(),
288 recipients: []recipientData{n.voteNotificationBase.getRecipient()},
289 }
290 }