diff --git a/cmd/boardvoting/jobs.go b/cmd/boardvoting/jobs.go index 99d20d3..2f9a72c 100644 --- a/cmd/boardvoting/jobs.go +++ b/cmd/boardvoting/jobs.go @@ -24,18 +24,6 @@ import ( "git.cacert.org/cacert-boardvoting/internal/models" ) -func (app *application) SetupJobs() { - quitChannel := make(chan struct{}) - - go app.jobScheduler.Schedule(quitChannel) - - go func() { - for range app.ctx.Done() { - quitChannel <- struct{}{} - } - }() -} - type Job interface { Schedule() Run() @@ -92,7 +80,9 @@ func (r *RemindVotersJob) Run() { } for _, voter := range voters { - decisions, err = r.decisions.FindUnVotedDecisionsForVoter(&voter) + v := voter + + decisions, err = r.decisions.FindUnVotedDecisionsForVoter(&v) if err != nil { r.errorLog.Printf("problem getting unvoted decisions: %v", err) @@ -126,7 +116,7 @@ func (app *application) NewRemindVotersJob( } } -type closeDecisionsJob struct { +type CloseDecisionsJob struct { timer *time.Timer infoLog *log.Logger errorLog *log.Logger @@ -135,7 +125,7 @@ type closeDecisionsJob struct { notify chan NotificationMail } -func (c *closeDecisionsJob) Schedule() { +func (c *CloseDecisionsJob) Schedule() { var ( nextDue *time.Time err error @@ -169,7 +159,7 @@ func (c *closeDecisionsJob) Schedule() { c.timer.Reset(when) } -func (c *closeDecisionsJob) Run() { +func (c *CloseDecisionsJob) Run() { c.infoLog.Printf("running CloseDecisionsJob") results, err := c.decisions.CloseDecisions() @@ -184,7 +174,7 @@ func (c *closeDecisionsJob) Run() { c.reschedule <- c } -func (c *closeDecisionsJob) Stop() { +func (c *CloseDecisionsJob) Stop() { if c.timer != nil { c.timer.Stop() c.timer = nil @@ -194,7 +184,7 @@ func (c *closeDecisionsJob) Stop() { func (app *application) NewCloseDecisionsJob( rescheduleChannel chan Job, ) Job { - return &closeDecisionsJob{ + return &CloseDecisionsJob{ infoLog: app.infoLog, errorLog: app.errorLog, decisions: app.decisions, @@ -208,6 +198,7 @@ type JobScheduler struct { errorLogger *log.Logger jobs []Job rescheduleChannel chan Job + quitChannel chan struct{} } func (app *application) NewJobScheduler() { @@ -218,26 +209,23 @@ func (app *application) NewJobScheduler() { errorLogger: app.errorLog, jobs: make([]Job, 0, 2), rescheduleChannel: rescheduleChannel, + quitChannel: make(chan struct{}), } app.jobScheduler.addJob(app.NewCloseDecisionsJob(rescheduleChannel)) app.jobScheduler.addJob(app.NewRemindVotersJob(rescheduleChannel)) } -func (js *JobScheduler) Schedule(quitChannel chan struct{}) { +func (js *JobScheduler) Schedule() { for _, job := range js.jobs { - js.infoLogger.Printf("schedule job %v", job) - job.Schedule() } for { select { case job := <-js.rescheduleChannel: - js.infoLogger.Printf("reschedule job %v", job) - job.Schedule() - case <-quitChannel: + case <-js.quitChannel: for _, job := range js.jobs { job.Stop() } @@ -252,3 +240,7 @@ func (js *JobScheduler) Schedule(quitChannel chan struct{}) { func (js *JobScheduler) addJob(job Job) { js.jobs = append(js.jobs, job) } + +func (js *JobScheduler) Quit() { + js.quitChannel <- struct{}{} +} diff --git a/cmd/boardvoting/main.go b/cmd/boardvoting/main.go index 6c651a1..497218d 100644 --- a/cmd/boardvoting/main.go +++ b/cmd/boardvoting/main.go @@ -44,7 +44,6 @@ type application struct { errorLog, infoLog *log.Logger voters *models.VoterModel decisions *models.DecisionModel - ctx context.Context jobScheduler *JobScheduler mailNotifier *MailNotifier mailConfig *mailConfig @@ -88,18 +87,23 @@ func main() { infoLog: infoLog, decisions: &models.DecisionModel{DB: db, InfoLog: infoLog}, voters: &models.VoterModel{DB: db}, - ctx: context.Background(), mailConfig: config.MailConfig, baseURL: config.BaseURL, } + app.NewMailNotifier() + defer app.mailNotifier.Quit() + app.NewJobScheduler() + defer app.jobScheduler.Quit() err = internal.InitializeDb(db.DB, infoLog) if err != nil { errorLog.Fatal(err) } + go app.jobScheduler.Schedule() + srv := &http.Server{ Addr: config.HTTPAddress, ErrorLog: errorLog, diff --git a/cmd/boardvoting/notifications.go b/cmd/boardvoting/notifications.go index 597199a..b73a7ff 100644 --- a/cmd/boardvoting/notifications.go +++ b/cmd/boardvoting/notifications.go @@ -1,3 +1,20 @@ +/* +Copyright 2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package main import ( @@ -6,10 +23,11 @@ import ( "path" "text/template" - "git.cacert.org/cacert-boardvoting/internal" - "git.cacert.org/cacert-boardvoting/internal/models" "github.com/Masterminds/sprig/v3" "gopkg.in/mail.v2" + + "git.cacert.org/cacert-boardvoting/internal" + "git.cacert.org/cacert-boardvoting/internal/models" ) type recipientData struct { @@ -32,6 +50,7 @@ type MailNotifier struct { notifyChannel chan NotificationMail senderAddress string dialer *mail.Dialer + quitChannel chan struct{} } func (app *application) NewMailNotifier() { @@ -39,6 +58,7 @@ func (app *application) NewMailNotifier() { notifyChannel: make(chan NotificationMail, 1), senderAddress: app.mailConfig.NotificationSenderAddress, dialer: mail.NewDialer(app.mailConfig.SMTPHost, app.mailConfig.SMTPPort, "", ""), + quitChannel: make(chan struct{}), } } @@ -73,7 +93,7 @@ func (app *application) StartMailNotifier() { if err = app.mailNotifier.dialer.DialAndSend(m); err != nil { app.errorLog.Printf("sending mail failed: %v", err) } - case <-app.ctx.Done(): + case <-app.mailNotifier.quitChannel: app.infoLog.Print("ending mail notifier") return @@ -81,10 +101,12 @@ func (app *application) StartMailNotifier() { } } -func (n *NotificationContent) buildMail(baseUrl string) (fmt.Stringer, error) { - b, err := internal.MailTemplates.ReadFile( - fmt.Sprintf(path.Join("mailtemplates", n.template)), - ) +func (m *MailNotifier) Quit() { + m.quitChannel <- struct{}{} +} + +func (n *NotificationContent) buildMail(baseURL string) (fmt.Stringer, error) { + b, err := internal.MailTemplates.ReadFile(path.Join("mailtemplates", n.template)) if err != nil { return nil, fmt.Errorf("could not read mail template %s: %w", n.template, err) } @@ -97,7 +119,7 @@ func (n *NotificationContent) buildMail(baseUrl string) (fmt.Stringer, error) { data := struct { Data any BaseURL string - }{Data: n.data, BaseURL: baseUrl} + }{Data: n.data, BaseURL: baseURL} mailText := bytes.NewBuffer(make([]byte, 0)) if err = t.Execute(mailText, data); err != nil { diff --git a/internal/mailtemplates.go b/internal/mailtemplates.go index fef5098..9b615cc 100644 --- a/internal/mailtemplates.go +++ b/internal/mailtemplates.go @@ -1,3 +1,20 @@ +/* +Copyright 2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package internal import "embed" diff --git a/internal/models/decisions_test.go b/internal/models/decisions_test.go index c237983..cc49cdb 100644 --- a/internal/models/decisions_test.go +++ b/internal/models/decisions_test.go @@ -1,3 +1,20 @@ +/* +Copyright 2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models_test import ( @@ -8,11 +25,12 @@ import ( "testing" "time" - "git.cacert.org/cacert-boardvoting/internal" - "git.cacert.org/cacert-boardvoting/internal/models" "github.com/jmoiron/sqlx" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "git.cacert.org/cacert-boardvoting/internal" + "git.cacert.org/cacert-boardvoting/internal/models" ) func prepareTestDb(t *testing.T) (*sqlx.DB, *log.Logger) { @@ -44,7 +62,14 @@ func TestDecisionModel_Create(t *testing.T) { Reminder: "test+voter@example.com", } - id, err := dm.Create(v, models.VoteTypeMotion, "test motion", "I move that we should test more", time.Now(), time.Now().AddDate(0, 0, 7)) + id, err := dm.Create( + v, + models.VoteTypeMotion, + "test motion", + "I move that we should test more", + time.Now(), + time.Now().AddDate(0, 0, 7), + ) assert.NoError(t, err) assert.NotEmpty(t, id) }