Use loggo for logging
[cacert-boardvoting.git] / models.go
1 package main
2
3 import (
4 "bitbucket.org/liamstask/goose/lib/goose"
5 "database/sql"
6 "fmt"
7 "github.com/jmoiron/sqlx"
8 "time"
9 )
10
11 type sqlKey int
12
13 const (
14 sqlLoadDecisions sqlKey = iota
15 sqlLoadUnvotedDecisions
16 sqlLoadDecisionByTag
17 sqlLoadDecisionById
18 sqlLoadVoteCountsForDecision
19 sqlLoadVotesForDecision
20 sqlLoadEnabledVoterByEmail
21 sqlCountOlderThanDecision
22 sqlCountOlderThanUnvotedDecision
23 sqlCreateDecision
24 sqlUpdateDecision
25 sqlUpdateDecisionStatus
26 sqlSelectClosableDecisions
27 sqlGetNextPendingDecisionDue
28 sqlGetReminderVoters
29 sqlFindUnvotedDecisionsForVoter
30 sqlGetEnabledVoterById
31 sqlCreateVote
32 sqlLoadVote
33 sqlGetVotersForProxy
34 )
35
36 var sqlStatements = map[sqlKey]string{
37 sqlLoadDecisions: `
38 SELECT decisions.id, decisions.tag, decisions.proponent, voters.name AS proposer, decisions.proposed, decisions.title,
39 decisions.content, decisions.votetype, decisions.status, decisions.due, decisions.modified
40 FROM decisions
41 JOIN voters ON decisions.proponent=voters.id
42 ORDER BY proposed DESC
43 LIMIT 10 OFFSET 10 * $1`,
44 sqlLoadUnvotedDecisions: `
45 SELECT decisions.id, decisions.tag, decisions.proponent, voters.name AS proposer, decisions.proposed, decisions.title,
46 decisions.content, decisions.votetype, decisions.status, decisions.due, decisions.modified
47 FROM decisions
48 JOIN voters ON decisions.proponent=voters.id
49 WHERE decisions.status = 0 AND decisions.id NOT IN (SELECT votes.decision FROM votes WHERE votes.voter = $1)
50 ORDER BY proposed DESC
51 LIMIT 10 OFFSET 10 * $2;`,
52 sqlLoadDecisionByTag: `
53 SELECT decisions.id, decisions.tag, decisions.proponent, voters.name AS proposer, decisions.proposed, decisions.title,
54 decisions.content, decisions.votetype, decisions.status, decisions.due, decisions.modified
55 FROM decisions
56 JOIN voters ON decisions.proponent=voters.id
57 WHERE decisions.tag=$1;`,
58 sqlLoadDecisionById: `
59 SELECT decisions.id, decisions.tag, decisions.proponent, decisions.proposed, decisions.title, decisions.content,
60 decisions.votetype, decisions.status, decisions.due, decisions.modified
61 FROM decisions
62 WHERE decisions.id=$1;`,
63 sqlLoadVoteCountsForDecision: `
64 SELECT vote, COUNT(vote) FROM votes WHERE decision=$1 GROUP BY vote`,
65 sqlLoadVotesForDecision: `
66 SELECT votes.decision, votes.voter, voters.name, votes.vote, votes.voted, votes.notes
67 FROM votes
68 JOIN voters ON votes.voter=voters.id
69 WHERE decision=$1`,
70 sqlLoadEnabledVoterByEmail: `
71 SELECT voters.id, voters.name, voters.enabled, voters.reminder
72 FROM voters
73 JOIN emails ON voters.id=emails.voter
74 WHERE emails.address=$1 AND voters.enabled=1`,
75 sqlGetEnabledVoterById: `
76 SELECT id, name, enabled, reminder
77 FROM voters
78 WHERE enabled=1 AND id=$1`,
79 sqlCountOlderThanDecision: `
80 SELECT COUNT(*) > 0 FROM decisions WHERE proposed < $1`,
81 sqlCountOlderThanUnvotedDecision: `
82 SELECT COUNT(*) > 0 FROM decisions
83 WHERE proposed < $1 AND status=0 AND id NOT IN (SELECT decision FROM votes WHERE votes.voter=$2)`,
84 sqlCreateDecision: `
85 INSERT INTO decisions (proposed, proponent, title, content, votetype, status, due, modified,tag)
86 VALUES (
87 :proposed, :proponent, :title, :content, :votetype, 0, :due, :proposed,
88 'm' || strftime('%Y%m%d', :proposed) || '.' || (
89 SELECT COUNT(*)+1 AS num
90 FROM decisions
91 WHERE proposed BETWEEN date(:proposed) AND date(:proposed, '1 day')
92 )
93 )`,
94 sqlUpdateDecision: `
95 UPDATE decisions
96 SET proponent=:proponent, title=:title, content=:content, votetype=:votetype, due=:due, modified=:modified
97 WHERE id=:id`,
98 sqlUpdateDecisionStatus: `
99 UPDATE decisions SET status=:status, modified=:modified WHERE id=:id`,
100 sqlSelectClosableDecisions: `
101 SELECT decisions.id, decisions.tag, decisions.proponent, decisions.proposed, decisions.title, decisions.content,
102 decisions.votetype, decisions.status, decisions.due, decisions.modified
103 FROM decisions
104 WHERE decisions.status=0 AND :now > due`,
105 sqlGetNextPendingDecisionDue: `
106 SELECT due FROM decisions WHERE status=0 ORDER BY due LIMIT 1`,
107 sqlGetVotersForProxy: `
108 SELECT id, name, reminder
109 FROM voters WHERE enabled=1 AND id != $1 AND id NOT IN (SELECT voter FROM votes WHERE decision=$2)`,
110 sqlGetReminderVoters: `
111 SELECT id, name, reminder FROM voters WHERE enabled=1 AND reminder!='' AND reminder IS NOT NULL`,
112 sqlFindUnvotedDecisionsForVoter: `
113 SELECT tag, title, votetype, due
114 FROM decisions
115 WHERE status = 0 AND id NOT IN (SELECT decision FROM votes WHERE voter = $1)
116 ORDER BY due ASC`,
117 sqlCreateVote: `
118 INSERT OR REPLACE INTO votes (decision, voter, vote, voted, notes)
119 VALUES (:decision, :voter, :vote, :voted, :notes)`,
120 sqlLoadVote: `
121 SELECT decision, voter, vote, voted, notes
122 FROM votes
123 WHERE decision=$1 AND voter=$2`,
124 }
125
126 var db *sqlx.DB
127
128 func init() {
129 failed_statements := make([]string, 0)
130 for _, sqlStatement := range sqlStatements {
131 var stmt *sqlx.Stmt
132 stmt, err := db.Preparex(sqlStatement)
133 if err != nil {
134 logger.Criticalf("ERROR parsing statement %s: %s", sqlStatement, err)
135 failed_statements = append(failed_statements, sqlStatement)
136 }
137 stmt.Close()
138 }
139 if len(failed_statements) > 0 {
140 panic(fmt.Sprintf("%d statements failed", len(failed_statements)))
141 }
142
143 migrateConf := &goose.DBConf{
144 MigrationsDir: config.MigrationsPath,
145 Env: "production",
146 Driver: goose.DBDriver{
147 Name: "sqlite3",
148 OpenStr: config.DatabaseFile,
149 Import: "github.com/mattn/go-sqlite3",
150 Dialect: &goose.Sqlite3Dialect{},
151 },
152 }
153
154 latest, err := goose.GetMostRecentDBVersion(migrateConf.MigrationsDir)
155 if err != nil {
156 logger.Criticalf("getting the most recent database repository version failed: %s", err)
157 panic(err)
158 }
159
160 err = goose.RunMigrationsOnDb(migrateConf, migrateConf.MigrationsDir, latest, db.DB)
161 if err != nil {
162 logger.Criticalf("running database migration failed: %s", err)
163 panic(err)
164 }
165 }
166
167 type VoteType uint8
168 type VoteStatus int8
169
170 type Decision struct {
171 Id int64
172 Proposed time.Time
173 ProponentId int64 `db:"proponent"`
174 Title string
175 Content string
176 Quorum int
177 Majority int
178 Status VoteStatus
179 Due time.Time
180 Modified time.Time
181 Tag string
182 VoteType VoteType
183 }
184
185 type Email struct {
186 VoterId int64 `db:"voter"`
187 Address string
188 }
189
190 type Voter struct {
191 Id int64
192 Name string
193 Enabled bool
194 Reminder string // reminder email address
195 }
196
197 type VoteChoice int
198
199 const (
200 voteAye = 1
201 voteNaye = -1
202 voteAbstain = 0
203 )
204
205 const (
206 voteTypeMotion = 0
207 voteTypeVeto = 1
208 )
209
210 func (v VoteType) String() string {
211 switch v {
212 case voteTypeMotion:
213 return "motion"
214 case voteTypeVeto:
215 return "veto"
216 default:
217 return "unknown"
218 }
219 }
220
221 func (v VoteType) QuorumAndMajority() (int, int) {
222 switch v {
223 case voteTypeMotion:
224 return 3, 50
225 default:
226 return 1, 99
227 }
228 }
229
230 func (v VoteChoice) String() string {
231 switch v {
232 case voteAye:
233 return "aye"
234 case voteNaye:
235 return "naye"
236 case voteAbstain:
237 return "abstain"
238 default:
239 return "unknown"
240 }
241 }
242
243 var VoteValues = map[string]VoteChoice{
244 "aye": voteAye,
245 "naye": voteNaye,
246 "abstain": voteAbstain,
247 }
248
249 var VoteChoices = map[int64]VoteChoice{
250 1: voteAye,
251 0: voteAbstain,
252 -1: voteNaye,
253 }
254
255 const (
256 voteStatusDeclined = -1
257 voteStatusPending = 0
258 voteStatusApproved = 1
259 voteStatusWithdrawn = -2
260 )
261
262 func (v VoteStatus) String() string {
263 switch v {
264 case voteStatusDeclined:
265 return "declined"
266 case voteStatusPending:
267 return "pending"
268 case voteStatusApproved:
269 return "approved"
270 case voteStatusWithdrawn:
271 return "withdrawn"
272 default:
273 return "unknown"
274 }
275 }
276
277 type Vote struct {
278 DecisionId int64 `db:"decision"`
279 VoterId int64 `db:"voter"`
280 Vote VoteChoice
281 Voted time.Time
282 Notes string
283 }
284
285 func (v *Vote) Save() (err error) {
286 insertVoteStmt, err := db.PrepareNamed(sqlStatements[sqlCreateVote])
287 if err != nil {
288 logger.Errorf("preparing statement failed: %s", err)
289 return
290 }
291 defer insertVoteStmt.Close()
292
293 _, err = insertVoteStmt.Exec(v)
294 if err != nil {
295 logger.Errorf("saving vote failed: %s", err)
296 return
297 }
298
299 getVoteStmt, err := db.Preparex(sqlStatements[sqlLoadVote])
300 if err != nil {
301 logger.Errorf("preparing statement failed: %s", err)
302 return
303 }
304 defer getVoteStmt.Close()
305
306 err = getVoteStmt.Get(v, v.DecisionId, v.VoterId)
307 if err != nil {
308 logger.Errorf("getting inserted vote failed: %s", err)
309 }
310
311 return
312 }
313
314 type VoteSums struct {
315 Ayes int
316 Nayes int
317 Abstains int
318 }
319
320 func (v *VoteSums) VoteCount() int {
321 return v.Ayes + v.Nayes + v.Abstains
322 }
323
324 func (v *VoteSums) TotalVotes() int {
325 return v.Ayes + v.Nayes
326 }
327
328 func (v *VoteSums) Percent() int {
329 totalVotes := v.TotalVotes()
330 if totalVotes == 0 {
331 return 0
332 }
333 return v.Ayes * 100 / totalVotes
334 }
335
336 type VoteForDisplay struct {
337 Vote
338 Name string
339 }
340
341 type DecisionForDisplay struct {
342 Decision
343 Proposer string `db:"proposer"`
344 *VoteSums
345 Votes []VoteForDisplay
346 }
347
348 func FindDecisionForDisplayByTag(tag string) (decision *DecisionForDisplay, err error) {
349 decisionStmt, err := db.Preparex(sqlStatements[sqlLoadDecisionByTag])
350 if err != nil {
351 logger.Errorf("preparing statement failed: %s", err)
352 return
353 }
354 defer decisionStmt.Close()
355
356 decision = &DecisionForDisplay{}
357 if err = decisionStmt.Get(decision, tag); err != nil {
358 if err == sql.ErrNoRows {
359 decision = nil
360 err = nil
361 } else {
362 logger.Errorf("getting motion %s failed: %v", tag, err)
363 }
364 }
365 decision.VoteSums, err = decision.Decision.VoteSums()
366 return
367 }
368
369 // FindDecisionsForDisplayOnPage loads a set of decisions from the database.
370 //
371 // This function uses OFFSET for pagination which is not a good idea for larger data sets.
372 //
373 // TODO: migrate to timestamp base pagination
374 func FindDecisionsForDisplayOnPage(page int64, unvoted bool, voter *Voter) (decisions []*DecisionForDisplay, err error) {
375 var decisionsStmt *sqlx.Stmt
376 if unvoted && voter != nil {
377 decisionsStmt, err = db.Preparex(sqlStatements[sqlLoadUnvotedDecisions])
378 } else {
379 decisionsStmt, err = db.Preparex(sqlStatements[sqlLoadDecisions])
380 }
381 if err != nil {
382 logger.Errorf("preparing statement failed: %s", err)
383 return
384 }
385 defer decisionsStmt.Close()
386
387 var rows *sqlx.Rows
388 if unvoted && voter != nil {
389 rows, err = decisionsStmt.Queryx(voter.Id, page-1)
390 } else {
391 rows, err = decisionsStmt.Queryx(page - 1)
392 }
393 if err != nil {
394 logger.Errorf("loading motions for page %d failed: %v", page, err)
395 return
396 }
397 defer rows.Close()
398
399 for rows.Next() {
400 var d DecisionForDisplay
401 if err = rows.StructScan(&d); err != nil {
402 logger.Errorf("loading motions for page %d failed: %v", page, err)
403 return
404 }
405 d.VoteSums, err = d.Decision.VoteSums()
406 if err != nil {
407 return
408 }
409 decisions = append(decisions, &d)
410 }
411 return
412 }
413
414 func (d *Decision) VoteSums() (sums *VoteSums, err error) {
415 votesStmt, err := db.Preparex(sqlStatements[sqlLoadVoteCountsForDecision])
416 if err != nil {
417 logger.Errorf("preparing statement failed: %s", err)
418 return
419 }
420 defer votesStmt.Close()
421
422 voteRows, err := votesStmt.Queryx(d.Id)
423 if err != nil {
424 logger.Errorf("fetching vote sums for motion %s failed: %v", d.Tag, err)
425 return
426 }
427 defer voteRows.Close()
428
429 sums = &VoteSums{}
430 for voteRows.Next() {
431 var vote VoteChoice
432 var count int
433 if err = voteRows.Scan(&vote, &count); err != nil {
434 logger.Errorf("fetching vote sums for motion %s failed: %v", d.Tag, err)
435 return
436 }
437 switch vote {
438 case voteAye:
439 sums.Ayes = count
440 case voteNaye:
441 sums.Nayes = count
442 case voteAbstain:
443 sums.Abstains = count
444 }
445 }
446 return
447 }
448
449 func (d *DecisionForDisplay) LoadVotes() (err error) {
450 votesStmt, err := db.Preparex(sqlStatements[sqlLoadVotesForDecision])
451 if err != nil {
452 logger.Errorf("preparing statement failed: %s", err)
453 return
454 }
455 defer votesStmt.Close()
456 err = votesStmt.Select(&d.Votes, d.Id)
457 if err != nil {
458 logger.Errorf("selecting votes for motion %s failed: %v", d.Tag, err)
459 }
460 return
461 }
462
463 func (d *Decision) OlderExists(unvoted bool, voter *Voter) (result bool, err error) {
464 var olderStmt *sqlx.Stmt
465 if unvoted && voter != nil {
466 olderStmt, err = db.Preparex(sqlStatements[sqlCountOlderThanUnvotedDecision])
467 } else {
468 olderStmt, err = db.Preparex(sqlStatements[sqlCountOlderThanDecision])
469 }
470 if err != nil {
471 logger.Errorf("preparing statement failed: %s", err)
472 return
473 }
474 defer olderStmt.Close()
475
476 if unvoted && voter != nil {
477 if err = olderStmt.Get(&result, d.Proposed, voter.Id); err != nil {
478 logger.Errorf("finding older motions than %s failed: %v", d.Tag, err)
479 }
480 } else {
481 if err = olderStmt.Get(&result, d.Proposed); err != nil {
482 logger.Errorf("finding older motions than %s failed: %v", d.Tag, err)
483 }
484 }
485
486 return
487 }
488
489 func (d *Decision) Create() (err error) {
490 insertDecisionStmt, err := db.PrepareNamed(sqlStatements[sqlCreateDecision])
491 if err != nil {
492 logger.Errorf("preparing statement failed: %s", err)
493 return
494 }
495 defer insertDecisionStmt.Close()
496
497 result, err := insertDecisionStmt.Exec(d)
498 if err != nil {
499 logger.Errorf("creating motion failed: %s", err)
500 return
501 }
502
503 lastInsertId, err := result.LastInsertId()
504 if err != nil {
505 logger.Errorf("getting id of inserted motion failed: %s", err)
506 return
507 }
508 rescheduleChannel <- JobIdCloseDecisions
509
510 getDecisionStmt, err := db.Preparex(sqlStatements[sqlLoadDecisionById])
511 if err != nil {
512 logger.Errorf("preparing statement failed: %s", err)
513 return
514 }
515 defer getDecisionStmt.Close()
516
517 err = getDecisionStmt.Get(d, lastInsertId)
518 if err != nil {
519 logger.Errorf("getting inserted motion failed: %s", err)
520 }
521
522 return
523 }
524
525 func (d *Decision) LoadWithId() (err error) {
526 getDecisionStmt, err := db.Preparex(sqlStatements[sqlLoadDecisionById])
527 if err != nil {
528 logger.Errorf("preparing statement failed: %s", err)
529 return
530 }
531 defer getDecisionStmt.Close()
532
533 err = getDecisionStmt.Get(d, d.Id)
534 if err != nil {
535 logger.Errorf("loading updated motion failed: %s", err)
536 }
537
538 return
539 }
540
541 func (d *Decision) Update() (err error) {
542 updateDecisionStmt, err := db.PrepareNamed(sqlStatements[sqlUpdateDecision])
543 if err != nil {
544 logger.Errorf("preparing statement failed: %s", err)
545 return
546 }
547 defer updateDecisionStmt.Close()
548
549 result, err := updateDecisionStmt.Exec(d)
550 if err != nil {
551 logger.Errorf("updating motion failed: %s", err)
552 return
553 }
554 affectedRows, err := result.RowsAffected()
555 if err != nil {
556 logger.Errorf("Problem determining the affected rows")
557 return
558 } else if affectedRows != 1 {
559 logger.Warningf("wrong number of affected rows: %d (1 expected)", affectedRows)
560 }
561 rescheduleChannel <- JobIdCloseDecisions
562
563 err = d.LoadWithId()
564 return
565 }
566
567 func (d *Decision) UpdateStatus() (err error) {
568 updateStatusStmt, err := db.PrepareNamed(sqlStatements[sqlUpdateDecisionStatus])
569 if err != nil {
570 logger.Errorf("preparing statement failed: %s", err)
571 return
572 }
573 defer updateStatusStmt.Close()
574
575 result, err := updateStatusStmt.Exec(d)
576 if err != nil {
577 logger.Errorf("setting motion status failed: %s", err)
578 return
579 }
580 affectedRows, err := result.RowsAffected()
581 if err != nil {
582 logger.Errorf("determining the affected rows failed: %s", err)
583 return
584 } else if affectedRows != 1 {
585 logger.Warningf("wrong number of affected rows: %d (1 expected)", affectedRows)
586 }
587 rescheduleChannel <- JobIdCloseDecisions
588
589 err = d.LoadWithId()
590 return
591 }
592
593 func (d *Decision) String() string {
594 return fmt.Sprintf("%s %s (Id %d)", d.Tag, d.Title, d.Id)
595 }
596
597 func FindVoterByAddress(emailAddress string) (voter *Voter, err error) {
598 findVoterStmt, err := db.Preparex(sqlStatements[sqlLoadEnabledVoterByEmail])
599 if err != nil {
600 logger.Errorf("preparing statement failed: %s", err)
601 return
602 }
603 defer findVoterStmt.Close()
604
605 voter = &Voter{}
606 if err = findVoterStmt.Get(voter, emailAddress); err != nil {
607 if err != sql.ErrNoRows {
608 logger.Errorf("getting voter for address %s failed: %v", emailAddress, err)
609 } else {
610 err = nil
611 voter = nil
612 }
613 }
614 return
615 }
616
617 func (d *Decision) Close() (err error) {
618 quorum, majority := d.VoteType.QuorumAndMajority()
619
620 voteSums, err := d.VoteSums()
621
622 if err != nil {
623 logger.Errorf("getting vote sums failed: %s", err)
624 return
625 }
626 votes := voteSums.VoteCount()
627
628 if votes < quorum {
629 d.Status = voteStatusDeclined
630 } else {
631 votes = voteSums.TotalVotes()
632 if (voteSums.Ayes / votes) > (majority / 100) {
633 d.Status = voteStatusApproved
634 } else {
635 d.Status = voteStatusDeclined
636 }
637 }
638
639 closeDecisionStmt, err := db.PrepareNamed(sqlStatements[sqlUpdateDecisionStatus])
640 if err != nil {
641 logger.Errorf("preparing statement failed: %s", err)
642 return
643 }
644 defer closeDecisionStmt.Close()
645
646 result, err := closeDecisionStmt.Exec(d)
647 if err != nil {
648 logger.Errorf("closing vote failed: %s", err)
649 return
650 }
651 affectedRows, err := result.RowsAffected()
652 if err != nil {
653 logger.Errorf("getting affected rows failed: %s", err)
654 }
655 if affectedRows != 1 {
656 logger.Warningf("wrong number of affected rows: %d (1 expected)", affectedRows)
657 }
658
659 NotifyMailChannel <- NewNotificationClosedDecision(d, voteSums)
660
661 return
662 }
663
664 func CloseDecisions() (err error) {
665 getClosableDecisionsStmt, err := db.PrepareNamed(sqlStatements[sqlSelectClosableDecisions])
666 if err != nil {
667 logger.Errorf("preparing statement failed: %s", err)
668 return
669 }
670 defer getClosableDecisionsStmt.Close()
671
672 decisions := make([]*Decision, 0)
673 rows, err := getClosableDecisionsStmt.Queryx(struct{ Now time.Time }{time.Now().UTC()})
674 if err != nil {
675 logger.Errorf("fetching closable decisions failed: %s", err)
676 return
677 }
678 defer rows.Close()
679 for rows.Next() {
680 decision := &Decision{}
681 if err = rows.StructScan(decision); err != nil {
682 logger.Errorf("scanning row failed: %s", err)
683 return
684 }
685 decisions = append(decisions, decision)
686 }
687 rows.Close()
688
689 for _, decision := range decisions {
690 logger.Debugf("found closable decision %s", decision.Tag)
691 if err = decision.Close(); err != nil {
692 logger.Errorf("closing decision %s failed: %s", decision.Tag, err)
693 return
694 }
695 }
696
697 return
698 }
699
700 func GetNextPendingDecisionDue() (due *time.Time, err error) {
701 getNextPendingDecisionDueStmt, err := db.Preparex(sqlStatements[sqlGetNextPendingDecisionDue])
702 if err != nil {
703 logger.Errorf("preparing statement failed: %s", err)
704 return
705 }
706 defer getNextPendingDecisionDueStmt.Close()
707
708 row := getNextPendingDecisionDueStmt.QueryRow()
709
710 var dueTimestamp time.Time
711 if err = row.Scan(&dueTimestamp); err != nil {
712 if err == sql.ErrNoRows {
713 logger.Debugf("No pending decisions")
714 return nil, nil
715 }
716 logger.Errorf("parsing result failed: %s", err)
717 return
718 }
719 due = &dueTimestamp
720
721 return
722 }
723
724 func GetReminderVoters() (voters *[]Voter, err error) {
725 getReminderVotersStmt, err := db.Preparex(sqlStatements[sqlGetReminderVoters])
726 if err != nil {
727 logger.Errorf("preparing statement failed: %s", err)
728 return
729 }
730 defer getReminderVotersStmt.Close()
731
732 voterSlice := make([]Voter, 0)
733
734 if err = getReminderVotersStmt.Select(&voterSlice); err != nil {
735 logger.Errorf("getting voters failed: %s", err)
736 return
737 }
738 voters = &voterSlice
739
740 return
741 }
742
743 func FindUnvotedDecisionsForVoter(voter *Voter) (decisions *[]Decision, err error) {
744 findUnvotedDecisionsForVoterStmt, err := db.Preparex(sqlStatements[sqlFindUnvotedDecisionsForVoter])
745 if err != nil {
746 logger.Errorf("preparing statement failed: %s", err)
747 return
748 }
749 defer findUnvotedDecisionsForVoterStmt.Close()
750
751 decisionsSlice := make([]Decision, 0)
752
753 if err = findUnvotedDecisionsForVoterStmt.Select(&decisionsSlice, voter.Id); err != nil {
754 logger.Errorf("getting unvoted decisions failed: %s", err)
755 return
756 }
757 decisions = &decisionsSlice
758
759 return
760 }
761
762 func GetVoterById(id int64) (voter *Voter, err error) {
763 getVoterByIdStmt, err := db.Preparex(sqlStatements[sqlGetEnabledVoterById])
764 if err != nil {
765 logger.Errorf("preparing statement failed: %s", err)
766 return
767 }
768 defer getVoterByIdStmt.Close()
769
770 voter = &Voter{}
771 if err = getVoterByIdStmt.Get(voter, id); err != nil {
772 logger.Errorf("getting voter failed: %s", err)
773 return
774 }
775
776 return
777 }
778
779 func GetVotersForProxy(proxy *Voter, decision *Decision) (voters *[]Voter, err error) {
780 getVotersForProxyStmt, err := db.Preparex(sqlStatements[sqlGetVotersForProxy])
781 if err != nil {
782 logger.Errorf("preparing statement failed: %s", err)
783 return
784 }
785 defer getVotersForProxyStmt.Close()
786
787 votersSlice := make([]Voter, 0)
788
789 if err = getVotersForProxyStmt.Select(&votersSlice, proxy.Id, decision.Id); err != nil {
790 logger.Errorf("Error getting voters for proxy failed: %s", err)
791 return
792 }
793 voters = &votersSlice
794
795 return
796 }