diff --git a/cmd/boardvoting/main.go b/cmd/boardvoting/main.go index 1d28b47..73f26df 100644 --- a/cmd/boardvoting/main.go +++ b/cmd/boardvoting/main.go @@ -155,6 +155,14 @@ func setupFormDecoder() *form.Decoder { return v, nil }, new(models.VoteType)) + decoder.RegisterCustomTypeFunc(func(values []string) (interface{}, error) { + v, err := models.VoteChoiceFromString(values[0]) + if err != nil { + return nil, fmt.Errorf("could not convert value %s: %w", values[0], err) + } + + return v, nil + }, new(models.VoteChoice)) return decoder } diff --git a/internal/models/motions.go b/internal/models/motions.go index 5e9dfdb..496c6c9 100644 --- a/internal/models/motions.go +++ b/internal/models/motions.go @@ -101,50 +101,109 @@ func (v *VoteType) QuorumAndMajority() (int, float32) { return quorumDefault, majorityDefault } -type VoteStatus int8 +type VoteStatus struct { + Label string + Id int8 +} -const ( - voteStatusDeclined VoteStatus = -1 - voteStatusPending VoteStatus = 0 - voteStatusApproved VoteStatus = 1 - voteStatusWithdrawn VoteStatus = -2 +var ( + voteStatusDeclined = &VoteStatus{Label: "declined", Id: -1} + voteStatusPending = &VoteStatus{Label: "pending", Id: 0} + voteStatusApproved = &VoteStatus{Label: "approved", Id: 1} + voteStatusWithdrawn = &VoteStatus{Label: "withdrawn", Id: -2} ) -var voteStatusLabels = map[VoteStatus]string{ - voteStatusDeclined: "declined", - voteStatusPending: "pending", - voteStatusApproved: "approved", - voteStatusWithdrawn: "withdrawn", -} - -func (v VoteStatus) String() string { - if label, ok := voteStatusLabels[v]; ok { - return label +func VoteStatusFromInt(id int64) (*VoteStatus, error) { + for _, vs := range []*VoteStatus{voteStatusPending, voteStatusApproved, voteStatusWithdrawn, voteStatusDeclined} { + if int64(vs.Id) == id { + return vs, nil + } } - return unknownVariant + return nil, fmt.Errorf("unknown vote status id %d", id) } -type VoteChoice int +func (v *VoteStatus) String() string { + return v.Label +} -const ( - voteAye VoteChoice = 1 - voteNaye VoteChoice = -1 - voteAbstain VoteChoice = 0 +func (v *VoteStatus) Scan(src any) error { + value, ok := src.(int64) + if !ok { + return fmt.Errorf("could not cast %v of %T to uint8", src, src) + } + + vs, err := VoteStatusFromInt(value) + if err != nil { + return err + } + + *v = *vs + + return nil +} + +func (v *VoteStatus) Value() (driver.Value, error) { + return int64(v.Id), nil +} + +type VoteChoice struct { + label string + id int8 +} + +var ( + VoteAye = &VoteChoice{label: "aye", id: 1} + VoteNaye = &VoteChoice{label: "naye", id: -1} + VoteAbstain = &VoteChoice{label: "abstain", id: 0} ) -var voteChoiceLabels = map[VoteChoice]string{ - voteAye: "aye", - voteNaye: "naye", - voteAbstain: "abstain", -} - -func (v VoteChoice) String() string { - if label, ok := voteChoiceLabels[v]; ok { - return label +func VoteChoiceFromString(label string) (*VoteChoice, error) { + for _, vc := range []*VoteChoice{VoteAye, VoteNaye, VoteAbstain} { + if strings.EqualFold(vc.label, label) { + return vc, nil + } } - return unknownVariant + return nil, fmt.Errorf("unknown vote choice %s", label) +} + +func VoteChoiceFromInt(id int64) (*VoteChoice, error) { + for _, vc := range []*VoteChoice{VoteAye, VoteNaye, VoteAbstain} { + if int64(vc.id) == id { + return vc, nil + } + } + + return nil, fmt.Errorf("unknown vote type id %d", id) +} + +func (v *VoteChoice) String() string { + return v.label +} + +func (v *VoteChoice) Scan(src any) error { + value, ok := src.(int64) + if !ok { + return fmt.Errorf("could not cast %v of %T to uint8", src, src) + } + + vc, err := VoteChoiceFromInt(value) + if err != nil { + return err + } + + *v = *vc + + return nil +} + +func (v *VoteChoice) Value() (driver.Value, error) { + return int64(v.id), nil +} + +func (v *VoteChoice) Equal(other *VoteChoice) bool { + return v.id == other.id } type VoteSums struct { @@ -159,7 +218,7 @@ func (v *VoteSums) TotalVotes() int { return v.Ayes + v.Nayes } -func (v *VoteSums) CalculateResult(quorum int, majority float32) (VoteStatus, string) { +func (v *VoteSums) CalculateResult(quorum int, majority float32) (*VoteStatus, string) { if v.VoteCount() < quorum { return voteStatusDeclined, fmt.Sprintf("Needed quorum of %d has not been reached.", quorum) } @@ -177,7 +236,7 @@ type Motion struct { Proponent int64 `db:"proponent"` Title string Content string - Status VoteStatus + Status *VoteStatus Due time.Time Modified time.Time Tag string @@ -366,7 +425,7 @@ func (m *MotionModel) SumsForDecision(ctx context.Context, tx *sqlx.Tx, d *Motio for voteRows.Next() { var ( - vote VoteChoice + vote *VoteChoice count int ) @@ -379,11 +438,11 @@ func (m *MotionModel) SumsForDecision(ctx context.Context, tx *sqlx.Tx, d *Motio } switch vote { - case voteAye: + case VoteAye: sums.Ayes = count - case voteNaye: + case VoteNaye: sums.Nayes = count - case voteAbstain: + case VoteAbstain: sums.Abstains = count } } @@ -430,7 +489,7 @@ type MotionForDisplay struct { Title string Content string Type *VoteType `db:"votetype"` - Status VoteStatus + Status *VoteStatus Due time.Time Modified time.Time Sums VoteSums @@ -439,7 +498,7 @@ type MotionForDisplay struct { type VoteForDisplay struct { Name string - Vote VoteChoice + Vote *VoteChoice } type MotionListOptions struct { @@ -580,7 +639,7 @@ func (m *MotionModel) FillVoteSums(ctx context.Context, decisions []*MotionForDi var ( decisionID int64 - vote VoteChoice + vote *VoteChoice count int ) @@ -589,12 +648,12 @@ func (m *MotionModel) FillVoteSums(ctx context.Context, decisions []*MotionForDi return fmt.Errorf("could not scan row: %w", err) } - switch vote { - case voteAye: + switch { + case vote.Equal(VoteAye): decisionMap[decisionID].Sums.Ayes = count - case voteNaye: + case vote.Equal(VoteNaye): decisionMap[decisionID].Sums.Nayes = count - case voteAbstain: + case vote.Equal(VoteAbstain): decisionMap[decisionID].Sums.Abstains = count } } diff --git a/ui/html/base.html b/ui/html/base.html index 15e40f5..ed3c287 100644 --- a/ui/html/base.html +++ b/ui/html/base.html @@ -20,7 +20,7 @@ {{ with .User }}
{{ wrap 76 .Content | nl2br }}
Votes: |
- Aye
+
+ Aye
- {{.Sums.Ayes}}
Naye
+
+ Naye
- {{.Sums.Nayes}}
Abstain
+
diff --git a/ui/html/partials/motion_status_class.html b/ui/html/partials/motion_status_class.html
index 48a0fb5..8330ca9 100644
--- a/ui/html/partials/motion_status_class.html
+++ b/ui/html/partials/motion_status_class.html
@@ -1,3 +1,7 @@
{{ define "motion_status_class" -}}
-{{ if eq . 0 }}blue{{ else if eq . 1 }}green{{ else if eq . -1 }}red{{ else if eq . -2 }}grey{{ end }}
+ {{- if eq .Label "pending" }}blue
+ {{- else if eq .Label "approved" }}green
+ {{- else if eq .Label "declined" }}red
+ {{- else if eq .Label "withdrawn" }}grey
+ {{- end }}
{{- end }}
diff --git a/ui/html/partials/nav.html b/ui/html/partials/nav.html
index 5de3147..8edf31c 100644
--- a/ui/html/partials/nav.html
+++ b/ui/html/partials/nav.html
@@ -20,7 +20,9 @@
{{ end }}
{{ if canStartVote $user }}
{{ end }}
{{ end }}
+ Abstain
{{.Sums.Abstains}}
|