diff --git a/cmd/boardvoting/config.go b/cmd/boardvoting/config.go index 4c61330..a619900 100644 --- a/cmd/boardvoting/config.go +++ b/cmd/boardvoting/config.go @@ -100,7 +100,9 @@ func parseConfig(configFile string) (*Config, error) { } if len(config.CsrfKey) != csrfKeyLength { - return nil, fmt.Errorf("CSRF key must be exactly %d bytes long but is %d bytes long", csrfKeyLength, len(config.CsrfKey)) + return nil, fmt.Errorf( + "CSRF key must be exactly %d bytes long but is %d bytes long", csrfKeyLength, len(config.CsrfKey), + ) } return config, nil diff --git a/cmd/boardvoting/handlers.go b/cmd/boardvoting/handlers.go index 74d5845..7f608ce 100644 --- a/cmd/boardvoting/handlers.go +++ b/cmd/boardvoting/handlers.go @@ -29,12 +29,13 @@ import ( "strings" "time" - "git.cacert.org/cacert-boardvoting/internal/models" - "git.cacert.org/cacert-boardvoting/internal/validator" - "git.cacert.org/cacert-boardvoting/ui" "github.com/Masterminds/sprig/v3" "github.com/gorilla/csrf" "github.com/julienschmidt/httprouter" + + "git.cacert.org/cacert-boardvoting/internal/models" + "git.cacert.org/cacert-boardvoting/internal/validator" + "git.cacert.org/cacert-boardvoting/ui" ) func newTemplateCache() (map[string]*template.Template, error) { @@ -295,12 +296,36 @@ func (app *application) newMotionSubmit(w http.ResponseWriter, r *http.Request) return } - form.CheckField(validator.NotBlank(form.Title), "title", "This field cannot be blank") - form.CheckField(validator.MinChars(form.Title, minimumTitleLength), "title", fmt.Sprintf("This field must be at least %d characters long", minimumTitleLength)) - form.CheckField(validator.MaxChars(form.Title, maximumTitleLength), "title", fmt.Sprintf("This field must be at most %d characters long", maximumTitleLength)) - form.CheckField(validator.NotBlank(form.Content), "content", "This field cannot be blank") - form.CheckField(validator.MinChars(form.Content, minimumContentLength), "content", fmt.Sprintf("This field must be at least %d characters long", minimumContentLength)) - form.CheckField(validator.MaxChars(form.Content, maximumContentLength), "content", fmt.Sprintf("This field must be at most %d characters long", maximumContentLength)) + form.CheckField( + validator.NotBlank(form.Title), + "title", + "This field cannot be blank", + ) + form.CheckField( + validator.MinChars(form.Title, minimumTitleLength), + "title", + fmt.Sprintf("This field must be at least %d characters long", minimumTitleLength), + ) + form.CheckField( + validator.MaxChars(form.Title, maximumTitleLength), + "title", + fmt.Sprintf("This field must be at most %d characters long", maximumTitleLength), + ) + form.CheckField( + validator.NotBlank(form.Content), + "content", + "This field cannot be blank", + ) + form.CheckField( + validator.MinChars(form.Content, minimumContentLength), + "content", + fmt.Sprintf("This field must be at least %d characters long", minimumContentLength), + ) + form.CheckField( + validator.MaxChars(form.Content, maximumContentLength), + "content", + fmt.Sprintf("This field must be at most %d characters long", maximumContentLength), + ) form.CheckField(validator.PermittedStr( form.Due, "+3 days", diff --git a/cmd/boardvoting/main.go b/cmd/boardvoting/main.go index 82d8279..a34617e 100644 --- a/cmd/boardvoting/main.go +++ b/cmd/boardvoting/main.go @@ -41,6 +41,8 @@ import ( "git.cacert.org/cacert-boardvoting/internal/models" ) +const sessionHours = 12 + var ( version = "undefined" commit = "undefined" @@ -95,7 +97,7 @@ func main() { sessionManager := scs.New() sessionManager.Store = sqlite3store.New(db.DB) - sessionManager.Lifetime = 12 * time.Hour + sessionManager.Lifetime = sessionHours * time.Hour app := &application{ errorLog: errorLog, @@ -145,7 +147,12 @@ func setupFormDecoder() *form.Decoder { decoder := form.NewDecoder() decoder.RegisterCustomTypeFunc(func(values []string) (interface{}, error) { - return models.VoteTypeFromString(values[0]) + v, err := models.VoteTypeFromString(values[0]) + if err != nil { + return nil, fmt.Errorf("could not convert value %s: %w", values[0], err) + } + + return v, nil }, new(models.VoteType)) return decoder diff --git a/internal/models/motions.go b/internal/models/motions.go index ae93f59..bf1516d 100644 --- a/internal/models/motions.go +++ b/internal/models/motions.go @@ -20,6 +20,7 @@ package models import ( "context" "database/sql" + "database/sql/driver" "errors" "fmt" "log" @@ -55,9 +56,9 @@ func VoteTypeFromString(label string) (*VoteType, error) { return nil, fmt.Errorf("unknown vote type %s", label) } -func VoteTypeFromUint8(id uint8) (*VoteType, error) { +func VoteTypeFromInt(id int64) (*VoteType, error) { for _, vt := range []*VoteType{VoteTypeMotion, VoteTypeVeto} { - if vt.id == id { + if int64(vt.id) == id { return vt, nil } } @@ -65,6 +66,26 @@ func VoteTypeFromUint8(id uint8) (*VoteType, error) { return nil, fmt.Errorf("unknown vote type id %d", id) } +func (v *VoteType) Scan(src any) error { + value, ok := src.(int64) + if !ok { + return fmt.Errorf("could not cast %v of %T to uint8", src, src) + } + + vt, err := VoteTypeFromInt(value) + if err != nil { + return err + } + + *v = *vt + + return nil +} + +func (v *VoteType) Value() (driver.Value, error) { + return int64(v.id), nil +} + func (v *VoteType) QuorumAndMajority() (int, float32) { const ( majorityDefault = 0.99 @@ -162,7 +183,7 @@ type Motion struct { Due time.Time Modified time.Time Tag string - VoteType VoteType + VoteType *VoteType } type ClosedMotion struct { @@ -180,7 +201,7 @@ type MotionModel struct { func (m *MotionModel) Create( ctx context.Context, proponent *Voter, - voteType VoteType, + voteType *VoteType, title, content string, proposed, due time.Time, ) (int64, error) { @@ -400,7 +421,7 @@ type MotionForDisplay struct { Proposed time.Time Title string Content string - Type VoteType `db:"votetype"` + Type *VoteType `db:"votetype"` Status VoteStatus Due time.Time Modified time.Time diff --git a/internal/validator/validator.go b/internal/validator/validator.go index 4f1fdcb..5f3f4c8 100644 --- a/internal/validator/validator.go +++ b/internal/validator/validator.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 validator import ( @@ -41,16 +58,6 @@ func MinChars(value string, n int) bool { return utf8.RuneCountInString(value) >= n } -func PermittedInt(value int, permittedValues ...int) bool { - for i := range permittedValues { - if value == permittedValues[i] { - return true - } - } - - return false -} - func PermittedStr(value string, permittedValues ...string) bool { for i := range permittedValues { if value == permittedValues[i] {