Use static assets for HTML templates
[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 log.Info("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 log.Errorf("building mail failed: %v", 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.NewPlainDialer(config.MailServer.Host, config.MailServer.Port, "", "")
60 if err := d.DialAndSend(m); err != nil {
61 log.Errorf("sending mail failed: %v", err)
62 }
63 case <-quitMailNotifier:
64 log.Info("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 if err := t.Execute(mailText, context); err != nil {
79 log.Errorf("Failed to execute template %s with context %+v: %v", templateName, context, err)
80 return nil, err
81 }
82
83 return
84 }
85
86 type notificationBase struct{}
87
88 func (n *notificationBase) getRecipient() recipientData {
89 return recipientData{field: "To", address: config.NoticeMailAddress, name: "CAcert board mailing list"}
90 }
91
92 type decisionReplyBase struct {
93 decision Decision
94 }
95
96 func (n *decisionReplyBase) getHeaders() headerList {
97 headers := make(headerList, 0)
98 headers = append(headers, headerData{
99 name: "References", value: []string{fmt.Sprintf("<%s>", n.decision.Tag)},
100 })
101 headers = append(headers, headerData{
102 name: "In-Reply-To", value: []string{fmt.Sprintf("<%s>", n.decision.Tag)},
103 })
104 return headers
105 }
106
107 func (n *decisionReplyBase) getSubject() string {
108 return fmt.Sprintf("Re: %s - %s", n.decision.Tag, n.decision.Title)
109 }
110
111 type notificationClosedDecision struct {
112 notificationBase
113 decisionReplyBase
114 voteSums VoteSums
115 reasoning string
116 }
117
118 func NewNotificationClosedDecision(decision *Decision, voteSums *VoteSums, reasoning string) NotificationMail {
119 notification := &notificationClosedDecision{voteSums: *voteSums, reasoning: reasoning}
120 notification.decision = *decision
121 return notification
122 }
123
124 func (n *notificationClosedDecision) GetNotificationContent() *notificationContent {
125 return &notificationContent{
126 template: "closed_motion_mail.txt",
127 data: struct {
128 *Decision
129 *VoteSums
130 Reasoning string
131 }{&n.decision, &n.voteSums, n.reasoning},
132 subject: fmt.Sprintf("Re: %s - %s - finalised", n.decision.Tag, n.decision.Title),
133 headers: n.decisionReplyBase.getHeaders(),
134 recipients: []recipientData{n.notificationBase.getRecipient()},
135 }
136 }
137
138 type NotificationCreateMotion struct {
139 notificationBase
140 decision Decision
141 voter Voter
142 }
143
144 func (n *NotificationCreateMotion) GetNotificationContent() *notificationContent {
145 voteURL := fmt.Sprintf("%s/vote/%s", config.BaseURL, n.decision.Tag)
146 unvotedURL := fmt.Sprintf("%s/motions/?unvoted=1", config.BaseURL)
147 return &notificationContent{
148 template: "create_motion_mail.txt",
149 data: struct {
150 *Decision
151 Name string
152 VoteURL string
153 UnvotedURL string
154 }{&n.decision, n.voter.Name, voteURL, unvotedURL},
155 subject: fmt.Sprintf("%s - %s", n.decision.Tag, n.decision.Title),
156 headers: headerList{headerData{"Message-ID", []string{fmt.Sprintf("<%s>", n.decision.Tag)}}},
157 recipients: []recipientData{n.notificationBase.getRecipient()},
158 }
159 }
160
161 type notificationUpdateMotion struct {
162 notificationBase
163 decisionReplyBase
164 voter Voter
165 }
166
167 func NewNotificationUpdateMotion(decision Decision, voter Voter) NotificationMail {
168 notification := notificationUpdateMotion{voter: voter}
169 notification.decision = decision
170 return &notification
171 }
172
173 func (n *notificationUpdateMotion) GetNotificationContent() *notificationContent {
174 voteURL := fmt.Sprintf("%s/vote/%s", config.BaseURL, n.decision.Tag)
175 unvotedURL := fmt.Sprintf("%s/motions/?unvoted=1", config.BaseURL)
176 return &notificationContent{
177 template: "update_motion_mail.txt",
178 data: struct {
179 *Decision
180 Name string
181 VoteURL string
182 UnvotedURL string
183 }{&n.decision, n.voter.Name, voteURL, unvotedURL},
184 subject: n.decisionReplyBase.getSubject(),
185 headers: n.decisionReplyBase.getHeaders(),
186 recipients: []recipientData{n.notificationBase.getRecipient()},
187 }
188 }
189
190 type notificationWithDrawMotion struct {
191 notificationBase
192 decisionReplyBase
193 voter Voter
194 }
195
196 func NewNotificationWithDrawMotion(decision *Decision, voter *Voter) NotificationMail {
197 notification := &notificationWithDrawMotion{voter: *voter}
198 notification.decision = *decision
199 return notification
200 }
201
202 func (n *notificationWithDrawMotion) GetNotificationContent() *notificationContent {
203 return &notificationContent{
204 template: "withdraw_motion_mail.txt",
205 data: struct {
206 *Decision
207 Name string
208 }{&n.decision, n.voter.Name},
209 subject: fmt.Sprintf("Re: %s - %s - withdrawn", n.decision.Tag, n.decision.Title),
210 headers: n.decisionReplyBase.getHeaders(),
211 recipients: []recipientData{n.notificationBase.getRecipient()},
212 }
213 }
214
215 type RemindVoterNotification struct {
216 voter Voter
217 decisions []Decision
218 }
219
220 func (n *RemindVoterNotification) GetNotificationContent() *notificationContent {
221 return &notificationContent{
222 template: "remind_voter_mail.txt",
223 data: struct {
224 Decisions []Decision
225 Name string
226 BaseURL string
227 }{n.decisions, n.voter.Name, config.BaseURL},
228 subject: "Outstanding CAcert board votes",
229 recipients: []recipientData{{"To", n.voter.Reminder, n.voter.Name}},
230 }
231 }
232
233 type voteNotificationBase struct{}
234
235 func (n *voteNotificationBase) getRecipient() recipientData {
236 return recipientData{"To", config.VoteNoticeMailAddress, "CAcert board votes mailing list"}
237 }
238
239 type notificationProxyVote struct {
240 voteNotificationBase
241 decisionReplyBase
242 proxy Voter
243 voter Voter
244 vote Vote
245 justification string
246 }
247
248 func NewNotificationProxyVote(decision *Decision, proxy *Voter, voter *Voter, vote *Vote, justification string) NotificationMail {
249 notification := &notificationProxyVote{proxy: *proxy, voter: *voter, vote: *vote, justification: justification}
250 notification.decision = *decision
251 return notification
252 }
253
254 func (n *notificationProxyVote) GetNotificationContent() *notificationContent {
255 return &notificationContent{
256 template: "proxy_vote_mail.txt",
257 data: struct {
258 Proxy string
259 Vote VoteChoice
260 Voter string
261 Decision *Decision
262 Justification string
263 }{n.proxy.Name, n.vote.Vote, n.voter.Name, &n.decision, n.justification},
264 subject: n.decisionReplyBase.getSubject(),
265 headers: n.decisionReplyBase.getHeaders(),
266 recipients: []recipientData{n.voteNotificationBase.getRecipient()},
267 }
268 }
269
270 type notificationDirectVote struct {
271 voteNotificationBase
272 decisionReplyBase
273 voter Voter
274 vote Vote
275 }
276
277 func NewNotificationDirectVote(decision *Decision, voter *Voter, vote *Vote) NotificationMail {
278 notification := &notificationDirectVote{voter: *voter, vote: *vote}
279 notification.decision = *decision
280 return notification
281 }
282
283 func (n *notificationDirectVote) GetNotificationContent() *notificationContent {
284 return &notificationContent{
285 template: "direct_vote_mail.txt",
286 data: struct {
287 Vote VoteChoice
288 Voter string
289 Decision *Decision
290 }{n.vote.Vote, n.voter.Name, &n.decision},
291 subject: n.decisionReplyBase.getSubject(),
292 headers: n.decisionReplyBase.getHeaders(),
293 recipients: []recipientData{n.voteNotificationBase.getRecipient()},
294 }
295 }