🚨 go fmt; go mod tidy
This commit is contained in:
parent
fb2a29be51
commit
b0657a3fb2
20 changed files with 715 additions and 722 deletions
16
cmd/main.go
16
cmd/main.go
|
@ -5,12 +5,12 @@ import (
|
|||
)
|
||||
|
||||
var App = cli.App{
|
||||
Name: "govote",
|
||||
Usage: "🌈 Referendums and concensus.",
|
||||
Commands: []*cli.Command{
|
||||
newCmd,
|
||||
showCmd,
|
||||
voteCmd,
|
||||
serveCmd,
|
||||
},
|
||||
Name: "govote",
|
||||
Usage: "🌈 Referendums and concensus.",
|
||||
Commands: []*cli.Command{
|
||||
newCmd,
|
||||
showCmd,
|
||||
voteCmd,
|
||||
serveCmd,
|
||||
},
|
||||
}
|
||||
|
|
166
cmd/new.go
166
cmd/new.go
|
@ -16,93 +16,93 @@ import (
|
|||
)
|
||||
|
||||
var newCmd = &cli.Command{
|
||||
Name: "new",
|
||||
Usage: "➕ Create a voting",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "deadline",
|
||||
Usage: "Duration for which this voting is open",
|
||||
Aliases: []string{"D"},
|
||||
Value: "1m",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "quorum",
|
||||
Usage: "Minimum required number of participants",
|
||||
Aliases: []string{"Q"},
|
||||
Value: "SIMPLE",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "threshold",
|
||||
Usage: "Minimum number of positive votes",
|
||||
Aliases: []string{"T"},
|
||||
Value: "SIMPLE",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "electors",
|
||||
Usage: "Comma-separated list of eligible electors or empty if anyone can vote",
|
||||
Aliases: []string{"E"},
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "anonymous",
|
||||
Usage: "Public visibility of votes.",
|
||||
Aliases: []string{"A"},
|
||||
Value: false,
|
||||
},
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
deadline := ctx.String("deadline")
|
||||
deadlineNum := fmt.Sprintf("%s", deadline[:len(deadline)-1])
|
||||
deadlineUnit := fmt.Sprintf("%s", deadline[len(deadline)-1:])
|
||||
Name: "new",
|
||||
Usage: "➕ Create a voting",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "deadline",
|
||||
Usage: "Duration for which this voting is open",
|
||||
Aliases: []string{"D"},
|
||||
Value: "1m",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "quorum",
|
||||
Usage: "Minimum required number of participants",
|
||||
Aliases: []string{"Q"},
|
||||
Value: "SIMPLE",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "threshold",
|
||||
Usage: "Minimum number of positive votes",
|
||||
Aliases: []string{"T"},
|
||||
Value: "SIMPLE",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "electors",
|
||||
Usage: "Comma-separated list of eligible electors or empty if anyone can vote",
|
||||
Aliases: []string{"E"},
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "anonymous",
|
||||
Usage: "Public visibility of votes.",
|
||||
Aliases: []string{"A"},
|
||||
Value: false,
|
||||
},
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
deadline := ctx.String("deadline")
|
||||
deadlineNum := fmt.Sprintf("%s", deadline[:len(deadline)-1])
|
||||
deadlineUnit := fmt.Sprintf("%s", deadline[len(deadline)-1:])
|
||||
|
||||
deadlineInt, err := strconv.Atoi(deadlineNum)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deadlineInt, err := strconv.Atoi(deadlineNum)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !strings.Contains("mhd", deadlineUnit) {
|
||||
return fmt.Errorf("invalid deadline unit '%s'. use one of: [ m | d | h ]", deadlineUnit)
|
||||
}
|
||||
if !strings.Contains("mhd", deadlineUnit) {
|
||||
return fmt.Errorf("invalid deadline unit '%s'. use one of: [ m | d | h ]", deadlineUnit)
|
||||
}
|
||||
|
||||
var d time.Time
|
||||
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:
|
||||
panic("this code should never be reached")
|
||||
}
|
||||
var d time.Time
|
||||
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:
|
||||
panic("this code should never be reached")
|
||||
}
|
||||
|
||||
var (
|
||||
q quorum.Quorum
|
||||
t threshold.Threshold
|
||||
)
|
||||
if q, err = quorum.FromString(ctx.String("quorum")); err != nil {
|
||||
return err
|
||||
}
|
||||
if t, err = threshold.FromString(ctx.String("threshold")); err != nil {
|
||||
return err
|
||||
}
|
||||
e := strings.Split(ctx.String("electors"), " ")
|
||||
a := ctx.Bool("anonymous")
|
||||
var (
|
||||
q quorum.Quorum
|
||||
t threshold.Threshold
|
||||
)
|
||||
if q, err = quorum.FromString(ctx.String("quorum")); err != nil {
|
||||
return err
|
||||
}
|
||||
if t, err = threshold.FromString(ctx.String("threshold")); err != nil {
|
||||
return err
|
||||
}
|
||||
e := strings.Split(ctx.String("electors"), " ")
|
||||
a := ctx.Bool("anonymous")
|
||||
|
||||
var r = ""
|
||||
if ctx.Args().Len() == 0 {
|
||||
fmt.Print("Give your referendum a concise name or subject: ")
|
||||
inputReader := bufio.NewReader(os.Stdin)
|
||||
r, _ = inputReader.ReadString('\n')
|
||||
r = r[:len(r)-1]
|
||||
} else {
|
||||
r = strings.Join(ctx.Args().Slice(), " ")
|
||||
}
|
||||
var r = ""
|
||||
if ctx.Args().Len() == 0 {
|
||||
fmt.Print("Give your referendum a concise name or subject: ")
|
||||
inputReader := bufio.NewReader(os.Stdin)
|
||||
r, _ = inputReader.ReadString('\n')
|
||||
r = r[:len(r)-1]
|
||||
} else {
|
||||
r = strings.Join(ctx.Args().Slice(), " ")
|
||||
}
|
||||
|
||||
id := utils.GenerateRandomString(11)
|
||||
if err := store.NewVoting(string(id), r, d, q, t, e, a); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(id))
|
||||
return nil
|
||||
},
|
||||
id := utils.GenerateRandomString(11)
|
||||
if err := store.NewVoting(string(id), r, d, q, t, e, a); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(id))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
|
24
cmd/serve.go
24
cmd/serve.go
|
@ -6,16 +6,16 @@ import (
|
|||
)
|
||||
|
||||
var serveCmd = &cli.Command{
|
||||
Name: "serve",
|
||||
Usage: "Start the HTTP server",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "bind-address",
|
||||
Usage: "The TCP address:port to bind to",
|
||||
Value: ":3000",
|
||||
},
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
return http.Serve(ctx.String("bind-address"))
|
||||
},
|
||||
Name: "serve",
|
||||
Usage: "Start the HTTP server",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "bind-address",
|
||||
Usage: "The TCP address:port to bind to",
|
||||
Value: ":3000",
|
||||
},
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
return http.Serve(ctx.String("bind-address"))
|
||||
},
|
||||
}
|
||||
|
|
40
cmd/show.go
40
cmd/show.go
|
@ -10,25 +10,25 @@ import (
|
|||
)
|
||||
|
||||
var showCmd = &cli.Command{
|
||||
Name: "show",
|
||||
Usage: "📈 Display a voting",
|
||||
Action: func(ctx *cli.Context) error {
|
||||
var r string
|
||||
if ctx.Args().Len() == 0 {
|
||||
inputReader := bufio.NewReader(os.Stdin)
|
||||
r, _ = inputReader.ReadString('\n')
|
||||
r = r[:len(r)-1]
|
||||
}
|
||||
Name: "show",
|
||||
Usage: "📈 Display a voting",
|
||||
Action: func(ctx *cli.Context) error {
|
||||
var r string
|
||||
if ctx.Args().Len() == 0 {
|
||||
inputReader := bufio.NewReader(os.Stdin)
|
||||
r, _ = inputReader.ReadString('\n')
|
||||
r = r[:len(r)-1]
|
||||
}
|
||||
|
||||
id := ctx.Args().Get(0)
|
||||
if id == "" {
|
||||
return fmt.Errorf("Please provide an ID!")
|
||||
}
|
||||
voting, err := store.GetVoting(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(voting)
|
||||
return nil
|
||||
},
|
||||
id := ctx.Args().Get(0)
|
||||
if id == "" {
|
||||
return fmt.Errorf("Please provide an ID!")
|
||||
}
|
||||
voting, err := store.GetVoting(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(voting)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
|
70
cmd/vote.go
70
cmd/vote.go
|
@ -8,39 +8,39 @@ import (
|
|||
)
|
||||
|
||||
var voteCmd = &cli.Command{
|
||||
Name: "vote",
|
||||
Usage: "📄 Place vote",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "voting-id",
|
||||
Usage: "Voting ID",
|
||||
Aliases: []string{"V"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "elector",
|
||||
Usage: "Elector",
|
||||
Aliases: []string{"E"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "choice",
|
||||
Usage: "Choice",
|
||||
Aliases: []string{"C"},
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
var (
|
||||
id = uuid.New().String()
|
||||
votingID = ctx.String("voting-id")
|
||||
elector = ctx.String("elector")
|
||||
)
|
||||
choice, err := vote.ChoiceFromString(ctx.String("choice"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = store.PlaceVote(id, votingID, elector, choice)
|
||||
return err
|
||||
},
|
||||
Name: "vote",
|
||||
Usage: "📄 Place vote",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "voting-id",
|
||||
Usage: "Voting ID",
|
||||
Aliases: []string{"V"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "elector",
|
||||
Usage: "Elector",
|
||||
Aliases: []string{"E"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "choice",
|
||||
Usage: "Choice",
|
||||
Aliases: []string{"C"},
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
var (
|
||||
id = uuid.New().String()
|
||||
votingID = ctx.String("voting-id")
|
||||
elector = ctx.String("elector")
|
||||
)
|
||||
choice, err := vote.ChoiceFromString(ctx.String("choice"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = store.PlaceVote(id, votingID, elector, choice)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
|
1
go.mod
1
go.mod
|
@ -13,7 +13,6 @@ require (
|
|||
require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||
github.com/labstack/echo/v4 v4.12.0 // indirect
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
|
|
4
go.sum
4
go.sum
|
@ -8,8 +8,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
|
||||
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
|
||||
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
|
||||
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
|
@ -35,8 +33,6 @@ github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe
|
|||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
|
216
http/main.go
216
http/main.go
|
@ -19,137 +19,137 @@ import (
|
|||
)
|
||||
|
||||
func Serve(bindAddr string) error {
|
||||
e := echo.New()
|
||||
e.Pre(middleware.RemoveTrailingSlash())
|
||||
// e.Use(middleware.Recover())
|
||||
e := echo.New()
|
||||
e.Pre(middleware.RemoveTrailingSlash())
|
||||
// e.Use(middleware.Recover())
|
||||
|
||||
NewTemplateRenderer(e, "tmpl/*.html")
|
||||
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)
|
||||
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)
|
||||
return e.Start(bindAddr)
|
||||
}
|
||||
|
||||
func handleIndex(ctx echo.Context) error {
|
||||
return ctx.Redirect(http.StatusTemporaryRedirect, "/v")
|
||||
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")
|
||||
)
|
||||
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
|
||||
)
|
||||
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:
|
||||
panic("this code should never be reached")
|
||||
}
|
||||
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:
|
||||
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
|
||||
}
|
||||
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))
|
||||
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", nil)
|
||||
return ctx.Render(http.StatusOK, "voting_form", nil)
|
||||
}
|
||||
|
||||
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{}{
|
||||
"Id": id,
|
||||
"Vid": vid,
|
||||
})
|
||||
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{}{
|
||||
"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.String(http.StatusForbidden, "")
|
||||
}
|
||||
}
|
||||
return ctx.Render(http.StatusOK, "voting", map[string]interface{}{
|
||||
"Voting": v,
|
||||
})
|
||||
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.String(http.StatusForbidden, "")
|
||||
}
|
||||
}
|
||||
return ctx.Render(http.StatusOK, "voting", map[string]interface{}{
|
||||
"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
|
||||
if electors == nil || len(electors) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, _e := range electors {
|
||||
if strings.ToLower(_e) == strings.ToLower(e) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -8,24 +8,24 @@ import (
|
|||
)
|
||||
|
||||
type Template struct {
|
||||
Templates *template.Template
|
||||
Templates *template.Template
|
||||
}
|
||||
|
||||
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
|
||||
return t.Templates.ExecuteTemplate(w, name, data)
|
||||
return t.Templates.ExecuteTemplate(w, name, data)
|
||||
}
|
||||
|
||||
func NewTemplateRenderer(e *echo.Echo, paths ...string) {
|
||||
tmpl := &template.Template{}
|
||||
for i := range paths {
|
||||
template.Must(tmpl.ParseGlob(paths[i]))
|
||||
}
|
||||
t := newTemplate(tmpl)
|
||||
e.Renderer = t
|
||||
tmpl := &template.Template{}
|
||||
for i := range paths {
|
||||
template.Must(tmpl.ParseGlob(paths[i]))
|
||||
}
|
||||
t := newTemplate(tmpl)
|
||||
e.Renderer = t
|
||||
}
|
||||
|
||||
func newTemplate(templates *template.Template) echo.Renderer {
|
||||
return &Template{
|
||||
Templates: templates,
|
||||
}
|
||||
return &Template{
|
||||
Templates: templates,
|
||||
}
|
||||
}
|
||||
|
|
8
main.go
8
main.go
|
@ -11,8 +11,8 @@ import (
|
|||
//go:generate cp global.css static/css/
|
||||
|
||||
func main() {
|
||||
app := cmd.App
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
app := cmd.App
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
17
store/db.go
17
store/db.go
|
@ -1,8 +1,8 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"database/sql"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
const file = "./govote.db"
|
||||
|
@ -10,12 +10,11 @@ const file = "./govote.db"
|
|||
var db *sql.DB
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
if db, err = sql.Open("sqlite3", file); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var err error
|
||||
if db, err = sql.Open("sqlite3", file); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
initCreateTables(db)
|
||||
initStmts(db)
|
||||
initCreateTables(db)
|
||||
initStmts(db)
|
||||
}
|
||||
|
||||
|
|
164
store/handler.go
164
store/handler.go
|
@ -12,103 +12,103 @@ import (
|
|||
)
|
||||
|
||||
func NewVoting(
|
||||
id string,
|
||||
r string,
|
||||
d time.Time,
|
||||
q quorum.Quorum,
|
||||
t threshold.Threshold,
|
||||
e []string,
|
||||
a bool,
|
||||
id string,
|
||||
r string,
|
||||
d time.Time,
|
||||
q quorum.Quorum,
|
||||
t threshold.Threshold,
|
||||
e []string,
|
||||
a bool,
|
||||
) error {
|
||||
electors := strings.Join(e, " ")
|
||||
if _, err := votingInsert.Exec(id, r, d.String(), q.String(), t.String(), electors, a); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
electors := strings.Join(e, " ")
|
||||
if _, err := votingInsert.Exec(id, r, d.String(), q.String(), t.String(), electors, a); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetVoting(id string) (*voting.Voting, error) {
|
||||
result := votingSelect.QueryRow(id)
|
||||
if result == nil {
|
||||
return nil, fmt.Errorf("not found: %s", id)
|
||||
}
|
||||
var (
|
||||
err error
|
||||
r string
|
||||
d time.Time
|
||||
q quorum.Quorum
|
||||
t threshold.Threshold
|
||||
e []string
|
||||
a bool
|
||||
dbDeadline string
|
||||
dbQuorum string
|
||||
dbThreshold string
|
||||
dbElectors string
|
||||
)
|
||||
if err := result.Scan(&r, &dbDeadline, &dbQuorum, &dbThreshold, &dbElectors, &a); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := votingSelect.QueryRow(id)
|
||||
if result == nil {
|
||||
return nil, fmt.Errorf("not found: %s", id)
|
||||
}
|
||||
var (
|
||||
err error
|
||||
r string
|
||||
d time.Time
|
||||
q quorum.Quorum
|
||||
t threshold.Threshold
|
||||
e []string
|
||||
a bool
|
||||
dbDeadline string
|
||||
dbQuorum string
|
||||
dbThreshold string
|
||||
dbElectors string
|
||||
)
|
||||
if err := result.Scan(&r, &dbDeadline, &dbQuorum, &dbThreshold, &dbElectors, &a); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if d, err = time.Parse("2006-01-02 15:04:05 -0700 MST", dbDeadline); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d, err = time.Parse("2006-01-02 15:04:05 -0700 MST", dbDeadline); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if q, err = quorum.FromString(dbQuorum); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if t, err = threshold.FromString(dbThreshold); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, _e := range strings.Split(dbElectors, " ") {
|
||||
if _e != "" {
|
||||
e = append(e, _e)
|
||||
}
|
||||
}
|
||||
if q, err = quorum.FromString(dbQuorum); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if t, err = threshold.FromString(dbThreshold); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, _e := range strings.Split(dbElectors, " ") {
|
||||
if _e != "" {
|
||||
e = append(e, _e)
|
||||
}
|
||||
}
|
||||
|
||||
votes, err := getVotes(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := voting.NewVotingWithVotes(id, r, d, q, t, e, a, votes)
|
||||
return v, nil
|
||||
votes, err := getVotes(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := voting.NewVotingWithVotes(id, r, d, q, t, e, a, votes)
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func PlaceVote(id, votingID, elector string, choice vote.Choice) error {
|
||||
if _, err := voteInsert.Exec(id, votingID, elector, choice.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
if _, err := voteInsert.Exec(id, votingID, elector, choice.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getVotes(id string) ([]vote.Vote, error) {
|
||||
result, err := voteSelect.Query(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
e string
|
||||
c vote.Choice
|
||||
ts time.Time
|
||||
dbChoice string
|
||||
dbTimestamp string
|
||||
votes = []vote.Vote{}
|
||||
)
|
||||
for result.Next() {
|
||||
if err = result.Scan(&e, &dbChoice, &dbTimestamp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err := voteSelect.Query(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
e string
|
||||
c vote.Choice
|
||||
ts time.Time
|
||||
dbChoice string
|
||||
dbTimestamp string
|
||||
votes = []vote.Vote{}
|
||||
)
|
||||
for result.Next() {
|
||||
if err = result.Scan(&e, &dbChoice, &dbTimestamp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c, err = vote.ChoiceFromString(dbChoice); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c, err = vote.ChoiceFromString(dbChoice); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ts, err = time.Parse("2006-01-02 15:04:05", dbTimestamp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ts, err = time.Parse("2006-01-02 15:04:05", dbTimestamp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := vote.NewVoteWithTimestamp(e, c, ts)
|
||||
votes = append(votes, v)
|
||||
}
|
||||
v := vote.NewVoteWithTimestamp(e, c, ts)
|
||||
votes = append(votes, v)
|
||||
}
|
||||
|
||||
return votes, nil
|
||||
return votes, nil
|
||||
}
|
||||
|
|
|
@ -3,16 +3,16 @@ package store
|
|||
import "database/sql"
|
||||
|
||||
var (
|
||||
votingInsert *sql.Stmt
|
||||
votingSelect *sql.Stmt
|
||||
voteEligible *sql.Stmt
|
||||
voteInsert *sql.Stmt
|
||||
voteSelect *sql.Stmt
|
||||
votingInsert *sql.Stmt
|
||||
votingSelect *sql.Stmt
|
||||
voteEligible *sql.Stmt
|
||||
voteInsert *sql.Stmt
|
||||
voteSelect *sql.Stmt
|
||||
)
|
||||
|
||||
func initCreateTables(db *sql.DB) {
|
||||
var err error
|
||||
createTables := `
|
||||
var err error
|
||||
createTables := `
|
||||
CREATE TABLE IF NOT EXISTS voting (
|
||||
id TEXT PRIMARY KEY,
|
||||
referendum TEXT NOT NULL,
|
||||
|
@ -36,23 +36,23 @@ func initCreateTables(db *sql.DB) {
|
|||
CREATE INDEX IF NOT EXISTS vote_voting ON vote ( voting );
|
||||
`
|
||||
|
||||
if _, err = db.Exec(createTables); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if _, err = db.Exec(createTables); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func initStmts(db *sql.DB) {
|
||||
initStmtVotingInsert(db)
|
||||
initStmtVotingSelect(db)
|
||||
initStmtVotingInsert(db)
|
||||
initStmtVotingSelect(db)
|
||||
|
||||
initStmtVoteEligible(db)
|
||||
initStmtVoteInsert(db)
|
||||
initStmtVoteSelect(db)
|
||||
initStmtVoteEligible(db)
|
||||
initStmtVoteInsert(db)
|
||||
initStmtVoteSelect(db)
|
||||
}
|
||||
|
||||
func initStmtVotingInsert(db *sql.DB) {
|
||||
var err error
|
||||
if votingInsert, err = db.Prepare(`
|
||||
var err error
|
||||
if votingInsert, err = db.Prepare(`
|
||||
INSERT INTO voting (
|
||||
id,
|
||||
referendum,
|
||||
|
@ -63,13 +63,13 @@ func initStmtVotingInsert(db *sql.DB) {
|
|||
anonymous
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?);
|
||||
`); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func initStmtVotingSelect(db *sql.DB) {
|
||||
var err error
|
||||
if votingSelect, err = db.Prepare(`
|
||||
var err error
|
||||
if votingSelect, err = db.Prepare(`
|
||||
SELECT
|
||||
referendum,
|
||||
deadline,
|
||||
|
@ -80,13 +80,13 @@ func initStmtVotingSelect(db *sql.DB) {
|
|||
FROM voting
|
||||
WHERE id = ?;
|
||||
`); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func initStmtVoteEligible(db *sql.DB) {
|
||||
var err error
|
||||
if voteEligible, err = db.Prepare(`
|
||||
var err error
|
||||
if voteEligible, err = db.Prepare(`
|
||||
SELECT
|
||||
id,
|
||||
referendum,
|
||||
|
@ -104,26 +104,26 @@ func initStmtVoteEligible(db *sql.DB) {
|
|||
)
|
||||
LIMIT 1;
|
||||
`); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func initStmtVoteInsert(db *sql.DB) {
|
||||
var err error
|
||||
if voteInsert, err = db.Prepare(`
|
||||
var err error
|
||||
if voteInsert, err = db.Prepare(`
|
||||
INSERT INTO vote (
|
||||
id, voting, elector, choice
|
||||
) VALUES (
|
||||
?, ?, ?, ?
|
||||
);
|
||||
`); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func initStmtVoteSelect(db *sql.DB) {
|
||||
var err error
|
||||
if voteSelect, err = db.Prepare(`
|
||||
var err error
|
||||
if voteSelect, err = db.Prepare(`
|
||||
SELECT
|
||||
elector,
|
||||
choice,
|
||||
|
@ -131,6 +131,6 @@ func initStmtVoteSelect(db *sql.DB) {
|
|||
FROM vote
|
||||
WHERE voting = ?;
|
||||
`); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,9 @@ import (
|
|||
const charSet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
||||
func GenerateRandomString(length int) string {
|
||||
result := make([]byte, length)
|
||||
for i := 0; i < length; i++ {
|
||||
result[i] = charSet[rand.Intn(len(charSet))]
|
||||
}
|
||||
return string(result)
|
||||
result := make([]byte, length)
|
||||
for i := 0; i < length; i++ {
|
||||
result[i] = charSet[rand.Intn(len(charSet))]
|
||||
}
|
||||
return string(result)
|
||||
}
|
||||
|
|
209
voting/main.go
209
voting/main.go
|
@ -13,95 +13,95 @@ import (
|
|||
|
||||
type (
|
||||
Voting struct {
|
||||
id string
|
||||
id string
|
||||
referendum string
|
||||
deadline time.Time
|
||||
quorum quorum.Quorum
|
||||
threshold threshold.Threshold
|
||||
electors []string
|
||||
annonymous bool
|
||||
annonymous bool
|
||||
votes []vote.Vote
|
||||
}
|
||||
)
|
||||
|
||||
func NewVoting(id, r string, d time.Time, q quorum.Quorum, t threshold.Threshold, e []string, a bool) *Voting {
|
||||
return &Voting{
|
||||
id: id,
|
||||
id: id,
|
||||
referendum: r,
|
||||
deadline: d,
|
||||
quorum: q,
|
||||
threshold: t,
|
||||
electors: e,
|
||||
annonymous: a,
|
||||
votes: []vote.Vote{},
|
||||
deadline: d,
|
||||
quorum: q,
|
||||
threshold: t,
|
||||
electors: e,
|
||||
annonymous: a,
|
||||
votes: []vote.Vote{},
|
||||
}
|
||||
}
|
||||
|
||||
func NewVotingWithVotes(id, r string, d time.Time, q quorum.Quorum, t threshold.Threshold, e []string, a bool, v []vote.Vote) *Voting {
|
||||
voting := NewVoting(id, r, d, q, t, e, a)
|
||||
for i := range v {
|
||||
voting.votes = append(voting.votes, v[i])
|
||||
voting.votes = append(voting.votes, v[i])
|
||||
}
|
||||
return voting
|
||||
}
|
||||
|
||||
func (v Voting) String() string {
|
||||
var (
|
||||
possibleVotes int = len(v.electors)
|
||||
totalVotes int = len(v.Votes())
|
||||
yesVotes int = len(v.yesVotes())
|
||||
noVotes int = len(v.noVotes())
|
||||
deadlineStatus string = "🎭 ONGOING 🎭"
|
||||
quorumStatus string = "❌ FAIL"
|
||||
thresholdStatus string = "❌ FAIL"
|
||||
out string = ""
|
||||
)
|
||||
var (
|
||||
possibleVotes int = len(v.electors)
|
||||
totalVotes int = len(v.Votes())
|
||||
yesVotes int = len(v.yesVotes())
|
||||
noVotes int = len(v.noVotes())
|
||||
deadlineStatus string = "🎭 ONGOING 🎭"
|
||||
quorumStatus string = "❌ FAIL"
|
||||
thresholdStatus string = "❌ FAIL"
|
||||
out string = ""
|
||||
)
|
||||
|
||||
quorumSatisfied := v.quorum.IsSatisfied(possibleVotes, totalVotes)
|
||||
thresholdSatisfied := v.threshold.IsSatisfied(totalVotes, yesVotes, noVotes)
|
||||
votingSatisfied := quorumSatisfied && thresholdSatisfied
|
||||
quorumSatisfied := v.quorum.IsSatisfied(possibleVotes, totalVotes)
|
||||
thresholdSatisfied := v.threshold.IsSatisfied(totalVotes, yesVotes, noVotes)
|
||||
votingSatisfied := quorumSatisfied && thresholdSatisfied
|
||||
|
||||
if time.Now().UTC().After(v.deadline) {
|
||||
if votingSatisfied {
|
||||
deadlineStatus = "✅ APROVED ✅"
|
||||
} else {
|
||||
deadlineStatus = "❌ REJECTED ❌"
|
||||
}
|
||||
}
|
||||
if v.quorum.IsSatisfied(possibleVotes, totalVotes) {
|
||||
quorumStatus = "✅ PASS"
|
||||
}
|
||||
if v.threshold.IsSatisfied(totalVotes, yesVotes, noVotes) {
|
||||
thresholdStatus = "✅ PASS"
|
||||
}
|
||||
if time.Now().UTC().After(v.deadline) {
|
||||
if votingSatisfied {
|
||||
deadlineStatus = "✅ APROVED ✅"
|
||||
} else {
|
||||
deadlineStatus = "❌ REJECTED ❌"
|
||||
}
|
||||
}
|
||||
if v.quorum.IsSatisfied(possibleVotes, totalVotes) {
|
||||
quorumStatus = "✅ PASS"
|
||||
}
|
||||
if v.threshold.IsSatisfied(totalVotes, yesVotes, noVotes) {
|
||||
thresholdStatus = "✅ PASS"
|
||||
}
|
||||
|
||||
out += fmt.Sprintf("Referendum: %s\n", strings.ToUpper(v.referendum))
|
||||
out += fmt.Sprintf("Deadline : %s UTC\n", v.deadline.Format(time.DateTime))
|
||||
out += fmt.Sprintf("Quorum : %s (%d/%d) (required: %s)\n", quorumStatus, totalVotes, possibleVotes, v.quorum)
|
||||
out += fmt.Sprintf("Threshold : %s (%d/%d) (required: %s)\n", thresholdStatus, yesVotes, totalVotes, v.threshold)
|
||||
out += fmt.Sprintf("Electors : [ %d ] %s\n", len(v.electors), v.electors)
|
||||
out += fmt.Sprintf(
|
||||
"Votes : [ %d | %d | %d ] (❎|❌|❔)\n",
|
||||
len(v.yesVotes()),
|
||||
len(v.noVotes()),
|
||||
len(v.abstainVotes()),
|
||||
)
|
||||
out += fmt.Sprintf("Status : %s\n", deadlineStatus)
|
||||
out += fmt.Sprintf("Quorum : %s (%d/%d) (required: %s)\n", quorumStatus, totalVotes, possibleVotes, v.quorum)
|
||||
out += fmt.Sprintf("Threshold : %s (%d/%d) (required: %s)\n", thresholdStatus, yesVotes, totalVotes, v.threshold)
|
||||
out += fmt.Sprintf("Electors : [ %d ] %s\n", len(v.electors), v.electors)
|
||||
out += fmt.Sprintf(
|
||||
"Votes : [ %d | %d | %d ] (❎|❌|❔)\n",
|
||||
len(v.yesVotes()),
|
||||
len(v.noVotes()),
|
||||
len(v.abstainVotes()),
|
||||
)
|
||||
out += fmt.Sprintf("Status : %s\n", deadlineStatus)
|
||||
|
||||
if !v.annonymous {
|
||||
out += "\n"
|
||||
if v.votes != nil && len(v.votes) > 0 {
|
||||
for _, _v := range v.votes {
|
||||
out += fmt.Sprintf("💬 %s\n", _v)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !v.annonymous {
|
||||
out += "\n"
|
||||
if v.votes != nil && len(v.votes) > 0 {
|
||||
for _, _v := range v.votes {
|
||||
out += fmt.Sprintf("💬 %s\n", _v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (v Voting) ID() string {
|
||||
return v.id
|
||||
return v.id
|
||||
}
|
||||
|
||||
func (v Voting) Referendum() string {
|
||||
|
@ -109,15 +109,15 @@ func (v Voting) Referendum() string {
|
|||
}
|
||||
|
||||
func (v Voting) Deadline() time.Time {
|
||||
return v.deadline
|
||||
return v.deadline
|
||||
}
|
||||
|
||||
func (v Voting) Quorum() string {
|
||||
return v.quorum.String()
|
||||
return v.quorum.String()
|
||||
}
|
||||
|
||||
func (v Voting) Threshold() string {
|
||||
return v.threshold.String()
|
||||
return v.threshold.String()
|
||||
}
|
||||
|
||||
func (v Voting) Electors() []string {
|
||||
|
@ -129,75 +129,74 @@ func (v Voting) Electors() []string {
|
|||
}
|
||||
|
||||
func (v Voting) IsOpen() bool {
|
||||
return v.deadline.After(time.Now().UTC())
|
||||
return v.deadline.After(time.Now().UTC())
|
||||
}
|
||||
|
||||
func (v Voting) Result() Result {
|
||||
var (
|
||||
possibleVotes = len(v.electors)
|
||||
totalVotes = len(v.Votes())
|
||||
yesVotes = len(v.yesVotes())
|
||||
noVotes = len(v.noVotes())
|
||||
votes []vote.Vote
|
||||
var (
|
||||
possibleVotes = len(v.electors)
|
||||
totalVotes = len(v.Votes())
|
||||
yesVotes = len(v.yesVotes())
|
||||
noVotes = len(v.noVotes())
|
||||
votes []vote.Vote
|
||||
|
||||
quorumSatisfied = v.quorum.IsSatisfied(possibleVotes, totalVotes)
|
||||
thresholdSatisfied = v.threshold.IsSatisfied(totalVotes, yesVotes, noVotes)
|
||||
)
|
||||
quorumSatisfied = v.quorum.IsSatisfied(possibleVotes, totalVotes)
|
||||
thresholdSatisfied = v.threshold.IsSatisfied(totalVotes, yesVotes, noVotes)
|
||||
)
|
||||
|
||||
if !v.annonymous {
|
||||
votes = v.Votes()
|
||||
}
|
||||
if !v.annonymous {
|
||||
votes = v.Votes()
|
||||
}
|
||||
|
||||
return Result{
|
||||
Quorum: quorumSatisfied,
|
||||
Threshold: thresholdSatisfied,
|
||||
Yes: len(v.yesVotes()),
|
||||
No: len(v.noVotes()),
|
||||
Abstain: len(v.Votes()) - len(v.yesVotes()) - len(v.noVotes()),
|
||||
Votes: votes,
|
||||
}
|
||||
return Result{
|
||||
Quorum: quorumSatisfied,
|
||||
Threshold: thresholdSatisfied,
|
||||
Yes: len(v.yesVotes()),
|
||||
No: len(v.noVotes()),
|
||||
Abstain: len(v.Votes()) - len(v.yesVotes()) - len(v.noVotes()),
|
||||
Votes: votes,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Voting) Vote(vote vote.Vote) error {
|
||||
if time.Now().UTC().After(v.deadline) {
|
||||
return fmt.Errorf("deadline has passed")
|
||||
}
|
||||
for _, elector := range v.electors {
|
||||
if elector == vote.Elector {
|
||||
v.votes = append(v.votes, vote)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("not eligable to vote: %s", vote.Elector)
|
||||
if time.Now().UTC().After(v.deadline) {
|
||||
return fmt.Errorf("deadline has passed")
|
||||
}
|
||||
for _, elector := range v.electors {
|
||||
if elector == vote.Elector {
|
||||
v.votes = append(v.votes, vote)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("not eligable to vote: %s", vote.Elector)
|
||||
}
|
||||
|
||||
func (v Voting) Votes() []vote.Vote {
|
||||
votes := []vote.Vote{}
|
||||
nextVote:
|
||||
for i := len(v.votes)-1; i >= 0; i-- {
|
||||
elector := v.votes[i].Elector
|
||||
for _, e := range votes {
|
||||
if e.Elector == elector {
|
||||
continue nextVote
|
||||
}
|
||||
}
|
||||
votes = append(votes, v.votes[i])
|
||||
}
|
||||
for i := len(v.votes) - 1; i >= 0; i-- {
|
||||
elector := v.votes[i].Elector
|
||||
for _, e := range votes {
|
||||
if e.Elector == elector {
|
||||
continue nextVote
|
||||
}
|
||||
}
|
||||
votes = append(votes, v.votes[i])
|
||||
}
|
||||
return votes
|
||||
}
|
||||
|
||||
func (v Voting) yesVotes() []vote.Vote {
|
||||
filterFunc := func(_v vote.Vote) bool { return _v.Choice == vote.Yes }
|
||||
return filter.Choose(v.Votes(), filterFunc).([]vote.Vote)
|
||||
filterFunc := func(_v vote.Vote) bool { return _v.Choice == vote.Yes }
|
||||
return filter.Choose(v.Votes(), filterFunc).([]vote.Vote)
|
||||
}
|
||||
|
||||
func (v Voting) noVotes() []vote.Vote {
|
||||
filterFunc := func(_v vote.Vote) bool { return _v.Choice == vote.No }
|
||||
return filter.Choose(v.Votes(), filterFunc).([]vote.Vote)
|
||||
filterFunc := func(_v vote.Vote) bool { return _v.Choice == vote.No }
|
||||
return filter.Choose(v.Votes(), filterFunc).([]vote.Vote)
|
||||
}
|
||||
|
||||
func (v Voting) abstainVotes() []vote.Vote {
|
||||
filterFunc := func(_v vote.Vote) bool { return _v.Choice == vote.Abstain }
|
||||
return filter.Choose(v.Votes(), filterFunc).([]vote.Vote)
|
||||
filterFunc := func(_v vote.Vote) bool { return _v.Choice == vote.Abstain }
|
||||
return filter.Choose(v.Votes(), filterFunc).([]vote.Vote)
|
||||
}
|
||||
|
||||
|
|
|
@ -8,96 +8,96 @@ import (
|
|||
type Quorum uint8
|
||||
|
||||
const (
|
||||
Simple Quorum = iota
|
||||
OneFifth
|
||||
OneQuarter
|
||||
OneThird
|
||||
OneHalf
|
||||
TwoFifths
|
||||
TwoThirds
|
||||
ThreeQuarters
|
||||
ThreeFifths
|
||||
FourFifths
|
||||
Unanimous
|
||||
Simple Quorum = iota
|
||||
OneFifth
|
||||
OneQuarter
|
||||
OneThird
|
||||
OneHalf
|
||||
TwoFifths
|
||||
TwoThirds
|
||||
ThreeQuarters
|
||||
ThreeFifths
|
||||
FourFifths
|
||||
Unanimous
|
||||
)
|
||||
|
||||
func ValidQuorums() []Quorum {
|
||||
return []Quorum{
|
||||
Simple,
|
||||
OneFifth, OneQuarter, OneThird, OneHalf,
|
||||
TwoThirds, TwoFifths,
|
||||
ThreeQuarters, ThreeFifths,
|
||||
FourFifths,
|
||||
}
|
||||
return []Quorum{
|
||||
Simple,
|
||||
OneFifth, OneQuarter, OneThird, OneHalf,
|
||||
TwoThirds, TwoFifths,
|
||||
ThreeQuarters, ThreeFifths,
|
||||
FourFifths,
|
||||
}
|
||||
}
|
||||
|
||||
func FromString(s string) (Quorum, error) {
|
||||
for _, q := range ValidQuorums() {
|
||||
if strings.ToUpper(q.String()) == strings.ToUpper(s) {
|
||||
return q, nil
|
||||
}
|
||||
}
|
||||
return Simple, fmt.Errorf("inalid quorum: %s", s)
|
||||
for _, q := range ValidQuorums() {
|
||||
if strings.ToUpper(q.String()) == strings.ToUpper(s) {
|
||||
return q, nil
|
||||
}
|
||||
}
|
||||
return Simple, fmt.Errorf("inalid quorum: %s", s)
|
||||
}
|
||||
|
||||
func (q Quorum) String() string {
|
||||
switch q {
|
||||
case Simple:
|
||||
return "SIMPLE"
|
||||
case OneFifth:
|
||||
return "1/5"
|
||||
case OneQuarter:
|
||||
return "1/4"
|
||||
case OneThird:
|
||||
return "1/3"
|
||||
case OneHalf:
|
||||
return "1/2"
|
||||
case TwoThirds:
|
||||
return "2/3"
|
||||
case TwoFifths:
|
||||
return "2/5"
|
||||
case ThreeQuarters:
|
||||
return "3/4"
|
||||
case ThreeFifths:
|
||||
return "3/5"
|
||||
case FourFifths:
|
||||
return "4/5"
|
||||
case Unanimous:
|
||||
return "ALL"
|
||||
default:
|
||||
panic("this code should never be reached")
|
||||
}
|
||||
switch q {
|
||||
case Simple:
|
||||
return "SIMPLE"
|
||||
case OneFifth:
|
||||
return "1/5"
|
||||
case OneQuarter:
|
||||
return "1/4"
|
||||
case OneThird:
|
||||
return "1/3"
|
||||
case OneHalf:
|
||||
return "1/2"
|
||||
case TwoThirds:
|
||||
return "2/3"
|
||||
case TwoFifths:
|
||||
return "2/5"
|
||||
case ThreeQuarters:
|
||||
return "3/4"
|
||||
case ThreeFifths:
|
||||
return "3/5"
|
||||
case FourFifths:
|
||||
return "4/5"
|
||||
case Unanimous:
|
||||
return "ALL"
|
||||
default:
|
||||
panic("this code should never be reached")
|
||||
}
|
||||
}
|
||||
|
||||
func (q Quorum) IsSatisfied(possibleVotes, totalVotes int) bool {
|
||||
if totalVotes == 0 {
|
||||
return false
|
||||
}
|
||||
switch q {
|
||||
case Simple:
|
||||
return true
|
||||
case OneFifth:
|
||||
return totalVotes * 5 >= possibleVotes
|
||||
case OneQuarter:
|
||||
return totalVotes * 4 >= possibleVotes
|
||||
case OneThird:
|
||||
return totalVotes * 3 >= possibleVotes
|
||||
case OneHalf:
|
||||
return totalVotes * 2 >= possibleVotes
|
||||
case TwoThirds:
|
||||
return totalVotes * 3 >= possibleVotes * 2
|
||||
case TwoFifths:
|
||||
return totalVotes * 5 >= possibleVotes * 2
|
||||
case ThreeQuarters:
|
||||
return totalVotes * 4 >= possibleVotes * 3
|
||||
case ThreeFifths:
|
||||
return totalVotes * 5 >= possibleVotes * 3
|
||||
case FourFifths:
|
||||
return totalVotes * 5 >= possibleVotes * 4
|
||||
case Unanimous:
|
||||
return totalVotes >= possibleVotes
|
||||
default:
|
||||
panic("this code should never be reached⚜️")
|
||||
}
|
||||
return false
|
||||
if totalVotes == 0 {
|
||||
return false
|
||||
}
|
||||
switch q {
|
||||
case Simple:
|
||||
return true
|
||||
case OneFifth:
|
||||
return totalVotes*5 >= possibleVotes
|
||||
case OneQuarter:
|
||||
return totalVotes*4 >= possibleVotes
|
||||
case OneThird:
|
||||
return totalVotes*3 >= possibleVotes
|
||||
case OneHalf:
|
||||
return totalVotes*2 >= possibleVotes
|
||||
case TwoThirds:
|
||||
return totalVotes*3 >= possibleVotes*2
|
||||
case TwoFifths:
|
||||
return totalVotes*5 >= possibleVotes*2
|
||||
case ThreeQuarters:
|
||||
return totalVotes*4 >= possibleVotes*3
|
||||
case ThreeFifths:
|
||||
return totalVotes*5 >= possibleVotes*3
|
||||
case FourFifths:
|
||||
return totalVotes*5 >= possibleVotes*4
|
||||
case Unanimous:
|
||||
return totalVotes >= possibleVotes
|
||||
default:
|
||||
panic("this code should never be reached⚜️")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ package voting
|
|||
import "code.c-base.org/baccenfutter/govote/voting/vote"
|
||||
|
||||
type Result struct {
|
||||
Quorum bool
|
||||
Threshold bool
|
||||
Yes, No, Abstain int
|
||||
Votes []vote.Vote
|
||||
Quorum bool
|
||||
Threshold bool
|
||||
Yes, No, Abstain int
|
||||
Votes []vote.Vote
|
||||
}
|
||||
|
|
|
@ -8,95 +8,95 @@ import (
|
|||
type Threshold uint8
|
||||
|
||||
const (
|
||||
Simple Threshold = iota
|
||||
OneFifth
|
||||
OneQuarter
|
||||
OneThird
|
||||
OneHalf
|
||||
TwoThirds
|
||||
TwoFifths
|
||||
ThreeQuarters
|
||||
ThreeFifths
|
||||
FourFifths
|
||||
Unanimous
|
||||
Simple Threshold = iota
|
||||
OneFifth
|
||||
OneQuarter
|
||||
OneThird
|
||||
OneHalf
|
||||
TwoThirds
|
||||
TwoFifths
|
||||
ThreeQuarters
|
||||
ThreeFifths
|
||||
FourFifths
|
||||
Unanimous
|
||||
)
|
||||
|
||||
func ValidThresholds() []Threshold {
|
||||
return []Threshold{
|
||||
Simple,
|
||||
OneFifth, OneQuarter, OneThird, OneHalf,
|
||||
TwoFifths, TwoThirds,
|
||||
ThreeFifths, ThreeQuarters, ThreeFifths,
|
||||
FourFifths,
|
||||
}
|
||||
return []Threshold{
|
||||
Simple,
|
||||
OneFifth, OneQuarter, OneThird, OneHalf,
|
||||
TwoFifths, TwoThirds,
|
||||
ThreeFifths, ThreeQuarters, ThreeFifths,
|
||||
FourFifths,
|
||||
}
|
||||
}
|
||||
|
||||
func FromString(s string) (Threshold, error) {
|
||||
for _, t := range ValidThresholds() {
|
||||
if strings.ToUpper(t.String()) == strings.ToUpper(s) {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
return Simple, fmt.Errorf("invalid threshold: %s", s)
|
||||
for _, t := range ValidThresholds() {
|
||||
if strings.ToUpper(t.String()) == strings.ToUpper(s) {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
return Simple, fmt.Errorf("invalid threshold: %s", s)
|
||||
}
|
||||
|
||||
func (t Threshold) String() string {
|
||||
switch t {
|
||||
case Simple:
|
||||
return "SIMPLE"
|
||||
case OneFifth:
|
||||
return "1/5"
|
||||
case OneQuarter:
|
||||
return "1/4"
|
||||
case OneThird:
|
||||
return "1/3"
|
||||
case OneHalf:
|
||||
return "1/2"
|
||||
case TwoThirds:
|
||||
return "2/3"
|
||||
case TwoFifths:
|
||||
return "2/5"
|
||||
case ThreeQuarters:
|
||||
return "3/4"
|
||||
case ThreeFifths:
|
||||
return "3/5"
|
||||
case FourFifths:
|
||||
return "4/5"
|
||||
case Unanimous:
|
||||
return "ALL"
|
||||
default:
|
||||
panic("this code should never be reached")
|
||||
}
|
||||
switch t {
|
||||
case Simple:
|
||||
return "SIMPLE"
|
||||
case OneFifth:
|
||||
return "1/5"
|
||||
case OneQuarter:
|
||||
return "1/4"
|
||||
case OneThird:
|
||||
return "1/3"
|
||||
case OneHalf:
|
||||
return "1/2"
|
||||
case TwoThirds:
|
||||
return "2/3"
|
||||
case TwoFifths:
|
||||
return "2/5"
|
||||
case ThreeQuarters:
|
||||
return "3/4"
|
||||
case ThreeFifths:
|
||||
return "3/5"
|
||||
case FourFifths:
|
||||
return "4/5"
|
||||
case Unanimous:
|
||||
return "ALL"
|
||||
default:
|
||||
panic("this code should never be reached")
|
||||
}
|
||||
}
|
||||
|
||||
func (t Threshold) IsSatisfied(totalVotes, yesVotes, noVotes int) bool {
|
||||
if totalVotes == 0 {
|
||||
return false
|
||||
}
|
||||
switch t {
|
||||
case Simple:
|
||||
return yesVotes > noVotes
|
||||
case OneFifth:
|
||||
return yesVotes * 5 >= totalVotes
|
||||
case OneQuarter:
|
||||
return yesVotes * 4 >= totalVotes
|
||||
case OneThird:
|
||||
return yesVotes * 3 >= totalVotes
|
||||
case OneHalf:
|
||||
return yesVotes * 2 >= totalVotes
|
||||
case TwoThirds:
|
||||
return yesVotes * 3 >= totalVotes * 2
|
||||
case TwoFifths:
|
||||
return yesVotes * 5 >= totalVotes * 2
|
||||
case ThreeQuarters:
|
||||
return yesVotes * 4 >= totalVotes * 3
|
||||
case ThreeFifths:
|
||||
return yesVotes * 5 >= totalVotes * 3
|
||||
case FourFifths:
|
||||
return yesVotes * 5 >= totalVotes * 4
|
||||
case Unanimous:
|
||||
return yesVotes >= totalVotes
|
||||
default:
|
||||
panic("this code should never be reached")
|
||||
}
|
||||
if totalVotes == 0 {
|
||||
return false
|
||||
}
|
||||
switch t {
|
||||
case Simple:
|
||||
return yesVotes > noVotes
|
||||
case OneFifth:
|
||||
return yesVotes*5 >= totalVotes
|
||||
case OneQuarter:
|
||||
return yesVotes*4 >= totalVotes
|
||||
case OneThird:
|
||||
return yesVotes*3 >= totalVotes
|
||||
case OneHalf:
|
||||
return yesVotes*2 >= totalVotes
|
||||
case TwoThirds:
|
||||
return yesVotes*3 >= totalVotes*2
|
||||
case TwoFifths:
|
||||
return yesVotes*5 >= totalVotes*2
|
||||
case ThreeQuarters:
|
||||
return yesVotes*4 >= totalVotes*3
|
||||
case ThreeFifths:
|
||||
return yesVotes*5 >= totalVotes*3
|
||||
case FourFifths:
|
||||
return yesVotes*5 >= totalVotes*4
|
||||
case Unanimous:
|
||||
return yesVotes >= totalVotes
|
||||
default:
|
||||
panic("this code should never be reached")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,33 +8,33 @@ import (
|
|||
type Choice int8
|
||||
|
||||
func (choice Choice) String() string {
|
||||
switch choice {
|
||||
case Yes:
|
||||
return "YIP"
|
||||
case No:
|
||||
return "NOPE"
|
||||
case Abstain:
|
||||
return "DUNNO"
|
||||
default:
|
||||
panic("this code should never be reached")
|
||||
}
|
||||
switch choice {
|
||||
case Yes:
|
||||
return "YIP"
|
||||
case No:
|
||||
return "NOPE"
|
||||
case Abstain:
|
||||
return "DUNNO"
|
||||
default:
|
||||
panic("this code should never be reached")
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
Abstain Choice = 0
|
||||
Yes Choice = 1
|
||||
No Choice = -1
|
||||
Abstain Choice = 0
|
||||
Yes Choice = 1
|
||||
No Choice = -1
|
||||
)
|
||||
|
||||
func ValidChoices() []Choice {
|
||||
return []Choice{Yes, No, Abstain}
|
||||
return []Choice{Yes, No, Abstain}
|
||||
}
|
||||
|
||||
func ChoiceFromString(s string) (Choice, error) {
|
||||
for _, c := range ValidChoices() {
|
||||
if strings.ToUpper(c.String()) == strings.ToUpper(s) {
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
return Abstain, fmt.Errorf("invalid choice: %s", s)
|
||||
for _, c := range ValidChoices() {
|
||||
if strings.ToUpper(c.String()) == strings.ToUpper(s) {
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
return Abstain, fmt.Errorf("invalid choice: %s", s)
|
||||
}
|
||||
|
|
|
@ -6,27 +6,27 @@ import (
|
|||
)
|
||||
|
||||
type Vote struct {
|
||||
Elector string
|
||||
Choice Choice
|
||||
timestamp time.Time
|
||||
Elector string
|
||||
Choice Choice
|
||||
timestamp time.Time
|
||||
}
|
||||
|
||||
func NewVote(elector string, choice Choice) Vote {
|
||||
return Vote{
|
||||
Elector: elector,
|
||||
Choice: choice,
|
||||
timestamp: time.Now().UTC(),
|
||||
}
|
||||
return Vote{
|
||||
Elector: elector,
|
||||
Choice: choice,
|
||||
timestamp: time.Now().UTC(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewVoteWithTimestamp(elector string, choice Choice, timestamp time.Time) Vote {
|
||||
return Vote{
|
||||
Elector: elector,
|
||||
Choice: choice,
|
||||
timestamp: timestamp,
|
||||
}
|
||||
return Vote{
|
||||
Elector: elector,
|
||||
Choice: choice,
|
||||
timestamp: timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
func (vote Vote) String() string {
|
||||
return fmt.Sprintf("%s %s %s", vote.timestamp.Format(time.DateTime), vote.Choice, vote.Elector)
|
||||
return fmt.Sprintf("%s %s %s", vote.timestamp.Format(time.DateTime), vote.Choice, vote.Elector)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue