Implement database mapping for vote types
This commit is contained in:
parent
e1af6876c1
commit
aa3a1b0cc7
5 changed files with 89 additions and 27 deletions
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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] {
|
||||
|
|
Loading…
Reference in a new issue