You've already forked golang-saas-starter-kit
mirror of
https://github.com/raseels-repos/golang-saas-starter-kit.git
synced 2025-06-15 00:15:15 +02:00
improved unique validation function to support multiple fields.
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||||
// This file was generated by geeks-accelerator/swag at
|
// This file was generated by geeks-accelerator/swag at
|
||||||
// 2019-08-07 19:21:01.42416 -0800 AKDT m=+430.010377170
|
// 2019-08-07 20:20:04.308889 -0800 AKDT m=+439.591671736
|
||||||
|
|
||||||
package docs
|
package docs
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ func (h *Signup) Step1(ctx context.Context, w http.ResponseWriter, r *http.Reque
|
|||||||
|
|
||||||
data["form"] = req
|
data["form"] = req
|
||||||
|
|
||||||
if verr, ok := weberror.NewValidationError(ctx, signup.Validator().Struct(signup.SignupRequest{})); ok {
|
if verr, ok := weberror.NewValidationError(ctx, webcontext.Validator().Struct(signup.SignupRequest{})); ok {
|
||||||
data["validationDefaults"] = verr.(*weberror.Error)
|
data["validationDefaults"] = verr.(*weberror.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
4
go.mod
4
go.mod
@ -9,9 +9,9 @@ require (
|
|||||||
github.com/dustin/go-humanize v1.0.0
|
github.com/dustin/go-humanize v1.0.0
|
||||||
github.com/fatih/camelcase v1.0.0
|
github.com/fatih/camelcase v1.0.0
|
||||||
github.com/fatih/structtag v1.0.0
|
github.com/fatih/structtag v1.0.0
|
||||||
github.com/geeks-accelerator/files v0.0.0-20190704085106-630677cd5c14 // indirect
|
github.com/geeks-accelerator/files v0.0.0-20190704085106-630677cd5c14
|
||||||
github.com/geeks-accelerator/sqlxmigrate v0.0.0-20190527223850-4a863a2d30db
|
github.com/geeks-accelerator/sqlxmigrate v0.0.0-20190527223850-4a863a2d30db
|
||||||
github.com/geeks-accelerator/swag v1.6.3 // indirect
|
github.com/geeks-accelerator/swag v1.6.3
|
||||||
github.com/go-openapi/spec v0.19.2 // indirect
|
github.com/go-openapi/spec v0.19.2 // indirect
|
||||||
github.com/go-openapi/swag v0.19.4 // indirect
|
github.com/go-openapi/swag v0.19.4 // indirect
|
||||||
github.com/go-playground/locales v0.12.1
|
github.com/go-playground/locales v0.12.1
|
||||||
|
@ -8,12 +8,12 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pborman/uuid"
|
|
||||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror"
|
|
||||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web"
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web"
|
||||||
"github.com/pkg/errors"
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror"
|
||||||
"github.com/geeks-accelerator/files"
|
"github.com/geeks-accelerator/files"
|
||||||
"github.com/geeks-accelerator/swag"
|
"github.com/geeks-accelerator/swag"
|
||||||
|
"github.com/pborman/uuid"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -91,23 +91,6 @@ func init() {
|
|||||||
zh_translations.RegisterDefaultTranslations(validate, transZh)
|
zh_translations.RegisterDefaultTranslations(validate, transZh)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
validate.RegisterTranslation("required", transEn, func(ut ut.Translator) error {
|
|
||||||
return ut.Add("required", "{0} must have a value!", true) // see universal-translator for details
|
|
||||||
}, func(ut ut.Translator, fe validator.FieldError) string {
|
|
||||||
t, _ := ut.T("required", fe.Field())
|
|
||||||
|
|
||||||
return t
|
|
||||||
})
|
|
||||||
|
|
||||||
validate.RegisterTranslation("required", transFr, func(ut ut.Translator) error {
|
|
||||||
return ut.Add("required", "{0} must have a value!", true) // see universal-translator for details
|
|
||||||
}, func(ut ut.Translator, fe validator.FieldError) string {
|
|
||||||
t, _ := ut.T("required", fe.Field())
|
|
||||||
|
|
||||||
return t
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
validate.RegisterTranslation("unique", transEn, func(ut ut.Translator) error {
|
validate.RegisterTranslation("unique", transEn, func(ut ut.Translator) error {
|
||||||
return ut.Add("unique", "{0} must be unique", true) // see universal-translator for details
|
return ut.Add("unique", "{0} must be unique", true) // see universal-translator for details
|
||||||
}, func(ut ut.Translator, fe validator.FieldError) string {
|
}, func(ut ut.Translator, fe validator.FieldError) string {
|
||||||
@ -122,14 +105,20 @@ func init() {
|
|||||||
t, _ := ut.T("unique", fe.Field())
|
t, _ := ut.T("unique", fe.Field())
|
||||||
|
|
||||||
return t
|
return t
|
||||||
})*/
|
})
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ctxKeyTagUnique represents the type of unique value for the context key used by the validation function.
|
||||||
type ctxKeyTagUnique int
|
type ctxKeyTagUnique int
|
||||||
|
|
||||||
|
// KeyTagUnique defines the value used in the context key for storing the uniqueness of a field.
|
||||||
const KeyTagUnique ctxKeyTagUnique = 1
|
const KeyTagUnique ctxKeyTagUnique = 1
|
||||||
|
|
||||||
|
// KeyTagFieldValue defined the struct+field name used as the context key for storing whether the field is unique
|
||||||
|
// or not that is used by the custom validation function registered in newValidator.
|
||||||
|
type KeyTagFieldValue string
|
||||||
|
|
||||||
// newValidator inits a new validator with custom settings.
|
// newValidator inits a new validator with custom settings.
|
||||||
func newValidator() *validator.Validate {
|
func newValidator() *validator.Validate {
|
||||||
var v = validator.New()
|
var v = validator.New()
|
||||||
@ -145,20 +134,24 @@ func newValidator() *validator.Validate {
|
|||||||
return "{{" + name + "}}"
|
return "{{" + name + "}}"
|
||||||
})
|
})
|
||||||
|
|
||||||
// Empty method that can be overwritten in business logic packages to prevent web.Decode from failing.
|
// Custom Validation function for the unique tag that checks the context to determine if a field is
|
||||||
f := func(fl validator.FieldLevel) bool {
|
// unique or not. First it will check a field specific context key and if that doesn't work, it will
|
||||||
return false
|
// check a shared context key.
|
||||||
}
|
|
||||||
v.RegisterValidation("unique", f)
|
|
||||||
|
|
||||||
fctx := func(ctx context.Context, fl validator.FieldLevel) bool {
|
fctx := func(ctx context.Context, fl validator.FieldLevel) bool {
|
||||||
if fl.Field().String() == "invalid" {
|
if fl.Field().String() == "invalid" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
cv := ctx.Value(KeyTagUnique)
|
// First check to see if a value is set for the specific field.
|
||||||
|
fk := KeyTagFieldValue(fl.Parent().Type().String() + "." + fl.StructFieldName())
|
||||||
|
cv := ctx.Value(fk)
|
||||||
|
|
||||||
|
// Second check if the default unique key is set in context.
|
||||||
if cv == nil {
|
if cv == nil {
|
||||||
return false
|
cv := ctx.Value(KeyTagUnique)
|
||||||
|
if cv == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, ok := cv.(bool); ok {
|
if v, ok := cv.(bool); ok {
|
||||||
@ -172,6 +165,14 @@ func newValidator() *validator.Validate {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContextAddUniqueValue allows multiple fields to be validated using the same unique validation function by added
|
||||||
|
// the unique value to the context using the struct name and field name.
|
||||||
|
func ContextAddUniqueValue(ctx context.Context, req interface{}, fieldName string, unique bool) context.Context {
|
||||||
|
fk := KeyTagFieldValue(reflect.TypeOf(req).String() + "." + fieldName)
|
||||||
|
|
||||||
|
return context.WithValue(ctx, fk, unique)
|
||||||
|
}
|
||||||
|
|
||||||
// Validator returns the current init validator.
|
// Validator returns the current init validator.
|
||||||
func Validator() *validator.Validate {
|
func Validator() *validator.Validate {
|
||||||
return validate
|
return validate
|
||||||
|
@ -14,7 +14,7 @@ type SignupRequest struct {
|
|||||||
|
|
||||||
// SignupAccount defined the details needed for account.
|
// SignupAccount defined the details needed for account.
|
||||||
type SignupAccount struct {
|
type SignupAccount struct {
|
||||||
Name string `json:"name" validate:"required,unique-name" example:"Company {RANDOM_UUID}"`
|
Name string `json:"name" validate:"required,unique" example:"Company {RANDOM_UUID}"`
|
||||||
Address1 string `json:"address1" validate:"required" example:"221 Tatitlek Ave"`
|
Address1 string `json:"address1" validate:"required" example:"221 Tatitlek Ave"`
|
||||||
Address2 string `json:"address2" validate:"omitempty" example:"Box #1832"`
|
Address2 string `json:"address2" validate:"omitempty" example:"Box #1832"`
|
||||||
City string `json:"city" validate:"required" example:"Valdez"`
|
City string `json:"city" validate:"required" example:"Valdez"`
|
||||||
@ -28,7 +28,7 @@ type SignupAccount struct {
|
|||||||
type SignupUser struct {
|
type SignupUser struct {
|
||||||
FirstName string `json:"first_name" validate:"required" example:"Gabi"`
|
FirstName string `json:"first_name" validate:"required" example:"Gabi"`
|
||||||
LastName string `json:"last_name" validate:"required" example:"May"`
|
LastName string `json:"last_name" validate:"required" example:"May"`
|
||||||
Email string `json:"email" validate:"required,email,unique-email" example:"{RANDOM_EMAIL}"`
|
Email string `json:"email" validate:"required,email,unique" example:"{RANDOM_EMAIL}"`
|
||||||
Password string `json:"password" validate:"required" example:"SecretString"`
|
Password string `json:"password" validate:"required" example:"SecretString"`
|
||||||
PasswordConfirm string `json:"password_confirm" validate:"required,eqfield=Password" example:"SecretString"`
|
PasswordConfirm string `json:"password_confirm" validate:"required,eqfield=Password" example:"SecretString"`
|
||||||
}
|
}
|
||||||
|
@ -11,62 +11,8 @@ import (
|
|||||||
"geeks-accelerator/oss/saas-starter-kit/internal/user_account"
|
"geeks-accelerator/oss/saas-starter-kit/internal/user_account"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
|
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
|
||||||
"gopkg.in/go-playground/validator.v9"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ctxKeyTagUniqueName int
|
|
||||||
|
|
||||||
const KeyTagUniqueName ctxKeyTagUniqueName = 1
|
|
||||||
|
|
||||||
type ctxKeyTagUniqueEmail int
|
|
||||||
|
|
||||||
const KeyTagUniqueEmail ctxKeyTagUniqueEmail = 1
|
|
||||||
|
|
||||||
// validate holds the settings and caches for validating request struct values.
|
|
||||||
var validate *validator.Validate
|
|
||||||
|
|
||||||
// Validator returns the current init validator.
|
|
||||||
func Validator() *validator.Validate {
|
|
||||||
if validate == nil {
|
|
||||||
validate = webcontext.Validator()
|
|
||||||
|
|
||||||
validate.RegisterValidationCtx("unique-name", func(ctx context.Context, fl validator.FieldLevel) bool {
|
|
||||||
if fl.Field().String() == "invalid" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
cv := ctx.Value(KeyTagUniqueName)
|
|
||||||
if cv == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, ok := cv.(bool); ok {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
validate.RegisterValidationCtx("unique-email", func(ctx context.Context, fl validator.FieldLevel) bool {
|
|
||||||
if fl.Field().String() == "invalid" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
cv := ctx.Value(KeyTagUniqueEmail)
|
|
||||||
if cv == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, ok := cv.(bool); ok {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return validate
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signup performs the steps needed to create a new account, new user and then associate
|
// Signup performs the steps needed to create a new account, new user and then associate
|
||||||
// both records with a new user_account entry.
|
// both records with a new user_account entry.
|
||||||
func Signup(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, req SignupRequest, now time.Time) (*SignupResult, error) {
|
func Signup(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, req SignupRequest, now time.Time) (*SignupResult, error) {
|
||||||
@ -78,17 +24,17 @@ func Signup(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, req Signup
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ctx = context.WithValue(ctx, KeyTagUniqueEmail, uniqEmail)
|
ctx = webcontext.ContextAddUniqueValue(ctx, req.User, "Email", uniqEmail)
|
||||||
|
|
||||||
// Validate the account name is unique in the database.
|
// Validate the account name is unique in the database.
|
||||||
uniqName, err := account.UniqueName(ctx, dbConn, req.Account.Name, "")
|
uniqName, err := account.UniqueName(ctx, dbConn, req.Account.Name, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ctx = context.WithValue(ctx, KeyTagUniqueName, uniqName)
|
ctx = webcontext.ContextAddUniqueValue(ctx, req.Account, "Name", uniqName)
|
||||||
|
|
||||||
// Validate the request.
|
// Validate the request.
|
||||||
err = Validator().StructCtx(ctx, req)
|
err = webcontext.Validator().StructCtx(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -113,8 +113,8 @@ func TestSignupFull(t *testing.T) {
|
|||||||
Zipcode: "99686",
|
Zipcode: "99686",
|
||||||
},
|
},
|
||||||
User: SignupUser{
|
User: SignupUser{
|
||||||
FirstName: "Lee",
|
FirstName: "Lee",
|
||||||
LastName: "Brown",
|
LastName: "Brown",
|
||||||
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
|
||||||
Password: "akTechFr0n!ier",
|
Password: "akTechFr0n!ier",
|
||||||
PasswordConfirm: "akTechFr0n!ier",
|
PasswordConfirm: "akTechFr0n!ier",
|
||||||
@ -163,7 +163,7 @@ func TestSignupFull(t *testing.T) {
|
|||||||
|
|
||||||
// Verify that the user can be authenticated with the updated password.
|
// Verify that the user can be authenticated with the updated password.
|
||||||
_, err = user_auth.Authenticate(ctx, test.MasterDB, tknGen, user_auth.AuthenticateRequest{
|
_, err = user_auth.Authenticate(ctx, test.MasterDB, tknGen, user_auth.AuthenticateRequest{
|
||||||
Email:res.User.Email,
|
Email: res.User.Email,
|
||||||
Password: req.User.Password,
|
Password: req.User.Password,
|
||||||
}, time.Hour, now)
|
}, time.Hour, now)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Reference in New Issue
Block a user