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