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