Implement vote closing, refactor notifications

This commit is contained in:
Jan Dittberner 2017-04-19 23:32:12 +02:00 committed by Jan Dittberner
parent 0ce9ad6dcc
commit 2de96dc13d
8 changed files with 276 additions and 517 deletions

View file

@ -1,131 +0,0 @@
package main
import (
"bytes"
"fmt"
"github.com/Masterminds/sprig"
"gopkg.in/gomail.v2"
"text/template"
)
type templateBody string
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("")
t.Execute(mailText, context)
return
}
func CreateMotion(decision *Decision, voter *Voter) (err error) {
decision.ProponentId = voter.Id
err = decision.Create()
if err != nil {
logger.Println("Error saving motion:", err)
return
}
type mailContext struct {
Decision
Name string
VoteURL string
UnvotedURL string
}
voteURL := fmt.Sprintf("%s/vote", config.BaseURL)
unvotedURL := fmt.Sprintf("%s/motions/?unvoted=1", config.BaseURL)
context := mailContext{*decision, voter.Name, voteURL, unvotedURL}
mailText, err := buildMail("create_motion_mail.txt", context)
if err != nil {
logger.Println("Error", err)
return
}
m := gomail.NewMessage()
m.SetHeader("From", config.NoticeSenderAddress)
m.SetHeader("To", config.BoardMailAddress)
m.SetHeader("Subject", fmt.Sprintf("%s - %s", decision.Tag, decision.Title))
m.SetHeader("Message-ID", fmt.Sprintf("<%s>", decision.Tag))
m.SetBody("text/plain", mailText.String())
d := gomail.NewDialer(config.MailServer.Host, config.MailServer.Port, "", "")
if err := d.DialAndSend(m); err != nil {
logger.Println("Error sending mail", err)
}
return
}
func UpdateMotion(decision *Decision, voter *Voter) (err error) {
err = decision.Update()
if err != nil {
logger.Println("Error updating motion:", err)
return
}
type mailContext struct {
Decision
Name string
VoteURL string
UnvotedURL string
}
voteURL := fmt.Sprintf("%s/vote", config.BaseURL)
unvotedURL := fmt.Sprintf("%s/motions/?unvoted=1", config.BaseURL)
context := mailContext{*decision, voter.Name, voteURL, unvotedURL}
mailText, err := buildMail("update_motion_mail.txt", context)
if err != nil {
logger.Println("Error", err)
return
}
m := gomail.NewMessage()
m.SetHeader("From", config.NoticeSenderAddress)
m.SetHeader("To", config.BoardMailAddress)
m.SetHeader("Subject", fmt.Sprintf("Re: %s - %s", decision.Tag, decision.Title))
m.SetHeader("References", fmt.Sprintf("<%s>", decision.Tag))
m.SetBody("text/plain", mailText.String())
d := gomail.NewDialer(config.MailServer.Host, config.MailServer.Port, "", "")
if err := d.DialAndSend(m); err != nil {
logger.Println("Error sending mail", err)
}
return
}
func WithdrawMotion(decision *Decision, voter *Voter) (err error) {
err = decision.UpdateStatus()
type mailContext struct {
*Decision
Name string
}
context := mailContext{decision, voter.Name}
mailText, err := buildMail("withdraw_motion_mail.txt", context)
if err != nil {
logger.Println("Error", err)
return
}
m := gomail.NewMessage()
m.SetHeader("From", config.NoticeSenderAddress)
m.SetHeader("To", config.BoardMailAddress)
m.SetHeader("Subject", fmt.Sprintf("Re: %s - %s - withdrawn", decision.Tag, decision.Title))
m.SetHeader("References", fmt.Sprintf("<%s>", decision.Tag))
m.SetBody("text/plain", mailText.String())
d := gomail.NewDialer(config.MailServer.Host, config.MailServer.Port, "", "")
if err := d.DialAndSend(m); err != nil {
logger.Println("Error sending mail", err)
}
return
}

View file

