diff --git a/cmd/boardvoting/jobs.go b/cmd/boardvoting/jobs.go index 2f9a72c..a732db7 100644 --- a/cmd/boardvoting/jobs.go +++ b/cmd/boardvoting/jobs.go @@ -18,6 +18,7 @@ limitations under the License. package main import ( + "context" "log" "time" @@ -72,7 +73,9 @@ func (r *RemindVotersJob) Run() { err error ) - voters, err = r.voters.GetReminderVoters() + ctx := context.Background() + + voters, err = r.voters.GetReminderVoters(ctx) if err != nil { r.errorLog.Printf("problem getting voters: %v", err) @@ -82,7 +85,7 @@ func (r *RemindVotersJob) Run() { for _, voter := range voters { v := voter - decisions, err = r.decisions.FindUnVotedDecisionsForVoter(&v) + decisions, err = r.decisions.UnVotedDecisionsForVoter(ctx, &v) if err != nil { r.errorLog.Printf("problem getting unvoted decisions: %v", err) @@ -131,7 +134,9 @@ func (c *CloseDecisionsJob) Schedule() { err error ) - nextDue, err = c.decisions.GetNextPendingDecisionDue() + ctx := context.Background() + + nextDue, err = c.decisions.NextPendingDecisionDue(ctx) if err != nil { c.errorLog.Printf("could not get next pending due date") @@ -162,7 +167,7 @@ func (c *CloseDecisionsJob) Schedule() { func (c *CloseDecisionsJob) Run() { c.infoLog.Printf("running CloseDecisionsJob") - results, err := c.decisions.CloseDecisions() + results, err := c.decisions.CloseDecisions(context.Background()) if err != nil { c.errorLog.Printf("closing decisions failed: %v", err) } diff --git a/internal/models/decisions.go b/internal/models/decisions.go index 87b49ef..5f5e447 100644 --- a/internal/models/decisions.go +++ b/internal/models/decisions.go @@ -18,6 +18,7 @@ limitations under the License. package models import ( + "context" "database/sql" "errors" "fmt" @@ -162,6 +163,7 @@ type DecisionModel struct { // Create a new decision. func (m *DecisionModel) Create( + ctx context.Context, proponent *Voter, voteType VoteType, title, content string, @@ -176,14 +178,18 @@ func (m *DecisionModel) Create( VoteType: voteType, } - result, err := m.DB.NamedExec(`INSERT INTO decisions + result, err := m.DB.NamedExecContext( + ctx, + `INSERT INTO decisions (proposed, proponent, title, content, votetype, status, due, modified, tag) VALUES (:proposed, :proponent, :title, :content, :votetype, :status, :due, :proposed, 'm' || strftime('%Y%m%d', :proposed) || '.' || ( SELECT COUNT(*)+1 AS num FROM decisions WHERE proposed BETWEEN date(:proposed) AND date(:proposed, '1 day') -))`, d) +))`, + d, + ) if err != nil { return 0, fmt.Errorf("creating motion failed: %w", err) } @@ -196,8 +202,8 @@ VALUES (:proposed, :proponent, :title, :content, :votetype, :status, :due, :prop return id, nil } -func (m *DecisionModel) CloseDecisions() ([]*ClosedDecision, error) { - tx, err := m.DB.Beginx() +func (m *DecisionModel) CloseDecisions(ctx context.Context) ([]*ClosedDecision, error) { + tx, err := m.DB.BeginTxx(ctx, nil) if err != nil { return nil, fmt.Errorf("could not start transaction: %w", err) } @@ -241,7 +247,7 @@ WHERE decisions.status=0 AND :now > due`, struct{ Now time.Time }{Now: time.Now for _, decision := range decisions { m.InfoLog.Printf("found closable decision %s", decision.Tag) - if decisionResult, err = m.CloseDecision(tx, decision); err != nil { + if decisionResult, err = m.CloseDecision(ctx, tx, decision); err != nil { return nil, fmt.Errorf("closing decision %s failed: %w", decision.Tag, err) } @@ -255,7 +261,7 @@ WHERE decisions.status=0 AND :now > due`, struct{ Now time.Time }{Now: time.Now return results, nil } -func (m *DecisionModel) CloseDecision(tx *sqlx.Tx, d *Decision) (*ClosedDecision, error) { +func (m *DecisionModel) CloseDecision(ctx context.Context, tx *sqlx.Tx, d *Decision) (*ClosedDecision, error) { quorum, majority := d.VoteType.QuorumAndMajority() var ( @@ -264,13 +270,14 @@ func (m *DecisionModel) CloseDecision(tx *sqlx.Tx, d *Decision) (*ClosedDecision reasoning string ) - if voteSums, err = m.GetVoteSums(tx, d); err != nil { + if voteSums, err = m.SumsForDecision(ctx, tx, d); err != nil { return nil, fmt.Errorf("getting vote sums failed: %w", err) } d.Status, reasoning = voteSums.CalculateResult(quorum, majority) - result, err := m.DB.NamedExec( + result, err := m.DB.NamedExecContext( + ctx, `UPDATE decisions SET status=:status, modified=CURRENT_TIMESTAMP WHERE id=:id`, d, ) @@ -293,12 +300,13 @@ func (m *DecisionModel) CloseDecision(tx *sqlx.Tx, d *Decision) (*ClosedDecision return &ClosedDecision{d, voteSums, reasoning}, nil } -func (m *DecisionModel) FindUnVotedDecisionsForVoter(_ *Voter) ([]Decision, error) { +func (m *DecisionModel) UnVotedDecisionsForVoter(_ context.Context, _ *Voter) ([]Decision, error) { panic("not implemented") } -func (m *DecisionModel) GetVoteSums(tx *sqlx.Tx, d *Decision) (*VoteSums, error) { - voteRows, err := tx.NamedQuery( +func (m *DecisionModel) SumsForDecision(ctx context.Context, tx *sqlx.Tx, d *Decision) (*VoteSums, error) { + voteRows, err := tx.QueryxContext( + ctx, `SELECT vote, COUNT(vote) FROM votes WHERE decision=$1 GROUP BY vote`, d.ID, ) @@ -339,8 +347,12 @@ func (m *DecisionModel) GetVoteSums(tx *sqlx.Tx, d *Decision) (*VoteSums, error) return sums, nil } -func (m *DecisionModel) GetNextPendingDecisionDue() (*time.Time, error) { - row := m.DB.QueryRow(`SELECT due FROM decisions WHERE status=0 ORDER BY due LIMIT 1`, nil) +func (m *DecisionModel) NextPendingDecisionDue(ctx context.Context) (*time.Time, error) { + row := m.DB.QueryRowContext( + ctx, + `SELECT due FROM decisions WHERE status=0 ORDER BY due LIMIT 1`, + nil, + ) if row == nil { return nil, errors.New("no row returned") diff --git a/internal/models/decisions_test.go b/internal/models/decisions_test.go index cc49cdb..3b8b623 100644 --- a/internal/models/decisions_test.go +++ b/internal/models/decisions_test.go @@ -18,6 +18,7 @@ limitations under the License. package models_test import ( + "context" "database/sql" "log" "os" @@ -63,6 +64,7 @@ func TestDecisionModel_Create(t *testing.T) { } id, err := dm.Create( + context.Background(), v, models.VoteTypeMotion, "test motion", @@ -84,7 +86,7 @@ func TestDecisionModel_GetNextPendingDecisionDue(t *testing.T) { err error ) - nextDue, err = dm.GetNextPendingDecisionDue() + nextDue, err = dm.NextPendingDecisionDue(context.Background()) assert.NoError(t, err) assert.Empty(t, nextDue) @@ -96,10 +98,12 @@ func TestDecisionModel_GetNextPendingDecisionDue(t *testing.T) { due := time.Now().Add(10 * time.Minute) - _, err = dm.Create(v, models.VoteTypeMotion, "test motion", "I move that we should test more", time.Now(), due) + ctx := context.Background() + + _, err = dm.Create(ctx, v, models.VoteTypeMotion, "test motion", "I move that we should test more", time.Now(), due) require.NoError(t, err) - nextDue, err = dm.GetNextPendingDecisionDue() + nextDue, err = dm.NextPendingDecisionDue(ctx) assert.NoError(t, err) assert.NotEmpty(t, nextDue) diff --git a/internal/models/voters.go b/internal/models/voters.go index 8c544a7..47ec118 100644 --- a/internal/models/voters.go +++ b/internal/models/voters.go @@ -18,6 +18,8 @@ limitations under the License. package models import ( + "context" + "github.com/jmoiron/sqlx" ) @@ -31,6 +33,6 @@ type VoterModel struct { DB *sqlx.DB } -func (m VoterModel) GetReminderVoters() ([]Voter, error) { +func (m VoterModel) GetReminderVoters(_ context.Context) ([]Voter, error) { panic("not implemented") }