package voting import ( "fmt" "strings" "time" "code.c-base.org/baccenfutter/govote/voting/quorum" "code.c-base.org/baccenfutter/govote/voting/threshold" "code.c-base.org/baccenfutter/govote/voting/vote" "robpike.io/filter" ) type ( Voting struct { id string referendum string deadline time.Time quorum quorum.Quorum threshold threshold.Threshold electors []string 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, referendum: r, 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]) } 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 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) 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 } func (v Voting) Referendum() string { return v.referendum } func (v Voting) Deadline() time.Time { return v.deadline } func (v Voting) Quorum() string { return v.quorum.String() } func (v Voting) Threshold() string { return v.threshold.String() } func (v Voting) Electors() []string { electors := make([]string, len(v.electors)) for i := range v.electors { electors[i] = v.electors[i] } return electors } func (v Voting) IsOpen() bool { 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 quorumSatisfied = v.quorum.IsSatisfied(possibleVotes, totalVotes) thresholdSatisfied = v.threshold.IsSatisfied(totalVotes, yesVotes, noVotes) ) 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, } } 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) } 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]) } 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) } 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) } 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) }