govote/http/main.go

192 lines
4.5 KiB
Go
Raw Normal View History

2024-05-12 22:47:24 +00:00
package http
import (
"fmt"
"net/http"
2024-05-28 18:38:20 +00:00
"os"
2024-05-12 22:47:24 +00:00
"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"
)
2024-05-28 18:38:20 +00:00
var BASE_URL = os.Getenv("BASE_URL")
func init() {
if BASE_URL == "" {
BASE_URL = "http://localhost"
}
}
2024-05-14 08:26:48 +00:00
// Serve takes a bind-address and starts the HTTP server.
2024-05-12 22:47:24 +00:00
func Serve(bindAddr string) error {
2024-05-13 08:45:38 +00:00
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)
2024-05-28 18:38:20 +00:00
e.GET("v/:vid/:id", handleShowVote)
2024-05-13 08:45:38 +00:00
e.POST("/v/:id", handleVote)
return e.Start(bindAddr)
2024-05-12 22:47:24 +00:00
}
func handleIndex(ctx echo.Context) error {
2024-05-13 08:45:38 +00:00
return ctx.Redirect(http.StatusTemporaryRedirect, "/v")
2024-05-12 22:47:24 +00:00
}
func handleNewVoting(ctx echo.Context) error {
2024-05-13 08:45:38 +00:00
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:
2024-05-14 08:26:48 +00:00
// TODO: this needs better handling!
2024-05-13 08:45:38 +00:00
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))
2024-05-12 22:47:24 +00:00
}
func handleVotingForm(ctx echo.Context) error {
2024-05-13 11:20:26 +00:00
return ctx.Render(http.StatusOK, "voting_form", map[string]interface{}{
"Title": "cvote",
})
2024-05-12 22:47:24 +00:00
}
func handleVote(ctx echo.Context) error {
2024-05-13 08:45:38 +00:00
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)
2024-05-28 18:38:20 +00:00
return ctx.Redirect(http.StatusFound, fmt.Sprintf("/v/%s/%s", vid, id))
}
func handleShowVote(ctx echo.Context) error {
var (
id = ctx.Param("id")
vid = ctx.Param("vid")
)
url := fmt.Sprintf(
"%s/v/%s/%s",
BASE_URL,
vid,
id,
)
qrCode, err := generateQrCode(url)
if err != nil {
return err
}
2024-05-13 08:45:38 +00:00
return ctx.Render(http.StatusFound, "thanks", map[string]interface{}{
2024-05-28 18:38:20 +00:00
"Title": "cvote | nom nom nom",
"Id": id,
"Vid": vid,
"QRCode": qrCode,
2024-05-13 08:45:38 +00:00
})
2024-05-12 22:47:24 +00:00
}
func handleShowVoting(ctx echo.Context) error {
2024-05-13 08:45:38 +00:00
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()) {
2024-05-13 12:10:40 +00:00
return ctx.Render(http.StatusOK, "not_eligible", nil)
2024-05-13 08:45:38 +00:00
}
}
return ctx.Render(http.StatusOK, "voting", map[string]interface{}{
2024-05-13 11:20:26 +00:00
"Title": "cvote | " + v.Referendum(),
2024-05-13 08:45:38 +00:00
"Voting": v,
})
2024-05-12 22:47:24 +00:00
}
func eligible(e string, electors []string) bool {
2024-05-13 08:45:38 +00:00
if electors == nil || len(electors) == 0 {
return true
}
for _, _e := range electors {
if strings.ToLower(_e) == strings.ToLower(e) {
return true
}
}
return false
}