Start notification job refactoring
This commit is contained in:
parent
933f21a43c
commit
01b95f2253
22 changed files with 897 additions and 85 deletions
|
@ -29,6 +29,8 @@ linters-settings:
|
||||||
gomnd:
|
gomnd:
|
||||||
ignore-functions:
|
ignore-functions:
|
||||||
- 'strconv.*'
|
- 'strconv.*'
|
||||||
|
ignored-numbers:
|
||||||
|
- '-1,0,1,2,8'
|
||||||
goimports:
|
goimports:
|
||||||
local-prefixes: git.cacert.org/cacert-boardvoting
|
local-prefixes: git.cacert.org/cacert-boardvoting
|
||||||
misspell:
|
misspell:
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
@ -31,17 +32,29 @@ import (
|
||||||
const (
|
const (
|
||||||
cookieSecretMinLen = 32
|
cookieSecretMinLen = 32
|
||||||
csrfKeyLength = 32
|
csrfKeyLength = 32
|
||||||
httpIdleTimeout = 5
|
httpIdleTimeout = 5 * time.Second
|
||||||
httpReadHeaderTimeout = 10
|
httpReadHeaderTimeout = 10 * time.Second
|
||||||
httpReadTimeout = 10
|
httpReadTimeout = 10 * time.Second
|
||||||
httpWriteTimeout = 60
|
httpWriteTimeout = 60 * time.Second
|
||||||
sessionCookieName = "votesession"
|
sessionCookieName = "votesession"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type mailConfig struct {
|
||||||
|
SMTPHost string `yaml:"host"`
|
||||||
|
SMTPPort int `yaml:"port"`
|
||||||
|
NotificationSenderAddress string `yaml:"notification_sender_address"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpTimeoutConfig struct {
|
||||||
|
Idle time.Duration `yaml:"idle,omitempty"`
|
||||||
|
Read time.Duration `yaml:"read,omitempty"`
|
||||||
|
ReadHeader time.Duration `yaml:"read_header,omitempty"`
|
||||||
|
Write time.Duration `yaml:"write,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
NoticeMailAddress string `yaml:"notice_mail_address"`
|
NoticeMailAddress string `yaml:"notice_mail_address"`
|
||||||
VoteNoticeMailAddress string `yaml:"vote_notice_mail_address"`
|
VoteNoticeMailAddress string `yaml:"vote_notice_mail_address"`
|
||||||
NotificationSenderAddress string `yaml:"notification_sender_address"`
|
|
||||||
DatabaseFile string `yaml:"database_file"`
|
DatabaseFile string `yaml:"database_file"`
|
||||||
ClientCACertificates string `yaml:"client_ca_certificates"`
|
ClientCACertificates string `yaml:"client_ca_certificates"`
|
||||||
ServerCert string `yaml:"server_certificate"`
|
ServerCert string `yaml:"server_certificate"`
|
||||||
|
@ -51,10 +64,8 @@ type Config struct {
|
||||||
BaseURL string `yaml:"base_url"`
|
BaseURL string `yaml:"base_url"`
|
||||||
HTTPAddress string `yaml:"http_address,omitempty"`
|
HTTPAddress string `yaml:"http_address,omitempty"`
|
||||||
HTTPSAddress string `yaml:"https_address,omitempty"`
|
HTTPSAddress string `yaml:"https_address,omitempty"`
|
||||||
MailServer struct {
|
MailConfig mailConfig `yaml:"mail_server"`
|
||||||
Host string `yaml:"host"`
|
Timeouts httpTimeoutConfig `yaml:"timeouts,omitempty"`
|
||||||
Port int `yaml:"port"`
|
|
||||||
} `yaml:"mail_server"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type configKey int
|
type configKey int
|
||||||
|
@ -73,6 +84,12 @@ func parseConfig(ctx context.Context, configFile string) (context.Context, error
|
||||||
config := &Config{
|
config := &Config{
|
||||||
HTTPAddress: "127.0.0.1:8000",
|
HTTPAddress: "127.0.0.1:8000",
|
||||||
HTTPSAddress: "127.0.0.1:8433",
|
HTTPSAddress: "127.0.0.1:8433",
|
||||||
|
Timeouts: httpTimeoutConfig{
|
||||||
|
Idle: httpIdleTimeout,
|
||||||
|
ReadHeader: httpReadHeaderTimeout,
|
||||||
|
Read: httpReadTimeout,
|
||||||
|
Write: httpWriteTimeout,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := yaml.Unmarshal(source, config); err != nil {
|
if err := yaml.Unmarshal(source, config); err != nil {
|
||||||
|
|
|
@ -24,7 +24,7 @@ import (
|
||||||
|
|
||||||
func (app *application) motions(w http.ResponseWriter, r *http.Request) {
|
func (app *application) motions(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/motions/" {
|
if r.URL.Path != "/motions/" {
|
||||||
http.NotFound(w, r)
|
app.notFound(w)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -37,16 +37,14 @@ func (app *application) motions(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
ts, err := template.ParseFiles(files...)
|
ts, err := template.ParseFiles(files...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.errorLog.Print(err.Error())
|
app.serverError(w, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ts.ExecuteTemplate(w, "base", nil)
|
err = ts.ExecuteTemplate(w, "base", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.errorLog.Print(err.Error())
|
app.serverError(w, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +57,7 @@ func (app *application) home(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if r.Method != "GET" && r.Method != "HEAD" {
|
if r.Method != "GET" && r.Method != "HEAD" {
|
||||||
w.Header().Set("Allow", "GET,HEAD")
|
w.Header().Set("Allow", "GET,HEAD")
|
||||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
app.clientError(w, http.StatusMethodNotAllowed)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
40
cmd/boardvoting/helpers.go
Normal file
40
cmd/boardvoting/helpers.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017-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 (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"runtime/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (app *application) serverError(w http.ResponseWriter, err error) {
|
||||||
|
trace := fmt.Sprintf("%s\n%s", err.Error(), debug.Stack())
|
||||||
|
|
||||||
|
_ = app.errorLog.Output(2, trace)
|
||||||
|
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *application) clientError(w http.ResponseWriter, status int) {
|
||||||
|
http.Error(w, http.StatusText(status), status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *application) notFound(w http.ResponseWriter) {
|
||||||
|
app.clientError(w, http.StatusNotFound)
|
||||||
|
}
|
201
cmd/boardvoting/jobs.go
Normal file
201
cmd/boardvoting/jobs.go
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017-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 (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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()
|
||||||
|
Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
type RemindVotersJob struct {
|
||||||
|
infoLog, errorLog *log.Logger
|
||||||
|
timer *time.Timer
|
||||||
|
voters *models.VoterModel
|
||||||
|
decisions *models.DecisionModel
|
||||||
|
notify chan NotificationMail
|
||||||
|
reschedule chan Job
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemindVotersJob) Schedule() {
|
||||||
|
const reminderDays = 3
|
||||||
|
|
||||||
|
year, month, day := time.Now().UTC().Date()
|
||||||
|
|
||||||
|
nextExecution := time.Date(
|
||||||
|
year, month, day, 0, 0, 0, 0, time.UTC,
|
||||||
|
).AddDate(0, 0, reminderDays)
|
||||||
|
|
||||||
|
r.infoLog.Printf("scheduling RemindVotersJob for %s", nextExecution)
|
||||||
|
|
||||||
|
when := time.Until(nextExecution)
|
||||||
|
|
||||||
|
if r.timer != nil {
|
||||||
|
r.timer.Reset(when)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.timer = time.AfterFunc(when, r.Run)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemindVotersJob) Run() {
|
||||||
|
r.infoLog.Print("running RemindVotersJob")
|
||||||
|
|
||||||
|
defer func(r *RemindVotersJob) { r.reschedule <- r }(r)
|
||||||
|
|
||||||
|
var (
|
||||||
|
voters []models.Voter
|
||||||
|
decisions []models.Decision
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
voters, err = r.voters.GetReminderVoters()
|
||||||
|
if err != nil {
|
||||||
|
r.errorLog.Printf("problem getting voters: %v", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, voter := range voters {
|
||||||
|
decisions, err = r.decisions.FindUnVotedDecisionsForVoter(&voter)
|
||||||
|
if err != nil {
|
||||||
|
r.errorLog.Printf("problem getting unvoted decisions: %v", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(decisions) > 0 {
|
||||||
|
r.notify <- &RemindVoterNotification{voter: voter, decisions: decisions}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemindVotersJob) Stop() {
|
||||||
|
if r.timer != nil {
|
||||||
|
r.timer.Stop()
|
||||||
|
|
||||||
|
r.timer = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *application) NewRemindVotersJob(
|
||||||
|
rescheduleChannel chan Job,
|
||||||
|
notificationChannel chan NotificationMail,
|
||||||
|
) Job {
|
||||||
|
return &RemindVotersJob{
|
||||||
|
infoLog: app.infoLog,
|
||||||
|
errorLog: app.errorLog,
|
||||||
|
voters: app.voters,
|
||||||
|
decisions: app.decisions,
|
||||||
|
reschedule: rescheduleChannel,
|
||||||
|
notify: notificationChannel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type closeDecisionsJob struct{}
|
||||||
|
|
||||||
|
func (c *closeDecisionsJob) Schedule() {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *closeDecisionsJob) Run() {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *closeDecisionsJob) Stop() {
|
||||||
|
// TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCloseDecisionsJob() Job {
|
||||||
|
// TODO implement real job
|
||||||
|
|
||||||
|
return &closeDecisionsJob{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type JobScheduler struct {
|
||||||
|
infoLogger *log.Logger
|
||||||
|
errorLogger *log.Logger
|
||||||
|
jobs []Job
|
||||||
|
rescheduleChannel chan Job
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *application) NewJobScheduler() {
|
||||||
|
rescheduleChannel := make(chan Job, 1)
|
||||||
|
|
||||||
|
app.jobScheduler = &JobScheduler{
|
||||||
|
infoLogger: app.infoLog,
|
||||||
|
errorLogger: app.errorLog,
|
||||||
|
jobs: make([]Job, 0, 2),
|
||||||
|
rescheduleChannel: rescheduleChannel,
|
||||||
|
}
|
||||||
|
|
||||||
|
app.jobScheduler.addJob(NewCloseDecisionsJob())
|
||||||
|
app.jobScheduler.addJob(app.NewRemindVotersJob(rescheduleChannel, app.mailNotifier.notifyChannel))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (js *JobScheduler) Schedule(quitChannel chan struct{}) {
|
||||||
|
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:
|
||||||
|
for _, job := range js.jobs {
|
||||||
|
job.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
js.infoLogger.Print("stop job scheduler")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (js *JobScheduler) addJob(job Job) {
|
||||||
|
js.jobs = append(js.jobs, job)
|
||||||
|
}
|
|
@ -20,16 +20,18 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"flag"
|
"flag"
|
||||||
"io/fs"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/vearutop/statigz"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/vearutop/statigz/brotli"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
|
||||||
"git.cacert.org/cacert-boardvoting/ui"
|
"git.cacert.org/cacert-boardvoting/internal"
|
||||||
|
"git.cacert.org/cacert-boardvoting/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -40,6 +42,13 @@ var (
|
||||||
|
|
||||||
type application struct {
|
type application struct {
|
||||||
errorLog, infoLog *log.Logger
|
errorLog, infoLog *log.Logger
|
||||||
|
voters *models.VoterModel
|
||||||
|
decisions *models.DecisionModel
|
||||||
|
ctx context.Context
|
||||||
|
jobScheduler *JobScheduler
|
||||||
|
mailNotifier *MailNotifier
|
||||||
|
mailConfig mailConfig
|
||||||
|
baseURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -56,28 +65,37 @@ func main() {
|
||||||
errorLog.Fatal(err)
|
errorLog.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
config, err := GetConfig(ctx)
|
||||||
|
if err != nil {
|
||||||
staticDir, _ := fs.Sub(ui.Files, "static")
|
errorLog.Fatal(err)
|
||||||
|
|
||||||
staticData, ok := staticDir.(fs.ReadDirFS)
|
|
||||||
if !ok {
|
|
||||||
errorLog.Fatal("could not use uiStaticDir as fs.ReadDirFS")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fileServer := statigz.FileServer(staticData, brotli.AddEncoding, statigz.EncodeOnInit)
|
db, err := openDB(config.DatabaseFile)
|
||||||
|
if err != nil {
|
||||||
|
errorLog.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
mux.Handle("/static/", http.StripPrefix("/static", fileServer))
|
defer func(db *sqlx.DB) {
|
||||||
|
_ = db.Close()
|
||||||
|
}(db)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
errorLog.Fatalf("could not setup decision model: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
app := &application{
|
app := &application{
|
||||||
errorLog: errorLog,
|
errorLog: errorLog,
|
||||||
infoLog: infoLog,
|
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()
|
||||||
|
app.NewJobScheduler()
|
||||||
|
|
||||||
mux.HandleFunc("/", app.home)
|
err = internal.InitializeDb(db.DB, infoLog)
|
||||||
mux.HandleFunc("/motions/", app.motions)
|
|
||||||
|
|
||||||
config, err := GetConfig(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorLog.Fatal(err)
|
errorLog.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -85,11 +103,29 @@ func main() {
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: config.HTTPAddress,
|
Addr: config.HTTPAddress,
|
||||||
ErrorLog: errorLog,
|
ErrorLog: errorLog,
|
||||||
Handler: mux,
|
Handler: app.routes(),
|
||||||
|
IdleTimeout: config.Timeouts.Idle,
|
||||||
|
ReadHeaderTimeout: config.Timeouts.ReadHeader,
|
||||||
|
ReadTimeout: config.Timeouts.Read,
|
||||||
|
WriteTimeout: config.Timeouts.Write,
|
||||||
}
|
}
|
||||||
|
|
||||||
infoLog.Printf("Starting server on %s", config.HTTPAddress)
|
infoLog.Printf("Starting server on %s", config.HTTPAddress)
|
||||||
|
|
||||||
err = srv.ListenAndServe()
|
err = srv.ListenAndServe()
|
||||||
|
|
||||||
errorLog.Fatal(err)
|
errorLog.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func openDB(dbFile string) (*sqlx.DB, error) {
|
||||||
|
db, err := sql.Open("sqlite3", dbFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not open database file %s: %w", dbFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = db.Ping(); err != nil {
|
||||||
|
return nil, fmt.Errorf("could not ping database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sqlx.NewDb(db, "sqlite3"), nil
|
||||||
|
}
|
||||||
|
|
133
cmd/boardvoting/notifications.go
Normal file
133
cmd/boardvoting/notifications.go
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
type headerData struct {
|
||||||
|
name string
|
||||||
|
value []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type recipientData struct {
|
||||||
|
field, address, name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotificationContent struct {
|
||||||
|
template string
|
||||||
|
data interface{}
|
||||||
|
subject string
|
||||||
|
headers []headerData
|
||||||
|
recipients []recipientData
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotificationMail interface {
|
||||||
|
GetNotificationContent() *NotificationContent
|
||||||
|
}
|
||||||
|
|
||||||
|
type MailNotifier struct {
|
||||||
|
notifyChannel chan NotificationMail
|
||||||
|
senderAddress string
|
||||||
|
dialer *mail.Dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *application) NewMailNotifier() {
|
||||||
|
app.mailNotifier = &MailNotifier{
|
||||||
|
notifyChannel: make(chan NotificationMail, 1),
|
||||||
|
senderAddress: app.mailConfig.NotificationSenderAddress,
|
||||||
|
dialer: mail.NewDialer(app.mailConfig.SMTPHost, app.mailConfig.SMTPPort, "", ""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *application) StartMailNotifier() {
|
||||||
|
app.infoLog.Print("Launching mail notifier")
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case notification := <-app.mailNotifier.notifyChannel:
|
||||||
|
content := notification.GetNotificationContent()
|
||||||
|
|
||||||
|
mailText, err := content.buildMail(app.baseURL)
|
||||||
|
if err != nil {
|
||||||
|
app.errorLog.Printf("building mail failed: %v", err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
m := mail.NewMessage()
|
||||||
|
m.SetAddressHeader("From", app.mailNotifier.senderAddress, "CAcert board voting system")
|
||||||
|
|
||||||
|
for _, recipient := range content.recipients {
|
||||||
|
m.SetAddressHeader(recipient.field, recipient.address, recipient.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.SetHeader("Subject", content.subject)
|
||||||
|
|
||||||
|
for _, header := range content.headers {
|
||||||
|
m.SetHeader(header.name, header.value...)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.SetBody("text/plain", mailText.String())
|
||||||
|
|
||||||
|
if err = app.mailNotifier.dialer.DialAndSend(m); err != nil {
|
||||||
|
app.errorLog.Printf("sending mail failed: %v", err)
|
||||||
|
}
|
||||||
|
case <-app.ctx.Done():
|
||||||
|
app.infoLog.Print("ending mail notifier")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NotificationContent) buildMail(baseUrl string) (fmt.Stringer, error) {
|
||||||
|
b, err := internal.MailTemplates.ReadFile(
|
||||||
|
fmt.Sprintf(path.Join("mailtemplates", n.template)),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not read mail template %s: %w", n.template, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := template.New(n.template).Funcs(sprig.GenericFuncMap()).Parse(string(b))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse mail template %s: %w", n.template, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := struct {
|
||||||
|
Data any
|
||||||
|
BaseURL string
|
||||||
|
}{Data: n.data, BaseURL: baseUrl}
|
||||||
|
|
||||||
|
mailText := bytes.NewBuffer(make([]byte, 0))
|
||||||
|
if err = t.Execute(mailText, data); err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"failed to execute template %s with context %v: %w", n.template, n.data, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mailText, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type RemindVoterNotification struct {
|
||||||
|
voter models.Voter
|
||||||
|
decisions []models.Decision
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r RemindVoterNotification) GetNotificationContent() *NotificationContent {
|
||||||
|
return &NotificationContent{
|
||||||
|
template: "remind_voter_mail.txt",
|
||||||
|
data: struct {
|
||||||
|
Decisions []models.Decision
|
||||||
|
Name string
|
||||||
|
}{Decisions: r.decisions, Name: r.voter.Name},
|
||||||
|
subject: "Outstanding CAcert board votes",
|
||||||
|
recipients: []recipientData{{"To", r.voter.Reminder, r.voter.Name}},
|
||||||
|
}
|
||||||
|
}
|
48
cmd/boardvoting/routes.go
Normal file
48
cmd/boardvoting/routes.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017-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 (
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/vearutop/statigz"
|
||||||
|
"github.com/vearutop/statigz/brotli"
|
||||||
|
|
||||||
|
"git.cacert.org/cacert-boardvoting/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (app *application) routes() *http.ServeMux {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
staticDir, _ := fs.Sub(ui.Files, "static")
|
||||||
|
|
||||||
|
staticData, ok := staticDir.(fs.ReadDirFS)
|
||||||
|
if !ok {
|
||||||
|
app.errorLog.Fatal("could not use uiStaticDir as fs.ReadDirFS")
|
||||||
|
}
|
||||||
|
|
||||||
|
fileServer := statigz.FileServer(staticData, brotli.AddEncoding, statigz.EncodeOnInit)
|
||||||
|
|
||||||
|
mux.Handle("/static/", http.StripPrefix("/static", fileServer))
|
||||||
|
|
||||||
|
mux.HandleFunc("/", app.home)
|
||||||
|
mux.HandleFunc("/motions/", app.motions)
|
||||||
|
|
||||||
|
return mux
|
||||||
|
}
|
5
go.mod
5
go.mod
|
@ -25,14 +25,19 @@ require (
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require github.com/stretchr/testify v1.7.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/shopspring/decimal v1.3.1 // indirect
|
github.com/shopspring/decimal v1.3.1 // indirect
|
||||||
github.com/spf13/cast v1.4.1 // indirect
|
github.com/spf13/cast v1.4.1 // indirect
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||||
)
|
)
|
||||||
|
|
36
go.sum
36
go.sum
|
@ -123,8 +123,6 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
|
||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||||
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
|
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
|
||||||
github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk=
|
github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk=
|
||||||
github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc=
|
|
||||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
|
||||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
|
@ -178,8 +176,6 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm
|
||||||
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||||
github.com/bool64/dev v0.1.18 h1:uMN5MsHrVWmtRoauefI8wD86b8vbXYnrCZlIhFGyuXI=
|
|
||||||
github.com/bool64/dev v0.1.18/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
|
|
||||||
github.com/bool64/dev v0.2.9 h1:efyGf5pgx4CYWQpCzPEX8a1PgewaCGaEexXa+IYHT/8=
|
github.com/bool64/dev v0.2.9 h1:efyGf5pgx4CYWQpCzPEX8a1PgewaCGaEexXa+IYHT/8=
|
||||||
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
|
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
|
||||||
|
@ -461,8 +457,8 @@ github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
|
||||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||||
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
|
||||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
|
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
|
@ -511,7 +507,6 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||||
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||||
github.com/golang-migrate/migrate/v4 v4.14.2-0.20201125065321-a53e6fc42574 h1:YEVMe8861NCZxTMzTI5BCobpwGpt1Md6D8v00jDc68w=
|
|
||||||
github.com/golang-migrate/migrate/v4 v4.14.2-0.20201125065321-a53e6fc42574/go.mod h1:l7Ks0Au6fYHuUIxUhQ0rcVX1uLlJg54C/VvW7tvxSz0=
|
github.com/golang-migrate/migrate/v4 v4.14.2-0.20201125065321-a53e6fc42574/go.mod h1:l7Ks0Au6fYHuUIxUhQ0rcVX1uLlJg54C/VvW7tvxSz0=
|
||||||
github.com/golang-migrate/migrate/v4 v4.15.2 h1:vU+M05vs6jWHKDdmE1Ecwj0BznygFc4QsdRe2E/L7kc=
|
github.com/golang-migrate/migrate/v4 v4.15.2 h1:vU+M05vs6jWHKDdmE1Ecwj0BznygFc4QsdRe2E/L7kc=
|
||||||
github.com/golang-migrate/migrate/v4 v4.15.2/go.mod h1:f2toGLkYqD3JH+Todi4aZ2ZdbeUNx4sIwiOK96rE9Lw=
|
github.com/golang-migrate/migrate/v4 v4.15.2/go.mod h1:f2toGLkYqD3JH+Todi4aZ2ZdbeUNx4sIwiOK96rE9Lw=
|
||||||
|
@ -602,7 +597,6 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
|
||||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
|
||||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
@ -615,8 +609,6 @@ github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2c
|
||||||
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
|
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
github.com/gorilla/csrf v1.7.0 h1:mMPjV5/3Zd460xCavIkppUdvnl5fPXMpv2uz2Zyg7/Y=
|
|
||||||
github.com/gorilla/csrf v1.7.0/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA=
|
|
||||||
github.com/gorilla/csrf v1.7.1 h1:Ir3o2c1/Uzj6FBxMlAUB6SivgVMy1ONXwYgXn+/aHPE=
|
github.com/gorilla/csrf v1.7.1 h1:Ir3o2c1/Uzj6FBxMlAUB6SivgVMy1ONXwYgXn+/aHPE=
|
||||||
github.com/gorilla/csrf v1.7.1/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA=
|
github.com/gorilla/csrf v1.7.1/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA=
|
||||||
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||||
|
@ -737,13 +729,10 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht
|
||||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||||
github.com/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE=
|
|
||||||
github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
|
github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
|
||||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||||
github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8=
|
github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8=
|
||||||
github.com/johejo/golang-migrate-extra v0.0.0-20210217013041-51a992e50d16 h1:sDjlyV4OzJYR2ZdLAAKZwV6N/CcX60xbGnPZzKJOZ50=
|
|
||||||
github.com/johejo/golang-migrate-extra v0.0.0-20210217013041-51a992e50d16/go.mod h1:lzH77MbyyahK7YO90wGRb65i9xLSoy2fD0dUSm23yMs=
|
|
||||||
github.com/johejo/golang-migrate-extra v0.0.0-20211005021153-c17dd75f8b4a h1:89hRqHzTmEoJi8TY11K42F0isvNR0UAhL4V3hYD74pk=
|
github.com/johejo/golang-migrate-extra v0.0.0-20211005021153-c17dd75f8b4a h1:89hRqHzTmEoJi8TY11K42F0isvNR0UAhL4V3hYD74pk=
|
||||||
github.com/johejo/golang-migrate-extra v0.0.0-20211005021153-c17dd75f8b4a/go.mod h1:lzH77MbyyahK7YO90wGRb65i9xLSoy2fD0dUSm23yMs=
|
github.com/johejo/golang-migrate-extra v0.0.0-20211005021153-c17dd75f8b4a/go.mod h1:lzH77MbyyahK7YO90wGRb65i9xLSoy2fD0dUSm23yMs=
|
||||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||||
|
@ -784,22 +773,21 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxv
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4=
|
github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4=
|
||||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
|
|
||||||
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
|
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
|
||||||
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
@ -834,7 +822,6 @@ github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vq
|
||||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0=
|
github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0=
|
||||||
|
@ -847,8 +834,6 @@ github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WT
|
||||||
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
|
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||||
github.com/mitchellh/copystructure v1.1.1 h1:Bp6x9R1Wn16SIz3OfeDr0b7RnCG2OB66Y7PQyC/cvq4=
|
|
||||||
github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4=
|
|
||||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
@ -861,8 +846,6 @@ github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:F
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
|
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
|
||||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE=
|
|
||||||
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
||||||
|
@ -1026,7 +1009,6 @@ github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvW
|
||||||
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
||||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||||
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
|
||||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
|
@ -1055,7 +1037,6 @@ github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd
|
||||||
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
|
||||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
|
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
|
||||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
|
@ -1082,9 +1063,9 @@ github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||||
|
@ -1102,8 +1083,6 @@ github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/
|
||||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
github.com/vearutop/statigz v1.1.2 h1:ltPbIR88z+6fNrF74WRbG59HVSdKA+Lr1dbn/wLcu4I=
|
|
||||||
github.com/vearutop/statigz v1.1.2/go.mod h1:q0L+aIjEv8qKU2WUV+legzbWLlNuP/x89qzMbmQCvLc=
|
|
||||||
github.com/vearutop/statigz v1.1.8 h1:IJgQHx6EomuYOYd2TzFt3haP+BIzV471zn7aepRiLHA=
|
github.com/vearutop/statigz v1.1.8 h1:IJgQHx6EomuYOYd2TzFt3haP+BIzV471zn7aepRiLHA=
|
||||||
github.com/vearutop/statigz v1.1.8/go.mod h1:pfzrpvgLRnFeSVZd9iUYrpYDLqbV+RgeCfizr3ZFf44=
|
github.com/vearutop/statigz v1.1.8/go.mod h1:pfzrpvgLRnFeSVZd9iUYrpYDLqbV+RgeCfizr3ZFf44=
|
||||||
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||||
|
@ -1220,8 +1199,6 @@ golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPh
|
||||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b h1:wSOdpTq0/eI46Ez/LkDwIsAKA71YP2SRKBODiRWM0as=
|
|
||||||
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
|
||||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
@ -1463,8 +1440,6 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210313202042-bd2e13477e9c h1:coiPEfMv+ThsjULRDygLrJVlNE1gDdL2g65s0LhV2os=
|
|
||||||
golang.org/x/sys v0.0.0-20210313202042-bd2e13477e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
@ -1786,10 +1761,10 @@ gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gG
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
@ -1819,6 +1794,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg=
|
gorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg=
|
||||||
gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||||
|
|
6
internal/mailtemplates.go
Normal file
6
internal/mailtemplates.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
//go:embed "mailtemplates"
|
||||||
|
var MailTemplates embed.FS
|
|
@ -17,7 +17,66 @@ limitations under the License.
|
||||||
|
|
||||||
package internal
|
package internal
|
||||||
|
|
||||||
import "embed"
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"embed"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/golang-migrate/migrate/v4"
|
||||||
|
"github.com/golang-migrate/migrate/v4/database/sqlite3"
|
||||||
|
"github.com/johejo/golang-migrate-extra/source/iofs"
|
||||||
|
)
|
||||||
|
|
||||||
//go:embed "migrations"
|
//go:embed "migrations"
|
||||||
var Migrations embed.FS
|
var Migrations embed.FS
|
||||||
|
|
||||||
|
type migrateLogger struct {
|
||||||
|
log *log.Logger
|
||||||
|
verbose bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m migrateLogger) Printf(format string, v ...interface{}) {
|
||||||
|
m.log.Printf(format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m migrateLogger) Verbose() bool {
|
||||||
|
return m.verbose
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m migrateLogger) Print(s string) {
|
||||||
|
m.log.Print(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitializeDb(db *sql.DB, log *log.Logger) error {
|
||||||
|
source, err := iofs.New(Migrations, "migrations")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create migration source: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
driver, err := sqlite3.WithInstance(db, &sqlite3.Config{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create migration driver: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := migrate.NewWithInstance("iofs", source, "sqlite3", driver)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create migration instance: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Log = &migrateLogger{log, true}
|
||||||
|
|
||||||
|
err = m.Up()
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, migrate.ErrNoChange) {
|
||||||
|
return fmt.Errorf("running database migration failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Print("no database migrations required")
|
||||||
|
} else {
|
||||||
|
log.Print("applied database migrations")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
213
internal/models/decisions.go
Normal file
213
internal/models/decisions.go
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017-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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VoteType uint8
|
||||||
|
|
||||||
|
const unknownVariant = "unknown"
|
||||||
|
|
||||||
|
const (
|
||||||
|
VoteTypeMotion VoteType = 0
|
||||||
|
VoteTypeVeto VoteType = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
var voteTypeLabels = map[VoteType]string{
|
||||||
|
VoteTypeMotion: "motion",
|
||||||
|
VoteTypeVeto: "veto",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v VoteType) String() string {
|
||||||
|
if label, ok := voteTypeLabels[v]; ok {
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
|
||||||
|
return unknownVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v VoteType) QuorumAndMajority() (int, float32) {
|
||||||
|
const (
|
||||||
|
majorityDefault = 0.99
|
||||||
|
majorityMotion = 0.50
|
||||||
|
quorumDefault = 1
|
||||||
|
quorumMotion = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
if v == VoteTypeMotion {
|
||||||
|
return quorumMotion, majorityMotion
|
||||||
|
}
|
||||||
|
|
||||||
|
return quorumDefault, majorityDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
type VoteStatus int8
|
||||||
|
|
||||||
|
const (
|
||||||
|
voteStatusDeclined VoteStatus = -1
|
||||||
|
voteStatusPending VoteStatus = 0
|
||||||
|
voteStatusApproved VoteStatus = 1
|
||||||
|
voteStatusWithdrawn VoteStatus = -2
|
||||||
|
)
|
||||||
|
|
||||||
|
var voteStatusLabels = map[VoteStatus]string{
|
||||||
|
voteStatusDeclined: "declined",
|
||||||
|
voteStatusPending: "pending",
|
||||||
|
voteStatusApproved: "approved",
|
||||||
|
voteStatusWithdrawn: "withdrawn",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v VoteStatus) String() string {
|
||||||
|
if label, ok := voteStatusLabels[v]; ok {
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
|
||||||
|
return unknownVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
type VoteChoice int
|
||||||
|
|
||||||
|
const (
|
||||||
|
voteAye VoteChoice = 1
|
||||||
|
voteNaye VoteChoice = -1
|
||||||
|
voteAbstain VoteChoice = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
var voteChoiceLabels = map[VoteChoice]string{
|
||||||
|
voteAye: "aye",
|
||||||
|
voteNaye: "naye",
|
||||||
|
voteAbstain: "abstain",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v VoteChoice) String() string {
|
||||||
|
if label, ok := voteChoiceLabels[v]; ok {
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
|
||||||
|
return unknownVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
type Decision struct {
|
||||||
|
ID int64 `db:"id"`
|
||||||
|
Proposed time.Time
|
||||||
|
Proponent int64 `db:"proponent"`
|
||||||
|
Title string
|
||||||
|
Content string
|
||||||
|
Quorum int
|
||||||
|
Majority int
|
||||||
|
Status VoteStatus
|
||||||
|
Due time.Time
|
||||||
|
Modified time.Time
|
||||||
|
Tag string
|
||||||
|
VoteType VoteType
|
||||||
|
}
|
||||||
|
|
||||||
|
type DecisionModel struct {
|
||||||
|
DB *sqlx.DB
|
||||||
|
InfoLog *log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new decision.
|
||||||
|
func (m *DecisionModel) Create(
|
||||||
|
proponent *Voter,
|
||||||
|
voteType VoteType,
|
||||||
|
title, content string,
|
||||||
|
proposed, due time.Time,
|
||||||
|
) (int64, error) {
|
||||||
|
d := &Decision{
|
||||||
|
Proposed: proposed.UTC(),
|
||||||
|
Proponent: proponent.ID,
|
||||||
|
Title: title,
|
||||||
|
Content: content,
|
||||||
|
Due: due.UTC(),
|
||||||
|
VoteType: voteType,
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := m.DB.NamedExec(`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)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("creating motion failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := result.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("could not get inserted decision id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *DecisionModel) CloseDecisions() error {
|
||||||
|
rows, err := m.DB.NamedQuery(`
|
||||||
|
SELECT decisions.id, decisions.tag, decisions.proponent, decisions.proposed, decisions.title, decisions.content,
|
||||||
|
decisions.votetype, decisions.status, decisions.due, decisions.modified
|
||||||
|
FROM decisions
|
||||||
|
WHERE decisions.status=0 AND :now > due`, struct{ Now time.Time }{Now: time.Now().UTC()})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("fetching closable decisions failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func(rows *sqlx.Rows) {
|
||||||
|
_ = rows.Close()
|
||||||
|
}(rows)
|
||||||
|
|
||||||
|
decisions := make([]*Decision, 0)
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
decision := &Decision{}
|
||||||
|
if err = rows.StructScan(decision); err != nil {
|
||||||
|
return fmt.Errorf("scanning row failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rows.Err() != nil {
|
||||||
|
return fmt.Errorf("row error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decisions = append(decisions, decision)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, decision := range decisions {
|
||||||
|
m.InfoLog.Printf("found closable decision %s", decision.Tag)
|
||||||
|
|
||||||
|
if err = m.Close(decision.Tag); err != nil {
|
||||||
|
return fmt.Errorf("closing decision %s failed: %w", decision.Tag, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *DecisionModel) Close(tag string) error {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *DecisionModel) FindUnVotedDecisionsForVoter(v *Voter) ([]Decision, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
42
internal/models/decisions_test.go
Normal file
42
internal/models/decisions_test.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package models_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDecisionModel_Create(t *testing.T) {
|
||||||
|
testDir := t.TempDir()
|
||||||
|
|
||||||
|
db, err := sql.Open("sqlite3", path.Join(testDir, "test.sqlite"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
dbx := sqlx.NewDb(db, "sqlite3")
|
||||||
|
|
||||||
|
logger := log.New(os.Stdout, "", log.LstdFlags)
|
||||||
|
|
||||||
|
err = internal.InitializeDb(dbx.DB, logger)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
dm := models.DecisionModel{DB: dbx, InfoLog: logger}
|
||||||
|
|
||||||
|
v := &models.Voter{
|
||||||
|
ID: 1, // sqlite does not check referential integrity. Might fail with a foreign key index.
|
||||||
|
Name: "test voter",
|
||||||
|
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))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, id)
|
||||||
|
}
|
36
internal/models/voters.go
Normal file
36
internal/models/voters.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017-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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Voter struct {
|
||||||
|
ID int64 `db:"id"`
|
||||||
|
Name string
|
||||||
|
Reminder string // reminder email address
|
||||||
|
}
|
||||||
|
|
||||||
|
type VoterModel struct {
|
||||||
|
DB *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m VoterModel) GetReminderVoters() ([]Voter, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
Loading…
Reference in a new issue