166 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			166 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
| package forms
 | |
| 
 | |
| import (
 | |
| 	"strings"
 | |
| 
 | |
| 	validation "github.com/go-ozzo/ozzo-validation/v4"
 | |
| 	"github.com/go-ozzo/ozzo-validation/v4/is"
 | |
| 	"github.com/pocketbase/pocketbase/core"
 | |
| 	"github.com/pocketbase/pocketbase/daos"
 | |
| 	"github.com/pocketbase/pocketbase/forms/validators"
 | |
| 	"github.com/pocketbase/pocketbase/models"
 | |
| 	"github.com/pocketbase/pocketbase/tools/list"
 | |
| 	"github.com/pocketbase/pocketbase/tools/types"
 | |
| )
 | |
| 
 | |
| // UserUpsert specifies a [models.User] upsert (create/update) form.
 | |
| type UserUpsert struct {
 | |
| 	config UserUpsertConfig
 | |
| 	user   *models.User
 | |
| 
 | |
| 	Id              string `form:"id" json:"id"`
 | |
| 	Email           string `form:"email" json:"email"`
 | |
| 	Password        string `form:"password" json:"password"`
 | |
| 	PasswordConfirm string `form:"passwordConfirm" json:"passwordConfirm"`
 | |
| }
 | |
| 
 | |
| // UserUpsertConfig is the [UserUpsert] factory initializer config.
 | |
| //
 | |
| // NB! App is required struct member.
 | |
| type UserUpsertConfig struct {
 | |
| 	App core.App
 | |
| 	Dao *daos.Dao
 | |
| }
 | |
| 
 | |
| // NewUserUpsert creates a new [UserUpsert] form with initializer
 | |
| // config created from the provided [core.App] instance
 | |
| // (for create you could pass a pointer to an empty User - `&models.User{}`).
 | |
| //
 | |
| // If you want to submit the form as part of another transaction, use
 | |
| // [NewUserEmailChangeConfirmWithConfig] with explicitly set Dao.
 | |
| func NewUserUpsert(app core.App, user *models.User) *UserUpsert {
 | |
| 	return NewUserUpsertWithConfig(UserUpsertConfig{
 | |
| 		App: app,
 | |
| 	}, user)
 | |
| }
 | |
| 
 | |
| // NewUserUpsertWithConfig creates a new [UserUpsert] form with the provided
 | |
| // config and [models.User] instance or panics on invalid configuration
 | |
| // (for create you could pass a pointer to an empty User - `&models.User{}`).
 | |
| func NewUserUpsertWithConfig(config UserUpsertConfig, user *models.User) *UserUpsert {
 | |
| 	form := &UserUpsert{
 | |
| 		config: config,
 | |
| 		user:   user,
 | |
| 	}
 | |
| 
 | |
| 	if form.config.App == nil || form.user == nil {
 | |
| 		panic("Invalid initializer config or nil upsert model.")
 | |
| 	}
 | |
| 
 | |
| 	if form.config.Dao == nil {
 | |
| 		form.config.Dao = form.config.App.Dao()
 | |
| 	}
 | |
| 
 | |
| 	// load defaults
 | |
| 	form.Id = user.Id
 | |
| 	form.Email = user.Email
 | |
| 
 | |
| 	return form
 | |
| }
 | |
| 
 | |
| // Validate makes the form validatable by implementing [validation.Validatable] interface.
 | |
| func (form *UserUpsert) Validate() error {
 | |
| 	return validation.ValidateStruct(form,
 | |
| 		validation.Field(
 | |
| 			&form.Id,
 | |
| 			validation.When(
 | |
| 				form.user.IsNew(),
 | |
| 				validation.Length(models.DefaultIdLength, models.DefaultIdLength),
 | |
| 				validation.Match(idRegex),
 | |
| 			).Else(validation.In(form.user.Id)),
 | |
| 		),
 | |
| 		validation.Field(
 | |
| 			&form.Email,
 | |
| 			validation.Required,
 | |
| 			validation.Length(1, 255),
 | |
| 			is.EmailFormat,
 | |
| 			validation.By(form.checkEmailDomain),
 | |
| 			validation.By(form.checkUniqueEmail),
 | |
| 		),
 | |
| 		validation.Field(
 | |
| 			&form.Password,
 | |
| 			validation.When(form.user.IsNew(), validation.Required),
 | |
| 			validation.Length(form.config.App.Settings().EmailAuth.MinPasswordLength, 100),
 | |
| 		),
 | |
| 		validation.Field(
 | |
| 			&form.PasswordConfirm,
 | |
| 			validation.When(form.user.IsNew() || form.Password != "", validation.Required),
 | |
| 			validation.By(validators.Compare(form.Password)),
 | |
| 		),
 | |
| 	)
 | |
| }
 | |
| 
 | |
| func (form *UserUpsert) checkUniqueEmail(value any) error {
 | |
| 	v, _ := value.(string)
 | |
| 
 | |
| 	if v == "" || form.config.Dao.IsUserEmailUnique(v, form.user.Id) {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	return validation.NewError("validation_user_email_exists", "User email already exists.")
 | |
| }
 | |
| 
 | |
| func (form *UserUpsert) checkEmailDomain(value any) error {
 | |
| 	val, _ := value.(string)
 | |
| 	if val == "" {
 | |
| 		return nil // nothing to check
 | |
| 	}
 | |
| 
 | |
| 	domain := val[strings.LastIndex(val, "@")+1:]
 | |
| 	only := form.config.App.Settings().EmailAuth.OnlyDomains
 | |
| 	except := form.config.App.Settings().EmailAuth.ExceptDomains
 | |
| 
 | |
| 	// only domains check
 | |
| 	if len(only) > 0 && !list.ExistInSlice(domain, only) {
 | |
| 		return validation.NewError("validation_email_domain_not_allowed", "Email domain is not allowed.")
 | |
| 	}
 | |
| 
 | |
| 	// except domains check
 | |
| 	if len(except) > 0 && list.ExistInSlice(domain, except) {
 | |
| 		return validation.NewError("validation_email_domain_not_allowed", "Email domain is not allowed.")
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Submit validates the form and upserts the form user model.
 | |
| //
 | |
| // You can optionally provide a list of InterceptorFunc to further
 | |
| // modify the form behavior before persisting it.
 | |
| func (form *UserUpsert) Submit(interceptors ...InterceptorFunc) error {
 | |
| 	if err := form.Validate(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if form.Password != "" {
 | |
| 		form.user.SetPassword(form.Password)
 | |
| 	}
 | |
| 
 | |
| 	// custom insertion id can be set only on create
 | |
| 	if form.user.IsNew() && form.Id != "" {
 | |
| 		form.user.MarkAsNew()
 | |
| 		form.user.SetId(form.Id)
 | |
| 	}
 | |
| 
 | |
| 	if !form.user.IsNew() && form.Email != form.user.Email {
 | |
| 		form.user.Verified = false
 | |
| 		form.user.LastVerificationSentAt = types.DateTime{} // reset
 | |
| 	}
 | |
| 
 | |
| 	form.user.Email = form.Email
 | |
| 
 | |
| 	return runInterceptors(func() error {
 | |
| 		return form.config.Dao.SaveUser(form.user)
 | |
| 	}, interceptors...)
 | |
| }
 |