cacert-boardvoting/notifications.go
Jan Dittberner 4d23b6a48f Switch to more flexible go-logging
This commit switches from loggo to the more flexible go-logging
framework. Logs of severity INFO or higher are now written to a separate
boardvoting.log file.

Errors during execution of mail templates are now logged.

A reasoning for the vote result is now logged and put into the mail
notification when a decision is closed.
2017-04-22 20:07:39 +02:00

295 lines
8.3 KiB
Go

package main
import (
"bytes"
"fmt"
"github.com/Masterminds/sprig"
"gopkg.in/gomail.v2"
"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 {
GetNotificationContent() *notificationContent
}
var NotifyMailChannel = make(chan NotificationMail, 1)
func MailNotifier(quitMailNotifier chan int) {
log.Info("Launched mail notifier")
for {
select {
case notification := <-NotifyMailChannel:
content := notification.GetNotificationContent()
mailText, err := buildMail(content.template, content.data)
if err != nil {
log.Errorf("building mail failed: %v", err)
continue
}
m := gomail.NewMessage()
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())
d := gomail.NewDialer(config.MailServer.Host, config.MailServer.Port, "", "")
if err := d.DialAndSend(m); err != nil {
log.Errorf("sending mail failed: %v", err)
}
case <-quitMailNotifier:
log.Info("Ending mail notifier")
return
}
}
}
func buildMail(templateName string, context interface{}) (mailText *bytes.Buffer, err error) {
t, err := template.New(templateName).Funcs(
sprig.GenericFuncMap()).ParseFiles(fmt.Sprintf("templates/%s", templateName))
if err != nil {
return
}
mailText = bytes.NewBufferString("")
if err := t.Execute(mailText, context); err != nil {
log.Errorf("Failed to execute template %s with context %+v: %v", templateName, context, err)
return nil, err
}
return
}
type notificationBase struct{}
func (n *notificationBase) getRecipient() recipientData {
return recipientData{field: "To", address: config.NoticeMailAddress, name: "CAcert board mailing list"}
}
type decisionReplyBase struct {
decision Decision
}
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 {
notificationBase
decisionReplyBase
voteSums VoteSums
reasoning string
}
func NewNotificationClosedDecision(decision *Decision, voteSums *VoteSums, reasoning string) NotificationMail {
notification := &notificationClosedDecision{voteSums: *voteSums, reasoning: reasoning}
notification.decision = *decision
return notification
}
func (n *notificationClosedDecision) GetNotificationContent() *notificationContent {
return &notificationContent{
template: "closed_motion_mail.txt",
data: struct {
*Decision
*VoteSums
Reasoning string
}{&n.decision, &n.voteSums, n.reasoning},
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 {
notificationBase
decision Decision
voter Voter
}
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 &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 {
notificationBase
decisionReplyBase
voter Voter
}
func NewNotificationUpdateMotion(decision Decision, voter Voter) NotificationMail {
notification := notificationUpdateMotion{voter: voter}
notification.decision = decision
return &notification
}
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 &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 {
notificationBase
decisionReplyBase
voter Voter
}
func NewNotificationWithDrawMotion(decision *Decision, voter *Voter) NotificationMail {
notification := &notificationWithDrawMotion{voter: *voter}
notification.decision = *decision
return notification
}
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 {
voter Voter
decisions []Decision
}
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{}
func (n *voteNotificationBase) getRecipient() recipientData {
return recipientData{"To", config.VoteNoticeMailAddress, "CAcert board votes mailing list"}
}
type notificationProxyVote struct {
voteNotificationBase
decisionReplyBase
proxy Voter
voter Voter
vote Vote
justification string
}
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
}
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 {
voteNotificationBase
decisionReplyBase
voter Voter
vote Vote
}
func NewNotificationDirectVote(decision *Decision, voter *Voter, vote *Vote) NotificationMail {
notification := &notificationDirectVote{voter: *voter, vote: *vote}
notification.decision = *decision
return notification
}
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()},
}
}