diff --git a/cmd/main.go b/cmd/main.go index 54f7648..9b556be 100644 --- a/cmd/main.go +++ b/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, + }, } diff --git a/cmd/new.go b/cmd/new.go index ebf96c3..e128c32 100644 --- a/cmd/new.go +++ b/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 + }, } diff --git a/cmd/serve.go b/cmd/serve.go index 94a635b..265a7a9 100644 --- a/cmd/serve.go +++ b/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")) + }, } diff --git a/cmd/show.go b/cmd/show.go index 83f4bf1..64ab971 100644 --- a/cmd/show.go +++ b/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 + }, } diff --git a/cmd/vote.go b/cmd/vote.go index eb76919..5189320 100644 --- a/cmd/vote.go +++ b/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 + }, } diff --git a/go.mod b/go.mod index 85c781e..44bcf44 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 8d3fcba..89cf1a4 100644 --- a/go.sum +++ b/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= diff --git a/http/main.go b/http/main.go index 98865f7..1ef2903 100644 --- a/http/main.go +++ b/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") - - 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) + NewTemplateRenderer(e, "tmpl/*.html") - 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 { - 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 } diff --git a/http/template_renderer.go b/http/template_renderer.go index 148a963..707306b 100644 --- a/http/template_renderer.go +++ b/http/template_renderer.go @@ -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, + } } diff --git a/main.go b/main.go index d6d28be..27695a1 100644 --- a/main.go +++ b/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) + } } diff --git a/store/db.go b/store/db.go index d57126a..fdf4e97 100644 --- a/store/db.go +++ b/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) } - diff --git a/store/handler.go b/store/handler.go index c2b1f44..5649b3c 100644 --- a/store/handler.go +++ b/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 - } - - if d, err = time.Parse("2006-01-02 15:04:05 -0700 MST", dbDeadline); 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 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 d, err = time.Parse("2006-01-02 15:04:05 -0700 MST", dbDeadline); err != nil { + return nil, err + } - votes, err := getVotes(id) - if err != nil { - return nil, err - } - v := voting.NewVotingWithVotes(id, r, d, q, t, e, a, votes) - return v, nil + 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 } 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 } diff --git a/store/prepared.go b/store/prepared.go index c90cfb5..5286dbb 100644 --- a/store/prepared.go +++ b/store/prepared.go @@ -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) + } } diff --git a/utils/random.go b/utils/random.go index 399c6d1..22a7c0d 100644 --- a/utils/random.go +++ b/utils/random.go @@ -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) } diff --git a/voting/main.go b/voting/main.go index 092319a..705f26d 100644 --- a/voting/main.go +++ b/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 = "" - ) - - quorumSatisfied := v.quorum.IsSatisfied(possibleVotes, totalVotes) - thresholdSatisfied := v.threshold.IsSatisfied(totalVotes, yesVotes, noVotes) - votingSatisfied := quorumSatisfied && thresholdSatisfied + 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 = "" + ) - 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" - } + 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" + } 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) } - diff --git a/voting/quorum/quorum.go b/voting/quorum/quorum.go index 341d4ad..729594d 100644 --- a/voting/quorum/quorum.go +++ b/voting/quorum/quorum.go @@ -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 } diff --git a/voting/result.go b/voting/result.go index 12f9190..75b14f6 100644 --- a/voting/result.go +++ b/voting/result.go @@ -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 } diff --git a/voting/threshold/threshold.go b/voting/threshold/threshold.go index 9559705..fdee948 100644 --- a/voting/threshold/threshold.go +++ b/voting/threshold/threshold.go @@ -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") + } } diff --git a/voting/vote/choice.go b/voting/vote/choice.go index bd680ab..e87b991 100644 --- a/voting/vote/choice.go +++ b/voting/vote/choice.go @@ -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) } diff --git a/voting/vote/vote.go b/voting/vote/vote.go index bc70310..7a8dad5 100644 --- a/voting/vote/vote.go +++ b/voting/vote/vote.go @@ -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) }