summaryrefslogtreecommitdiff
path: root/models.go
diff options
context:
space:
mode:
authorJan Dittberner <jan@dittberner.info>2017-04-20 11:35:33 +0200
committerJan Dittberner <jan@dittberner.info>2017-04-22 00:12:38 +0200
commitdcdd5f715f4800d02b04841054342ea2e44d950e (patch)
treee082c9ad6ec2e9964a9f3c6e6f694264ab76d329 /models.go
parent2de96dc13dc5e2843ebe204a7e1dfe6197ab7de0 (diff)
downloadcacert-boardvoting-dcdd5f715f4800d02b04841054342ea2e44d950e.tar.gz
cacert-boardvoting-dcdd5f715f4800d02b04841054342ea2e44d950e.tar.xz
cacert-boardvoting-dcdd5f715f4800d02b04841054342ea2e44d950e.zip
Implement decision closing job
Diffstat (limited to 'models.go')
-rw-r--r--models.go97
1 files changed, 75 insertions, 22 deletions
diff --git a/models.go b/models.go
index 5df7d95..d27610f 100644
--- a/models.go
+++ b/models.go
@@ -2,6 +2,7 @@ package main
import (
"database/sql"
+ "fmt"
"github.com/jmoiron/sqlx"
"time"
)
@@ -22,6 +23,7 @@ const (
sqlUpdateDecision
sqlUpdateDecisionStatus
sqlSelectClosableDecisions
+ sqlGetNextPendingDecisionDue
)
var sqlStatements = map[sqlKey]string{
@@ -105,11 +107,13 @@ UPDATE decisions
SET status=:status, modified=:modified WHERE id=:id
`,
sqlSelectClosableDecisions: `
-SELECT decisions.id, decisions.tag, decisions.proponent, voters.name AS proposer, decisions.proposed, decisions.title,
- decisions.content, decisions.votetype, decisions.status, decisions.due, decisions.modified
+SELECT decisions.id, decisions.tag, decisions.proponent, decisions.proposed,
+ decisions.title, decisions.content, decisions.votetype, decisions.status,
+ decisions.due, decisions.modified
FROM decisions
-JOIN voters ON decisions.proponent=voters.id
WHERE decisions.status=0 AND :now > due`,
+ sqlGetNextPendingDecisionDue: `
+SELECT due FROM decisions WHERE status=0 ORDER BY due LIMIT 1`,
}
var db *sqlx.DB
@@ -241,6 +245,18 @@ func (v *VoteSums) VoteCount() int {
return v.Ayes + v.Nayes + v.Abstains
}
+func (v *VoteSums) TotalVotes() int {
+ return v.Ayes + v.Nayes
+}
+
+func (v *VoteSums) Percent() int {
+ totalVotes := v.TotalVotes()
+ if totalVotes == 0 {
+ return 0
+ }
+ return v.Ayes * 100 / totalVotes
+}
+
type VoteForDisplay struct {
Vote
Name string
@@ -413,6 +429,7 @@ func (d *Decision) Create() (err error) {
logger.Println("Error getting id of inserted motion:", err)
return
}
+ rescheduleChannel <- JobIdCloseDecisions
getDecisionStmt, err := db.Preparex(sqlStatements[sqlLoadDecisionById])
if err != nil {
@@ -465,6 +482,7 @@ func (d *Decision) Update() (err error) {
} else if affectedRows != 1 {
logger.Printf("WARNING wrong number of affected rows: %d (1 expected)\n", affectedRows)
}
+ rescheduleChannel <- JobIdCloseDecisions
err = d.LoadWithId()
return
@@ -486,14 +504,20 @@ func (d *Decision) UpdateStatus() (err error) {
affectedRows, err := result.RowsAffected()
if err != nil {
logger.Print("Problem determining the affected rows")
+ return
} else if affectedRows != 1 {
logger.Printf("WARNING wrong number of affected rows: %d (1 expected)\n", affectedRows)
}
+ rescheduleChannel <- JobIdCloseDecisions
err = d.LoadWithId()
return
}
+func (d *Decision) String() string {
+ return fmt.Sprintf("%s %s (Id %d)", d.Tag, d.Title, d.Id)
+}
+
func FindVoterByAddress(emailAddress string) (voter *Voter, err error) {
findVoterStmt, err := db.Preparex(sqlStatements[sqlLoadEnabledVoterByEmail])
if err != nil {
@@ -515,13 +539,6 @@ func FindVoterByAddress(emailAddress string) (voter *Voter, err error) {
}
func (d *Decision) Close() (err error) {
- closeDecisionStmt, err := db.PrepareNamed(sqlStatements[sqlUpdateDecisionStatus])
- if err != nil {
- logger.Println("Error preparing statement:", err)
- return
- }
- defer closeDecisionStmt.Close()
-
quorum, majority := d.VoteType.QuorumAndMajority()
voteSums, err := d.VoteSums()
@@ -535,7 +552,7 @@ func (d *Decision) Close() (err error) {
if votes < quorum {
d.Status = voteStatusDeclined
} else {
- votes = voteSums.Ayes + voteSums.Nayes
+ votes = voteSums.TotalVotes()
if (voteSums.Ayes / votes) > (majority / 100) {
d.Status = voteStatusApproved
} else {
@@ -543,6 +560,13 @@ func (d *Decision) Close() (err error) {
}
}
+ closeDecisionStmt, err := db.PrepareNamed(sqlStatements[sqlUpdateDecisionStatus])
+ if err != nil {
+ logger.Println("Error preparing statement:", err)
+ return
+ }
+ defer closeDecisionStmt.Close()
+
result, err := closeDecisionStmt.Exec(d)
if err != nil {
logger.Println("Error closing vote:", err)
@@ -562,32 +586,61 @@ func (d *Decision) Close() (err error) {
}
func CloseDecisions() (err error) {
- getClosedDecisionsStmt, err := db.PrepareNamed(sqlStatements[sqlSelectClosableDecisions])
+ getClosableDecisionsStmt, err := db.PrepareNamed(sqlStatements[sqlSelectClosableDecisions])
if err != nil {
logger.Println("Error preparing statement:", err)
return
}
- defer getClosedDecisionsStmt.Close()
+ defer getClosableDecisionsStmt.Close()
- params := make(map[string]interface{}, 1)
- params["now"] = time.Now().UTC()
- rows, err := getClosedDecisionsStmt.Queryx(params)
+ decisions := make([]*Decision, 0)
+ rows, err := getClosableDecisionsStmt.Queryx(struct{ Now time.Time }{time.Now().UTC()})
if err != nil {
- logger.Println("Error fetching closed decisions", err)
+ logger.Println("Error fetching closable decisions", err)
return
}
defer rows.Close()
for rows.Next() {
- d := &Decision{}
- if err = rows.StructScan(d); err != nil {
- logger.Println("Error filling decision from database row", err)
+ decision := &Decision{}
+ if err = rows.StructScan(decision); err != nil {
+ logger.Println("Error scanning row", err)
return
}
- if err = d.Close(); err != nil {
- logger.Printf("Error closing decision %s: %s\n", d.Tag, err)
+ decisions = append(decisions, decision)
+ }
+ rows.Close()
+
+ for _, decision := range decisions {
+ logger.Println("DEBUG found closable decision", decision)
+ if err = decision.Close(); err != nil {
+ logger.Printf("Error closing decision %s: %s\n", decision, err)
return
}
}
return
}
+
+func GetNextPendingDecisionDue() (due *time.Time, err error) {
+ getNextPendingDecisionDueStmt, err := db.Preparex(sqlStatements[sqlGetNextPendingDecisionDue])
+ if err != nil {
+ logger.Println("Error preparing statement:", err)
+ return
+ }
+ defer getNextPendingDecisionDueStmt.Close()
+
+ row := getNextPendingDecisionDueStmt.QueryRow()
+
+ var dueTimestamp time.Time
+ if err = row.Scan(&dueTimestamp); err != nil {
+ if err == sql.ErrNoRows {
+ logger.Println("DEBUG No pending decisions")
+ return nil, nil
+ }
+ logger.Println("Error parsing result", err)
+ return
+ }
+ due = &dueTimestamp
+
+ return
+}