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