@ -271,16 +271,20 @@ func (a *withDrawMotionAction) Handle(w http.ResponseWriter, r *http.Request) {
case http.MethodPost:
decision.Status = voteStatusWithdrawn
decision.Modified = time.Now().UTC()
if err := WithdrawMotion(&decision.Decision, voter); err != nil {
if err := decision.UpdateStatus(); err != nil {
logger.Println("Error withdrawing motion:", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
notifyMail <- &NotificationWithDrawMotion{decision: decision.Decision, voter: *voter}
if err := a.AddFlash(w, r, fmt.Sprintf("Motion %s has been withdrawn!", decision.Tag)); err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/motions/", http.StatusTemporaryRedirect)
return
default:
templateContext.Decision = decision
renderTemplate(w, templates, templateContext)
@ -319,10 +323,15 @@ func (h *newMotionHandler) Handle(w http.ResponseWriter, r *http.Request) {
renderTemplate(w, templates, templateContext)
} else {
data.Proposed = time.Now().UTC()
if err := CreateMotion(data, voter); err != nil {
data.ProponentId = voter.Id
if err := data.Create(); err != nil {
logger.Println("Error saving motion:", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
notifyMail <- &NotificationCreateMotion{decision: *data, voter: *voter}
if err := h.AddFlash(w, r, "The motion has been proposed!"); err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
@ -380,10 +389,14 @@ func (a editMotionAction) Handle(w http.ResponseWriter, r *http.Request) {
renderTemplate(w, templates, templateContext)
} else {
data.Modified = time.Now().UTC()
if err := UpdateMotion(data, voter); err != nil {
if err := data.Update(); err != nil {
logger.Println("Error updating motion:", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
notifyMail <- &NotificationUpdateMotion{decision: *data, voter: *voter}
if err := a.AddFlash(w, r, "The motion has been modified!"); err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
@ -502,6 +515,9 @@ func main() {
defer db.Close()
go MailNotifier()
defer CloseMailNotifier()
http.Handle("/motions/", http.StripPrefix("/motions/", motionsHandler{}))
http.Handle("/newmotion/", motionsHandler{})
http.Handle("/static/", http.FileServer(http.Dir(".")))

View file

@ -1,12 +0,0 @@
<html>
<head>
<title>CAcert Board Decisions</title>
<meta http-equiv="Content-Type" content="text/html; charset='UTF-8'" />
<link rel="stylesheet" type="text/css" href="styles.css" />
</head>
<body>
<b>You are not authorized to act here!</b><br/>
<i>If you think this is in error, please contact the administrator</i>
<i>If you don't know who that is, it is definitely not an error ;)</i>
</body>
</html>

View file

@ -1,5 +0,0 @@
<?php
header("HTTP/1.0 301 Redirect");
header("Location: motions.php");
exit();
?>

View file

@ -21,6 +21,7 @@ const (
sqlCreateDecision
sqlUpdateDecision
sqlUpdateDecisionStatus
sqlSelectClosableDecisions
)
var sqlStatements = map[sqlKey]string{
@ -103,6 +104,12 @@ WHERE id=:id`,
UPDATE decisions
SET status=:status, modified=:modified WHERE id=:id
`,
sqlSelectClosableDecisions: `
SELECT decisions.id, decisions.tag, decisions.proponent, voters.name AS proposer, decisions.proposed, decisions.title,
decisions.content, decisions.votetype, decisions.status, decisions.due, decisions.modified
FROM decisions
JOIN voters ON decisions.proponent=voters.id
WHERE decisions.status=0 AND :now > due`,
}
var db *sqlx.DB
@ -506,3 +513,81 @@ func FindVoterByAddress(emailAddress string) (voter *Voter, err error) {
}
return
}
func (d *Decision) Close() (err error) {
closeDecisionStmt, err := db.PrepareNamed(sqlStatements[sqlUpdateDecisionStatus])
if err != nil {
logger.Println("Error preparing statement:", err)
return
}
defer closeDecisionStmt.Close()
quorum, majority := d.VoteType.QuorumAndMajority()
voteSums, err := d.VoteSums()
if err != nil {
logger.Println("Error getting vote sums")
return
}
votes := voteSums.VoteCount()
if votes < quorum {
d.Status = voteStatusDeclined
} else {
votes = voteSums.Ayes + voteSums.Nayes
if (voteSums.Ayes / votes) > (majority / 100) {
d.Status = voteStatusApproved
} else {
d.Status = voteStatusDeclined
}
}
result, err := closeDecisionStmt.Exec(d)
if err != nil {
logger.Println("Error closing vote:", err)
return
}
affectedRows, err := result.RowsAffected()
if err != nil {
logger.Println("Error getting affected rows:", err)
}
if affectedRows != 1 {
logger.Printf("WARNING wrong number of affected rows: %d (1 expected)\n", affectedRows)
}
notifyMail <- &NotificationClosedDecision{decision: *d, voteSums: *voteSums}
return
}
func CloseDecisions() (err error) {
getClosedDecisionsStmt, err := db.PrepareNamed(sqlStatements[sqlSelectClosableDecisions])
if err != nil {
logger.Println("Error preparing statement:", err)
return
}
defer getClosedDecisionsStmt.Close()
params := make(map[string]interface{}, 1)
params["now"] = time.Now().UTC()
rows, err := getClosedDecisionsStmt.Queryx(params)
if err != nil {
logger.Println("Error fetching closed decisions", err)
return
}
defer rows.Close()
for rows.Next() {
d := &Decision{}
if err = rows.StructScan(d); err != nil {
logger.Println("Error filling decision from database row", err)
return
}
if err = d.Close(); err != nil {
logger.Printf("Error closing decision %s: %s\n", d.Tag, err)
return
}
}
return
}

View file

@ -1,198 +0,0 @@
<?php
if ($_SERVER['HTTPS'] != 'on') {
header("HTTP/1.0 302 Redirect");
header("Location: https://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
exit();
}
require_once("database.php");
$db = new DB();
if (!($user = $db->auth())) {
header("HTTP/1.0 302 Redirect");
header("Location: denied.php");
exit();
}
$db->getStatement("stats")->execute();
$stats = $db->getStatement("stats")->fetch();
?>
<html>
<head>
<title>CAcert Board Decisions</title>
<meta http-equiv="Content-Type" content="text/html; charset='UTF-8'" />
<link rel="stylesheet" type="text/css" href="styles.css" />
</head>
<body>
<?php
if ($_REQUEST['action'] == "store") {
if (is_numeric($_REQUEST['motion'])) {
$stmt = $db->getStatement("update decision");
$stmt->bindParam(":id",$_POST['motion']);
$stmt->bindParam(":proponent",$user['id']);
$stmt->bindParam(":title",$_POST['title']);
$stmt->bindParam(":content",$_POST['content']);
$stmt->bindParam(":due",$_POST['due']);
$stmt->bindParam(":votetype",$_POST['votetype']);
if ($stmt->execute()) {
?>
<b>The motion has been proposed!</b><br/>
<a href="motions.php">Back to motions</a><br/>
<br/>
<br/>
<?php
$decision = $db->getStatement("get decision")->execute(array($_POST['motion']))?$db->getStatement("get decision")->fetch():array();
$name = $user['name'];
$tag = $decision['tag'];
$title = $decision['title'];
$content =$decision['content'];
$due = $decision['due']." UTC";
$votetype = !$decision['votetype'] ? 'motion' : 'veto';
$baseurl = "https://".$_SERVER['HTTP_HOST'].":".$_SERVER['SERVER_PORT'].preg_replace('/motion\.php/','',$_SERVER['REQUEST_URI']);
$voteurl = $baseurl."vote.php?motion=".$decision['id'];
$unvoted = $baseurl."motions.php?unvoted=1";
$body = <<<BODY
Dear Board,
$name has modified motion $tag to the following:
$title
$content
Vote type: $votetype
To vote please choose:
Aye: $voteurl&vote=1
Naye: $voteurl&vote=-1
Abstain: $voteurl&vote=0
Please be aware, that if you have voted already your vote is still registered and valid.
If this modification has an impact on how you wish to vote, you are responsible for voting
again.
To see all your outstanding votes : $unvoted
Kind regards,
the voting system
BODY;
$db->notify("Re: $tag - $title - modified",$body,$tag);
} else {
?>
<b>The motion has NOT been proposed!</b><br/>
<a href="motions.php">Back to motions</a><br/>
<i><?php echo join("<br/>\n",$stmt->errorInfo()); ?></i><br/>
<br/>
<br/>
<?php
}
} else {
$stmt = $db->getStatement("create decision");
$stmt->bindParam(":proponent",$user['id']);
$stmt->bindParam(":title",$_POST['title']);
$stmt->bindParam(":content",$_POST['content']);
$stmt->bindParam(":votetype",$_POST['votetype']);
$stmt->bindParam(":due",$_POST['due']);
if ($stmt->execute()) {
?>
<b>The motion has been proposed!</b><br/>
<a href="motions.php">Back to motions</a><br/>
<br/>
<br/>
<?php
$decision = $db->getStatement("get new decision")->execute()?$db->getStatement("get new decision")->fetch():array();
$name = $user['name'];
$tag = $decision['tag'];
$title = $decision['title'];
$content =$decision['content'];
$due = $decision['due']." UTC";
$votetype = !$decision['votetype'] ? 'motion' : 'veto';
$baseurl = "https://".$_SERVER['HTTP_HOST'].":".$_SERVER['SERVER_PORT'].preg_replace('/motion\.php/','',$_SERVER['REQUEST_URI']);
$voteurl = $baseurl."vote.php?motion=".$decision['id'];
$unvoted = $baseurl."motions.php?unvoted=1";
$body = <<<BODY
Dear Board,
$name has made the following motion:
$title
$content
Vote type: $votetype
Voting will close $due.
To vote please choose:
Aye: $voteurl&vote=1
Naye: $voteurl&vote=-1
Abstain: $voteurl&vote=0
To see all your outstanding votes : $unvoted
Kind regards,
the voting system
BODY;
$db->notify("$tag - $title",$body,$tag,TRUE);
} else {
?>
<b>The motion has NOT been proposed!</b><br/>
<a href="motions.php">Back to motions</a><br/>
<i><?php echo join("<br/>\n",$stmt->errorInfo()); ?></i><br/>
<br/>
<br/>
<?php
}
}
}
if (is_numeric($_REQUEST['motion'])) {
$stmt = $db->getStatement("get decision");
if ($stmt->execute(array($_REQUEST['motion']))) {
$motion = $stmt->fetch();
}
if (!is_numeric($motion['id'])) {
$motion = array();
foreach (array("title","content") as $column) {
$motion[$column] = "";
}
$motion["proposer"] = $user['name'];
$motion["votetype"] = 0; // defaults to motion
}
} else {
$motion = array();
foreach (array("title","content") as $column) {
$motion[$column] = "";
}
$motion["proposer"] = $user['name'];
$motion["votetype"] = 0; // defaults to motion
}
?>
<form <?php if (is_numeric($_REQUEST['motion'])) { echo(" action=\"?\""); } ?> method="POST">
<input type="hidden" name="action" value="store" />
<?php
if (is_numeric($_REQUEST['motion'])) {
?><input type="hidden" name="motion" value="<?php echo($_REQUEST["motion"]); ?>" /><?php
}
?>
<table>
<tr><td>ID:</td><td><?php echo htmlentities($motion['tag']); ?></td></tr>
<tr><td>Proponent:</td><td><?php echo htmlentities($motion['proposer']); ?></td></tr>
<tr><td>Proposed date/time:</td><td><?php echo htmlentities($motion['proposed'] ? $motion['proposed']." UTC" : '(auto filled to current date/time)'); ?></td></tr>
<tr><td>Title:</td><td><input name="title" value="<?php echo htmlentities($motion['title'])?>"></td></tr>
<tr><td>Text:</td><td><textarea name="content"><?php echo htmlspecialchars($motion['content'])?></textarea></td></tr>
<tr><td>Vote type:</td><td><select name="votetype">
<option value="0" <?php if(!$motion['votetype']) { echo(" selected=\"selected\""); } ?>>Motion</option>
<option value="1" <?php if($motion['votetype']) { echo(" selected=\"selected\""); } ?>>Veto</option>
</select></td></tr>
<tr><td rowspan="2">Due:</td><td><?php echo($motion['due'] ? $motion['due'].' UTC' : '(autofilled from option below)')?></td></tr>
<tr><td><select name="due">
<option value="+3 days">In 3 Days</option>
<option value="+7 days">In 1 Week</option>
<option value="+14 days">In 2 Weeks</option>
<option value="+28 days">In 4 Weeks</option>
</select></td></tr>
<tr><td>&nbsp;</td><td><input type="submit" value="Propose" /></td></tr>
</table>
</form>
<br/>
<a href="motions.php">Back to motions</a>
</body>
</html>

View file

@ -1,167 +0,0 @@
<?php
require_once("database.php");
$db = new DB();
$page = is_numeric($_REQUEST['page'])?$_REQUEST['page']:1;
$user = $db->auth();
if ($_REQUEST['withdrawl'] && $_REQUEST['confirm'] && $_REQUEST['id']) {
if (!$user) {
header("HTTP/1.0 302 Redirect");
header("Location: denied.php");
exit();
}
$stmt = $db->getStatement("get decision");
$stmt->bindParam(":decision",$_REQUEST['id']);
if ($stmt->execute() && ($decision=$stmt->fetch())) {
$name = $user['name'];
$tag = $decision['tag'];
$title = $decision['title'];
$content = $decision['content'];
$body = <<<BODY
Dear Board,
$name has withdrawn the motion $tag that was as follows:
$title
$content
Kind regards,
the voting system
BODY;
$db->notify("Re: $tag - $title - withdrawn",$body,$tag);
}
$stmt = $db->getStatement("close decision");
$status = -2;
$stmt->bindParam(":status",$status);
$stmt->bindParam(":decision",$_REQUEST['id']);
$stmt->execute();
}
?>
<html>
<head>
<title>CAcert Board Decisions</title>
<meta http-equiv="Content-Type" content="text/html; charset='UTF-8'" />
<link rel="stylesheet" type="text/css" href="styles.css" />
</head>
<body>
<?php
if ($user) echo '<a href="?unvoted=1">Show my outstanding votes</a><br/>';
?>
<table class="list">
<tr>
<th>Status</th>
<th>Motion</th>
<th>Actions</th>
</tr>
<?php
if ($_REQUEST['motion']) {
$stmt = $db->getStatement("list decision");
$stmt->execute(array($_REQUEST['motion']));
} else {
if ($user && $_REQUEST['unvoted']) {
$stmt = $db->getStatement("list my unvoted decisions");
$stmt->bindParam(":id",$user['id']);
} else {
$stmt = $db->getStatement("list decisions");
}
$stmt->bindParam(":page",$page);
$stmt->execute();
}
$items = 0;
$id = -1;
while ($row = $stmt->fetch()) {
$items++;
$id = $row['id'];
?><tr>
<td class="<?php switch($row['status']) { case 0: echo "pending"; break; case 1: echo "approved"; break; case -1: echo "declined"; break; case -2: echo "withdrawn"; break; }?>">
<?php
switch($row['status']) {
case 0: echo "Pending<br/><i>".$row['due']." UTC</i>"; break;
case 1: echo "Approved<br/><i>".$row['modified']." UTC</i>"; break;
case -1: echo "Declined<br/><i>".$row['modified']." UTC</i>"; break;
case -2: echo "Withdrawn<br/><i>".$row['modified']." UTC</i>"; break;
}
?>
</td>
<td>
<i><a href="motions.php?motion=<?php echo $row['tag'].'">'.$row['tag']; ?></a></i><br/>
<b><?php echo htmlspecialchars($row['title']); ?></b><br/>
<pre><?php echo wordwrap(htmlspecialchars($row['content'])); ?></pre>
<br/>
<i>Due: <?php echo($row['due']); ?> UTC</i><br/>
<i>Proposed: <?php echo($row['proposer']); ?> (<?php echo($row['proposed']); ?> UTC)</i><br/>
<i>Vote type: <?php echo(!$row['votetype']?'motion':'veto'); ?></i><br/>
<i>Aye|Naye|Abstain: <?php echo($row['ayes']); ?>|<?php echo($row['nayes']); ?>|<?php echo($row['abstains']); ?></i><br/>
<?php
if ($row['status'] ==0 || $_REQUEST['showvotes']) {
$state = array('Naye','Abstain','Aye');
$vstmt = $db->getStatement("list votes");
$vstmt->execute(array($row['id']));
echo "<i>Votes:</i><br/>";
while ($vrow = $vstmt->fetch()) {
echo "<i>".$vrow['name'].": ".$state[$vrow['vote']+1]."</i><br/>";
}
} else {
echo '<i><a href="motions.php?motion='.$row['tag'].'&showvotes=1">Show Votes</a></i><br/>';
}
?>
</td>
<td class="actions">
<?php
if ($row['status'] == 0 && $user ) {
?>
<ul>
<li><a href="vote.php?motion=<?php echo($row['id']); ?>&amp;vote=1">Aye</a></li>
<li><a href="vote.php?motion=<?php echo($row['id']); ?>&amp;vote=0">Abstain</a></li>
<li><a href="vote.php?motion=<?php echo($row['id']); ?>&amp;vote=-1">Naye</a></li>
<li><a href="proxy.php?motion=<?php echo($row['id']); ?>">Proxy Vote</a></li>
<li><a href="motion.php?motion=<?php echo($row['id']); ?>">Modify</a></li>
<li><a href="motions.php?motion=<?php echo($row['tag']); ?>&amp;withdrawl=1">Withdrawl</a></li>
</ul>
<?php
} else {
?>
&nbsp;
<?php
}
?>
</td>
</tr><?php
}
?>
<tr>
<td colspan="2" class="navigation">
<?php if ($page>1) { ?><a href="?page=<?php echo($page-1); ?>">&lt;</a><?php } else { ?>&nbsp;<?php } ?>
&nbsp;
<?php if ($items>9) { ?><a href="?page=<?php echo($page+1); ?>">&gt;</a><?php } else { ?>&nbsp;<?php } ?>
</td>
<td class="actions">
<?php if ($user) echo('<ul><li><a href="motion.php">New Motion</a></li></ul>'); ?>
</td>
</tr>
<?php
if ($_REQUEST['withdrawl']) {
?>
<tr>
<td colspan="3">
<?php
if ($_REQUEST['confirm'] && $_REQUEST['id']) {
?>
<a href="motions.php">Motion Withdrawn</a>
<?php
} else {
?>
<form action="?withdrawl=1&amp;confirm=1&amp;id=<?php echo $id;?>" method="post">
<input type="submit" value="Withdraw">
</form>
<?php
}
?>
</td>
</tr>
<?php
}
?>
</table>
</body>
</html>

171
notifications.go Normal file
View file

@ -0,0 +1,171 @@
package main
import (
"bytes"
"fmt"
"github.com/Masterminds/sprig"
"gopkg.in/gomail.v2"
"text/template"
)
type NotificationMail interface {
GetData() interface{}
GetTemplate() string
GetSubject() string
GetHeaders() map[string]string
}
var notifyMail = make(chan NotificationMail, 1)
var quitMailNotifier = make(chan int)
func CloseMailNotifier() {
quitMailNotifier <- 1
}
func MailNotifier() {
logger.Println("Launched mail notifier")
for {
select {
case notification := <-notifyMail:
mailText, err := buildMail(notification.GetTemplate(), notification.GetData())
if err != nil {
logger.Println("ERROR building mail:", err)
continue
}
m := gomail.NewMessage()
m.SetHeader("From", config.NoticeSenderAddress)
m.SetHeader("To", config.BoardMailAddress)
m.SetHeader("Subject", notification.GetSubject())
for header, value := range notification.GetHeaders() {
m.SetHeader(header, value)
}
m.SetBody("text/plain", mailText.String())
d := gomail.NewDialer(config.MailServer.Host, config.MailServer.Port, "", "")
if err := d.DialAndSend(m); err != nil {
logger.Println("ERROR sending mail:", err)
}
case <-quitMailNotifier:
fmt.Println("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("")
t.Execute(mailText, context)
return
}
type NotificationClosedDecision struct {
decision Decision
voteSums VoteSums
}
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) GetHeaders() map[string]string {
return map[string]string{"References": fmt.Sprintf("<%s>", n.decision.Tag)}
}
type NotificationCreateMotion struct {
decision Decision
voter Voter
}
func (n *NotificationCreateMotion) GetData() interface{} {
voteURL := fmt.Sprintf("%s/vote", config.BaseURL)
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)}
}
type NotificationUpdateMotion struct {
decision Decision
voter Voter
}
func (n *NotificationUpdateMotion) GetData() interface{} {
voteURL := fmt.Sprintf("%s/vote", config.BaseURL)
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)
}
func (n *NotificationUpdateMotion) GetHeaders() map[string]string {
return map[string]string{"References": fmt.Sprintf("<%s>", n.decision.Tag)}
}
type NotificationWithDrawMotion struct {
decision Decision
voter Voter
}
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) GetHeaders() map[string]string {
return map[string]string{"References": fmt.Sprintf("<%s>", n.decision.Tag)}
}