From 8d1f18e16dcf44001155e8dff273f82900899d76 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Fri, 21 Apr 2017 11:31:32 +0200 Subject: [PATCH] Implement direct voting --- boardvoting.go | 55 +++++++++++++++++++++++++++------ notifications.go | 31 +++++++++++++++++-- templates/direct_vote_form.html | 22 +++++++++++++ templates/direct_vote_mail.txt | 10 ++++++ 4 files changed, 106 insertions(+), 12 deletions(-) create mode 100644 templates/direct_vote_form.html create mode 100644 templates/direct_vote_mail.txt diff --git a/boardvoting.go b/boardvoting.go index d62b80e..e2e9838 100644 --- a/boardvoting.go +++ b/boardvoting.go @@ -53,6 +53,7 @@ const ( ctxVoter ctxDecision ctxVote + ctxAuthenticatedCert ) func authenticateRequest(w http.ResponseWriter, r *http.Request, handler func(http.ResponseWriter, *http.Request)) { @@ -66,7 +67,9 @@ func authenticateRequest(w http.ResponseWriter, r *http.Request, handler func(ht return } if voter != nil { - handler(w, r.WithContext(context.WithValue(r.Context(), ctxVoter, voter))) + requestContext := context.WithValue(r.Context(), ctxVoter, voter) + requestContext = context.WithValue(requestContext, ctxAuthenticatedCert, cert) + handler(w, r.WithContext(requestContext)) return } } @@ -475,12 +478,12 @@ func (h motionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } -type voteHandler struct { +type directVoteHandler struct { FlashMessageAction authenticationRequiredHandler } -func (h *voteHandler) Handle(w http.ResponseWriter, r *http.Request) { +func (h *directVoteHandler) Handle(w http.ResponseWriter, r *http.Request) { decision, ok := getDecisionFromRequest(r) if !ok { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -496,10 +499,37 @@ func (h *voteHandler) Handle(w http.ResponseWriter, r *http.Request) { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } - fmt.Fprintln(w, "to be implemented") - fmt.Fprintln(w, "Decision:", decision) - fmt.Fprintln(w, "Voter:", voter) - fmt.Fprintln(w, "Vote:", vote) + switch r.Method { + case http.MethodPost: + voteResult := &Vote{ + VoterId: voter.Id, Vote: vote, DecisionId: decision.Id, Voted: time.Now().UTC(), + Notes: fmt.Sprintf("Direct Vote\n\n%s", getPEMClientCert(r))} + if err := voteResult.Save(); err != nil { + logger.Println("ERROR", "Problem saving vote:", err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + + NotifyMailChannel <- NewNotificationDirectVote(&decision.Decision, voter, voteResult) + + if err := h.AddFlash(w, r, "Your vote has been registered."); err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + + http.Redirect(w, r, "/motions/", http.StatusMovedPermanently) + default: + templates := []string{"direct_vote_form.html", "header.html", "footer.html", "motion_fragments.html"} + var templateContext struct { + Decision *DecisionForDisplay + VoteChoice VoteChoice + PageTitle string + Flashes interface{} + } + templateContext.Decision = decision + templateContext.VoteChoice = vote + renderTemplate(w, templates, templateContext) + } } type proxyVoteHandler struct { @@ -509,7 +539,8 @@ type proxyVoteHandler struct { func getPEMClientCert(r *http.Request) string { clientCertPEM := bytes.NewBufferString("") - pem.Encode(clientCertPEM, &pem.Block{Type: "CERTIFICATE", Bytes: r.TLS.PeerCertificates[0].Raw}) + authenticatedCertificate := r.Context().Value(ctxAuthenticatedCert).(*x509.Certificate) + pem.Encode(clientCertPEM, &pem.Block{Type: "CERTIFICATE", Bytes: authenticatedCertificate.Raw}) return clientCertPEM.String() } @@ -604,13 +635,17 @@ func (h *decisionVoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) }) case strings.HasPrefix(r.URL.Path, "/vote/"): parts := strings.Split(r.URL.Path[len("/vote/"):], "/") + if len(parts) != 2 { + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } motionTag := parts[0] voteValue, ok := VoteValues[parts[1]] if !ok { - http.NotFound(w, r) + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } - handler := &voteHandler{} + handler := &directVoteHandler{} authenticateRequest( w, r.WithContext(context.WithValue(r.Context(), ctxNeedsAuth, true)), func(w http.ResponseWriter, r *http.Request) { diff --git a/notifications.go b/notifications.go index 499a689..4a76710 100644 --- a/notifications.go +++ b/notifications.go @@ -113,7 +113,7 @@ type NotificationCreateMotion struct { } func (n *NotificationCreateMotion) GetData() interface{} { - voteURL := fmt.Sprintf("%s/vote", config.BaseURL) + voteURL := fmt.Sprintf("%s/vote/%s", config.BaseURL, n.decision.Tag) unvotedURL := fmt.Sprintf("%s/motions/?unvoted=1", config.BaseURL) return struct { *Decision @@ -146,7 +146,7 @@ func NewNotificationUpdateMotion(decision Decision, voter Voter) *NotificationUp } func (n *NotificationUpdateMotion) GetData() interface{} { - voteURL := fmt.Sprintf("%s/vote", config.BaseURL) + voteURL := fmt.Sprintf("%s/vote/%s", config.BaseURL, n.decision.Tag) unvotedURL := fmt.Sprintf("%s/motions/?unvoted=1", config.BaseURL) return struct { *Decision @@ -250,3 +250,30 @@ func (n *NotificationProxyVote) GetTemplate() string { return "proxy_vote_mail.t func (n *NotificationProxyVote) GetSubject() string { return fmt.Sprintf("Re: %s - %s", n.decision.Tag, n.decision.Title) } + +type NotificationDirectVote struct { + voteNotificationBase + decisionReplyBase + voter Voter + vote Vote +} + +func NewNotificationDirectVote(decision *Decision, voter *Voter, vote *Vote) *NotificationDirectVote { + notification := &NotificationDirectVote{voter: *voter, vote: *vote} + notification.decision = *decision + return notification +} + +func (n *NotificationDirectVote) GetData() interface{} { + return struct { + Vote VoteChoice + Voter string + Decision *Decision + }{n.vote.Vote, n.voter.Name, &n.decision} +} + +func (n *NotificationDirectVote) GetTemplate() string { return "direct_vote_mail.txt" } + +func (n *NotificationDirectVote) GetSubject() string { + return fmt.Sprintf("Re: %s - %s", n.decision.Tag, n.decision.Title) +} diff --git a/templates/direct_vote_form.html b/templates/direct_vote_form.html new file mode 100644 index 0000000..3b656f2 --- /dev/null +++ b/templates/direct_vote_form.html @@ -0,0 +1,22 @@ +{{ template "header" . }} +Show all votes + + + + + + + + + + {{ with .Decision }} + {{ template "motion_fragment" .}} + {{ end}} + + +
StatusMotion
+ +
+ +
+{{ template "footer" . }} \ No newline at end of file diff --git a/templates/direct_vote_mail.txt b/templates/direct_vote_mail.txt new file mode 100644 index 0000000..95bacb8 --- /dev/null +++ b/templates/direct_vote_mail.txt @@ -0,0 +1,10 @@ +Dear Board, + +{{ .Voter }} has just voted {{ .Vote }} on motion {{ .Decision.Tag }}. + +Motion: + {{ .Decision.Title }} + {{ .Decision.Content }} + +Kind regards, +the vote system \ No newline at end of file