govote/http/main.go
2024-05-14 10:27:13 +02:00

162 lines
4.0 KiB
Go

package http
import (
"fmt"
"net/http"
"strconv"
"strings"
"time"
"code.c-base.org/baccenfutter/govote/store"
"code.c-base.org/baccenfutter/govote/utils"
"code.c-base.org/baccenfutter/govote/voting"
"code.c-base.org/baccenfutter/govote/voting/quorum"
"code.c-base.org/baccenfutter/govote/voting/threshold"
"code.c-base.org/baccenfutter/govote/voting/vote"
"github.com/google/uuid"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
)
// Serve takes a bind-address and starts the HTTP server.
func Serve(bindAddr string) error {
e := echo.New()
e.Pre(middleware.RemoveTrailingSlash())
// e.Use(middleware.Recover())
NewTemplateRenderer(e, "tmpl/*.html")
e.Static("/static", "static")
e.GET("/", handleIndex)
e.GET("/v", handleVotingForm)
e.POST("/v", handleNewVoting)
e.GET("/v/:id", handleShowVoting)
e.POST("/v/:id", handleVote)
return e.Start(bindAddr)
}
func handleIndex(ctx echo.Context) error {
return ctx.Redirect(http.StatusTemporaryRedirect, "/v")
}
func handleNewVoting(ctx echo.Context) error {
id := utils.GenerateRandomString(11)
var (
formReferendum = ctx.FormValue("referendum")
formDeadline = ctx.FormValue("deadline")
formQuorum = ctx.FormValue("quorum")
formThreshold = ctx.FormValue("threshold")
formElectors = ctx.FormValue("electors")
formAnonymous = ctx.FormValue("anonymous")
)
var (
err error
r string
d time.Time
q quorum.Quorum
t threshold.Threshold
e = []string{}
a bool
)
r = formReferendum
deadlineNum := fmt.Sprintf("%s", formDeadline[:len(formDeadline)-1])
deadlineUnit := fmt.Sprintf("%s", formDeadline[len(formDeadline)-1:])
deadlineInt, err := strconv.Atoi(deadlineNum)
if err != nil {
return err
}
switch deadlineUnit {
case "m", "":
d = time.Now().UTC().Add(time.Duration(deadlineInt) * time.Minute).Round(time.Second)
case "h":
d = time.Now().UTC().Add(time.Duration(deadlineInt) * time.Hour).Round(time.Second)
case "d":
d = time.Now().UTC().Add(time.Duration(deadlineInt) * time.Hour * 24).Round(time.Second)
default:
// TODO: this needs better handling!
panic("this code should never be reached")
}
if q, err = quorum.FromString(formQuorum); err != nil {
return err
}
if t, err = threshold.FromString(formThreshold); err != nil {
return err
}
e = strings.Split(formElectors, " ")
if formAnonymous == "on" {
a = true
}
store.NewVoting(id, r, d, q, t, e, a)
return ctx.Redirect(http.StatusFound, fmt.Sprintf("/v/%s", id))
}
func handleVotingForm(ctx echo.Context) error {
return ctx.Render(http.StatusOK, "voting_form", map[string]interface{}{
"Title": "cvote",
})
}
func handleVote(ctx echo.Context) error {
var (
id = uuid.New().String()
vid = ctx.Param("id")
elector = ctx.Request().Header.Get("X-Remote-User")
choice = ctx.FormValue("vote")
v *voting.Voting
c vote.Choice
err error
)
v, err = store.GetVoting(vid)
if err != nil {
return err
}
if time.Now().UTC().After(v.Deadline()) {
return ctx.Redirect(http.StatusFound, fmt.Sprintf("/v/%s", vid))
}
if !eligible(elector, v.Electors()) {
return ctx.String(http.StatusForbidden, "")
}
if c, err = vote.ChoiceFromString(choice); err != nil {
return err
}
store.PlaceVote(id, vid, elector, c)
return ctx.Render(http.StatusFound, "thanks", map[string]interface{}{
"Title": "cvote | thx",
"Id": id,
"Vid": vid,
})
}
func handleShowVoting(ctx echo.Context) error {
v, err := store.GetVoting(ctx.Param("id"))
if err != nil {
return err
}
if v.Deadline().After(time.Now().UTC()) {
if !eligible(ctx.Request().Header.Get("X-Remote-User"), v.Electors()) {
return ctx.Render(http.StatusOK, "not_eligible", nil)
}
}
return ctx.Render(http.StatusOK, "voting", map[string]interface{}{
"Title": "cvote | " + v.Referendum(),
"Voting": v,
})
}
func eligible(e string, electors []string) bool {
if electors == nil || len(electors) == 0 {
return true
}
for _, _e := range electors {
if strings.ToLower(_e) == strings.ToLower(e) {
return true
}
}
return false
}