feat(07-01): add bcrypt, locale override, and validation rules

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jakub Zych
2026-09-22 13:39:25 +02:00
parent bccd7f8f35
commit 8fcaff77cf
6 changed files with 95 additions and 15 deletions

View File

@@ -26,8 +26,7 @@ var validateOnce = validator.New(validator.WithRequiredStructEnabled())
func Validate(ctx context.Context, tx *gorm.DB, model any, rules map[string]string, values map[string]any, tr *phrasebook.Translator) (map[string][]string, error) {
out := map[string][]string{}
for field, rule := range rules {
val, _ := values[field]
msgs, err := validateField(ctx, tx, model, field, rule, val, tr)
msgs, err := validateField(ctx, tx, model, field, rule, values, tr)
if err != nil {
return nil, err
}
@@ -41,7 +40,8 @@ func Validate(ctx context.Context, tx *gorm.DB, model any, rules map[string]stri
return out, nil
}
func validateField(ctx context.Context, tx *gorm.DB, model any, field, rule string, val any, tr *phrasebook.Translator) ([]string, error) {
func validateField(ctx context.Context, tx *gorm.DB, model any, field, rule string, values map[string]any, tr *phrasebook.Translator) ([]string, error) {
val := values[field]
tokens := splitRule(rule)
nullable := false
required := false
@@ -82,6 +82,29 @@ func validateField(ctx context.Context, tx *gorm.DB, model any, field, rule stri
uniqueTable = arg
case "boolean":
// Go's bool field type already enforces this; treat as a type-check no-op.
case "email":
tags = append(tags, "email")
case "confirmed":
if fmt.Sprint(val) != fmt.Sprint(values[field+"_confirmation"]) {
return []string{validateMessage(ctx, tr, "confirmed", field, nil)}, nil
}
case "different":
if fmt.Sprint(val) == fmt.Sprint(values[arg]) {
return []string{validateMessage(ctx, tr, "different", field, map[string]string{"other": arg})}, nil
}
case "mimes":
got := strings.TrimPrefix(strings.ToLower(strings.TrimSpace(fmt.Sprint(val))), ".")
match := false
for _, ext := range strings.Split(arg, ",") {
want := strings.TrimPrefix(strings.ToLower(strings.TrimSpace(ext)), ".")
if got != "" && got != "<nil>" && got == want {
match = true
break
}
}
if !match {
return []string{validateMessage(ctx, tr, "mimes", field, nil)}, nil
}
default:
return nil, fmt.Errorf("lagoon: unrecognized validation rule %q", tok)
}
@@ -382,6 +405,14 @@ func validateMessage(ctx context.Context, tr *phrasebook.Translator, rule, field
return "The " + field + " must be at least " + params["min"] + "."
case "oneof":
return "The selected " + field + " is invalid."
case "email":
return "The " + field + " must be a valid email address."
case "confirmed":
return "The " + field + " confirmation does not match."
case "different":
return "The " + field + " and " + params["other"] + " must be different."
case "mimes":
return "The " + field + " must be a file of the allowed types."
default:
return "The " + field + " is invalid."
}