added WithConfig factory to all forms
This commit is contained in:
		
							parent
							
								
									b0ca9b2f1b
								
							
						
					
					
						commit
						a426484916
					
				| 
						 | 
				
			
			@ -10,7 +10,7 @@ import (
 | 
			
		|||
	"github.com/pocketbase/pocketbase/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AdminLogin defines an admin email/pass login form.
 | 
			
		||||
// AdminLogin specifies an admin email/pass login form.
 | 
			
		||||
type AdminLogin struct {
 | 
			
		||||
	config AdminLoginConfig
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -20,20 +20,20 @@ type AdminLogin struct {
 | 
			
		|||
 | 
			
		||||
// AdminLoginConfig is the [AdminLogin] factory initializer config.
 | 
			
		||||
//
 | 
			
		||||
// NB! Dao is a required struct member.
 | 
			
		||||
// NB! App is a required struct member.
 | 
			
		||||
type AdminLoginConfig struct {
 | 
			
		||||
	Dao *daos.Dao
 | 
			
		||||
	App   core.App
 | 
			
		||||
	TxDao *daos.Dao
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAdminLogin creates a new [AdminLogin] form with initializer
 | 
			
		||||
// config created from the provided [core.App] instance.
 | 
			
		||||
//
 | 
			
		||||
// This factory method is used primarily for convenience (and backward compatibility).
 | 
			
		||||
// If you want to submit the form as part of another transaction, use
 | 
			
		||||
// [NewCollectionUpsertWithConfig] with Dao configured to your txDao.
 | 
			
		||||
// [NewAdminLoginWithConfig] with explicitly set TxDao.
 | 
			
		||||
func NewAdminLogin(app core.App) *AdminLogin {
 | 
			
		||||
	return NewAdminLoginWithConfig(AdminLoginConfig{
 | 
			
		||||
		Dao: app.Dao(),
 | 
			
		||||
		App: app,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -42,8 +42,12 @@ func NewAdminLogin(app core.App) *AdminLogin {
 | 
			
		|||
func NewAdminLoginWithConfig(config AdminLoginConfig) *AdminLogin {
 | 
			
		||||
	form := &AdminLogin{config: config}
 | 
			
		||||
 | 
			
		||||
	if form.config.Dao == nil {
 | 
			
		||||
		panic("Invalid initializer config.")
 | 
			
		||||
	if form.config.App == nil {
 | 
			
		||||
		panic("Missing required config.App instance.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.TxDao == nil {
 | 
			
		||||
		form.config.TxDao = form.config.App.Dao()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return form
 | 
			
		||||
| 
						 | 
				
			
			@ -64,7 +68,7 @@ func (form *AdminLogin) Submit() (*models.Admin, error) {
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	admin, err := form.config.Dao.FindAdminByEmail(form.Email)
 | 
			
		||||
	admin, err := form.config.TxDao.FindAdminByEmail(form.Email)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,24 +3,53 @@ package forms
 | 
			
		|||
import (
 | 
			
		||||
	validation "github.com/go-ozzo/ozzo-validation/v4"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/core"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/daos"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/forms/validators"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AdminPasswordResetConfirm defines an admin password reset confirmation form.
 | 
			
		||||
// AdminPasswordResetConfirm specifies an admin password reset confirmation form.
 | 
			
		||||
type AdminPasswordResetConfirm struct {
 | 
			
		||||
	app core.App
 | 
			
		||||
	config AdminPasswordResetConfirmConfig
 | 
			
		||||
 | 
			
		||||
	Token           string `form:"token" json:"token"`
 | 
			
		||||
	Password        string `form:"password" json:"password"`
 | 
			
		||||
	PasswordConfirm string `form:"passwordConfirm" json:"passwordConfirm"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAdminPasswordResetConfirm creates new admin password reset confirmation form.
 | 
			
		||||
// AdminPasswordResetConfirmConfig is the [AdminPasswordResetConfirm] factory initializer config.
 | 
			
		||||
//
 | 
			
		||||
// NB! App is required struct member.
 | 
			
		||||
type AdminPasswordResetConfirmConfig struct {
 | 
			
		||||
	App   core.App
 | 
			
		||||
	TxDao *daos.Dao
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAdminPasswordResetConfirm creates a new [AdminPasswordResetConfirm]
 | 
			
		||||
// form with initializer config created from the provided [core.App] instance.
 | 
			
		||||
//
 | 
			
		||||
// If you want to submit the form as part of another transaction, use
 | 
			
		||||
// [NewAdminPasswordResetConfirmWithConfig] with explicitly set TxDao.
 | 
			
		||||
func NewAdminPasswordResetConfirm(app core.App) *AdminPasswordResetConfirm {
 | 
			
		||||
	return &AdminPasswordResetConfirm{
 | 
			
		||||
		app: app,
 | 
			
		||||
	return NewAdminPasswordResetConfirmWithConfig(AdminPasswordResetConfirmConfig{
 | 
			
		||||
		App: app,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAdminPasswordResetConfirmWithConfig creates a new [AdminPasswordResetConfirm]
 | 
			
		||||
// form with the provided config or panics on invalid configuration.
 | 
			
		||||
func NewAdminPasswordResetConfirmWithConfig(config AdminPasswordResetConfirmConfig) *AdminPasswordResetConfirm {
 | 
			
		||||
	form := &AdminPasswordResetConfirm{config: config}
 | 
			
		||||
 | 
			
		||||
	if form.config.App == nil {
 | 
			
		||||
		panic("Missing required config.App instance.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.TxDao == nil {
 | 
			
		||||
		form.config.TxDao = form.config.App.Dao()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return form
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate makes the form validatable by implementing [validation.Validatable] interface.
 | 
			
		||||
| 
						 | 
				
			
			@ -38,9 +67,9 @@ func (form *AdminPasswordResetConfirm) checkToken(value any) error {
 | 
			
		|||
		return nil // nothing to check
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	admin, err := form.app.Dao().FindAdminByToken(
 | 
			
		||||
	admin, err := form.config.TxDao.FindAdminByToken(
 | 
			
		||||
		v,
 | 
			
		||||
		form.app.Settings().AdminPasswordResetToken.Secret,
 | 
			
		||||
		form.config.App.Settings().AdminPasswordResetToken.Secret,
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil || admin == nil {
 | 
			
		||||
		return validation.NewError("validation_invalid_token", "Invalid or expired token.")
 | 
			
		||||
| 
						 | 
				
			
			@ -56,9 +85,9 @@ func (form *AdminPasswordResetConfirm) Submit() (*models.Admin, error) {
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	admin, err := form.app.Dao().FindAdminByToken(
 | 
			
		||||
	admin, err := form.config.TxDao.FindAdminByToken(
 | 
			
		||||
		form.Token,
 | 
			
		||||
		form.app.Settings().AdminPasswordResetToken.Secret,
 | 
			
		||||
		form.config.App.Settings().AdminPasswordResetToken.Secret,
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
| 
						 | 
				
			
			@ -68,7 +97,7 @@ func (form *AdminPasswordResetConfirm) Submit() (*models.Admin, error) {
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := form.app.Dao().SaveAdmin(admin); err != nil {
 | 
			
		||||
	if err := form.config.TxDao.SaveAdmin(admin); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,24 +7,53 @@ import (
 | 
			
		|||
	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/mails"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/tools/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AdminPasswordResetRequest defines an admin password reset request form.
 | 
			
		||||
// AdminPasswordResetRequest specifies an admin password reset request form.
 | 
			
		||||
type AdminPasswordResetRequest struct {
 | 
			
		||||
	app             core.App
 | 
			
		||||
	resendThreshold float64
 | 
			
		||||
	config AdminPasswordResetRequestConfig
 | 
			
		||||
 | 
			
		||||
	Email string `form:"email" json:"email"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAdminPasswordResetRequest creates new admin password reset request form.
 | 
			
		||||
// AdminPasswordResetRequestConfig is the [AdminPasswordResetRequest] factory initializer config.
 | 
			
		||||
//
 | 
			
		||||
// NB! App is required struct member.
 | 
			
		||||
type AdminPasswordResetRequestConfig struct {
 | 
			
		||||
	App             core.App
 | 
			
		||||
	TxDao           *daos.Dao
 | 
			
		||||
	ResendThreshold float64 // in seconds
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAdminPasswordResetRequest creates a new [AdminPasswordResetRequest]
 | 
			
		||||
// form with initializer config created from the provided [core.App] instance.
 | 
			
		||||
//
 | 
			
		||||
// If you want to submit the form as part of another transaction, use
 | 
			
		||||
// [NewAdminPasswordResetRequestWithConfig] with explicitly set TxDao.
 | 
			
		||||
func NewAdminPasswordResetRequest(app core.App) *AdminPasswordResetRequest {
 | 
			
		||||
	return &AdminPasswordResetRequest{
 | 
			
		||||
		app:             app,
 | 
			
		||||
		resendThreshold: 120, // 2 min
 | 
			
		||||
	return NewAdminPasswordResetRequestWithConfig(AdminPasswordResetRequestConfig{
 | 
			
		||||
		App:             app,
 | 
			
		||||
		ResendThreshold: 120, // 2min
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAdminPasswordResetRequestWithConfig creates a new [AdminPasswordResetRequest]
 | 
			
		||||
// form with the provided config or panics on invalid configuration.
 | 
			
		||||
func NewAdminPasswordResetRequestWithConfig(config AdminPasswordResetRequestConfig) *AdminPasswordResetRequest {
 | 
			
		||||
	form := &AdminPasswordResetRequest{config: config}
 | 
			
		||||
 | 
			
		||||
	if form.config.App == nil {
 | 
			
		||||
		panic("Missing required config.App instance.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.TxDao == nil {
 | 
			
		||||
		form.config.TxDao = form.config.App.Dao()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return form
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate makes the form validatable by implementing [validation.Validatable] interface.
 | 
			
		||||
| 
						 | 
				
			
			@ -48,23 +77,23 @@ func (form *AdminPasswordResetRequest) Submit() error {
 | 
			
		|||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	admin, err := form.app.Dao().FindAdminByEmail(form.Email)
 | 
			
		||||
	admin, err := form.config.TxDao.FindAdminByEmail(form.Email)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	now := time.Now().UTC()
 | 
			
		||||
	lastResetSentAt := admin.LastResetSentAt.Time()
 | 
			
		||||
	if now.Sub(lastResetSentAt).Seconds() < form.resendThreshold {
 | 
			
		||||
	if now.Sub(lastResetSentAt).Seconds() < form.config.ResendThreshold {
 | 
			
		||||
		return errors.New("You have already requested a password reset.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := mails.SendAdminPasswordReset(form.app, admin); err != nil {
 | 
			
		||||
	if err := mails.SendAdminPasswordReset(form.config.App, admin); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// update last sent timestamp
 | 
			
		||||
	admin.LastResetSentAt = types.NowDateTime()
 | 
			
		||||
 | 
			
		||||
	return form.app.Dao().SaveAdmin(admin)
 | 
			
		||||
	return form.config.TxDao.SaveAdmin(admin)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,21 +23,21 @@ type AdminUpsert struct {
 | 
			
		|||
 | 
			
		||||
// AdminUpsertConfig is the [AdminUpsert] factory initializer config.
 | 
			
		||||
//
 | 
			
		||||
// NB! Dao is a required struct member.
 | 
			
		||||
// NB! App is a required struct member.
 | 
			
		||||
type AdminUpsertConfig struct {
 | 
			
		||||
	Dao *daos.Dao
 | 
			
		||||
	App   core.App
 | 
			
		||||
	TxDao *daos.Dao
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAdminUpsert creates a new [AdminUpsert] form with initializer
 | 
			
		||||
// config created from the provided [core.App] and [models.Admin] instances
 | 
			
		||||
// (for create you could pass a pointer to an empty Admin - `&models.Admin{}`).
 | 
			
		||||
//
 | 
			
		||||
// This factory method is used primarily for convenience (and backward compatibility).
 | 
			
		||||
// If you want to submit the form as part of another transaction, use
 | 
			
		||||
// [NewAdminUpsertWithConfig] with Dao configured to your txDao.
 | 
			
		||||
// [NewAdminUpsertWithConfig] with explicitly set TxDao.
 | 
			
		||||
func NewAdminUpsert(app core.App, admin *models.Admin) *AdminUpsert {
 | 
			
		||||
	return NewAdminUpsertWithConfig(AdminUpsertConfig{
 | 
			
		||||
		Dao: app.Dao(),
 | 
			
		||||
		App: app,
 | 
			
		||||
	}, admin)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -50,10 +50,14 @@ func NewAdminUpsertWithConfig(config AdminUpsertConfig, admin *models.Admin) *Ad
 | 
			
		|||
		admin:  admin,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.Dao == nil || form.admin == nil {
 | 
			
		||||
	if form.config.App == nil || form.admin == nil {
 | 
			
		||||
		panic("Invalid initializer config or nil upsert model.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.TxDao == nil {
 | 
			
		||||
		form.config.TxDao = form.config.App.Dao()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// load defaults
 | 
			
		||||
	form.Id = admin.Id
 | 
			
		||||
	form.Avatar = admin.Avatar
 | 
			
		||||
| 
						 | 
				
			
			@ -100,7 +104,7 @@ func (form *AdminUpsert) Validate() error {
 | 
			
		|||
func (form *AdminUpsert) checkUniqueEmail(value any) error {
 | 
			
		||||
	v, _ := value.(string)
 | 
			
		||||
 | 
			
		||||
	if form.config.Dao.IsAdminEmailUnique(v, form.admin.Id) {
 | 
			
		||||
	if form.config.TxDao.IsAdminEmailUnique(v, form.admin.Id) {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -130,6 +134,6 @@ func (form *AdminUpsert) Submit(interceptors ...InterceptorFunc) error {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	return runInterceptors(func() error {
 | 
			
		||||
		return form.config.Dao.SaveAdmin(form.admin)
 | 
			
		||||
		return form.config.TxDao.SaveAdmin(form.admin)
 | 
			
		||||
	}, interceptors...)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,21 +33,21 @@ type CollectionUpsert struct {
 | 
			
		|||
 | 
			
		||||
// CollectionUpsertConfig is the [CollectionUpsert] factory initializer config.
 | 
			
		||||
//
 | 
			
		||||
// NB! Dao is a required struct member.
 | 
			
		||||
// NB! App is a required struct member.
 | 
			
		||||
type CollectionUpsertConfig struct {
 | 
			
		||||
	Dao *daos.Dao
 | 
			
		||||
	App   core.App
 | 
			
		||||
	TxDao *daos.Dao
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewCollectionUpsert creates a new [CollectionUpsert] form with initializer
 | 
			
		||||
// config created from the provided [core.App] and [models.Collection] instances
 | 
			
		||||
// (for create you could pass a pointer to an empty Collection - `&models.Collection{}`).
 | 
			
		||||
//
 | 
			
		||||
// This factory method is used primarily for convenience (and backward compatibility).
 | 
			
		||||
// If you want to submit the form as part of another transaction, use
 | 
			
		||||
// [NewCollectionUpsertWithConfig] with Dao configured to your txDao.
 | 
			
		||||
// [NewCollectionUpsertWithConfig] with explicitly set TxDao.
 | 
			
		||||
func NewCollectionUpsert(app core.App, collection *models.Collection) *CollectionUpsert {
 | 
			
		||||
	return NewCollectionUpsertWithConfig(CollectionUpsertConfig{
 | 
			
		||||
		Dao: app.Dao(),
 | 
			
		||||
		App: app,
 | 
			
		||||
	}, collection)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -60,10 +60,14 @@ func NewCollectionUpsertWithConfig(config CollectionUpsertConfig, collection *mo
 | 
			
		|||
		collection: collection,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.Dao == nil || form.collection == nil {
 | 
			
		||||
	if form.config.App == nil || form.collection == nil {
 | 
			
		||||
		panic("Invalid initializer config or nil upsert model.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.TxDao == nil {
 | 
			
		||||
		form.config.TxDao = form.config.App.Dao()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// load defaults
 | 
			
		||||
	form.Id = form.collection.Id
 | 
			
		||||
	form.Name = form.collection.Name
 | 
			
		||||
| 
						 | 
				
			
			@ -123,11 +127,11 @@ func (form *CollectionUpsert) Validate() error {
 | 
			
		|||
func (form *CollectionUpsert) checkUniqueName(value any) error {
 | 
			
		||||
	v, _ := value.(string)
 | 
			
		||||
 | 
			
		||||
	if !form.config.Dao.IsCollectionNameUnique(v, form.collection.Id) {
 | 
			
		||||
	if !form.config.TxDao.IsCollectionNameUnique(v, form.collection.Id) {
 | 
			
		||||
		return validation.NewError("validation_collection_name_exists", "Collection name must be unique (case insensitive).")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (form.collection.IsNew() || !strings.EqualFold(v, form.collection.Name)) && form.config.Dao.HasTable(v) {
 | 
			
		||||
	if (form.collection.IsNew() || !strings.EqualFold(v, form.collection.Name)) && form.config.TxDao.HasTable(v) {
 | 
			
		||||
		return validation.NewError("validation_collection_name_table_exists", "The collection name must be also unique table name.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -194,7 +198,7 @@ func (form *CollectionUpsert) checkRule(value any) error {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	dummy := &models.Collection{Schema: form.Schema}
 | 
			
		||||
	r := resolvers.NewRecordFieldResolver(form.config.Dao, dummy, nil)
 | 
			
		||||
	r := resolvers.NewRecordFieldResolver(form.config.TxDao, dummy, nil)
 | 
			
		||||
 | 
			
		||||
	_, err := search.FilterData(*v).BuildExpr(r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -239,6 +243,6 @@ func (form *CollectionUpsert) Submit(interceptors ...InterceptorFunc) error {
 | 
			
		|||
	form.collection.DeleteRule = form.DeleteRule
 | 
			
		||||
 | 
			
		||||
	return runInterceptors(func() error {
 | 
			
		||||
		return form.config.Dao.SaveCollection(form.collection)
 | 
			
		||||
		return form.config.TxDao.SaveCollection(form.collection)
 | 
			
		||||
	}, interceptors...)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,10 +22,21 @@ type CollectionsImport struct {
 | 
			
		|||
 | 
			
		||||
// CollectionsImportConfig is the [CollectionsImport] factory initializer config.
 | 
			
		||||
//
 | 
			
		||||
// NB! Dao is a required struct member.
 | 
			
		||||
// NB! App is a required struct member.
 | 
			
		||||
type CollectionsImportConfig struct {
 | 
			
		||||
	Dao     *daos.Dao
 | 
			
		||||
	IsDebug bool
 | 
			
		||||
	App   core.App
 | 
			
		||||
	TxDao *daos.Dao
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewCollectionsImport creates a new [CollectionsImport] form with
 | 
			
		||||
// initializer config created from the provided [core.App] instance.
 | 
			
		||||
//
 | 
			
		||||
// If you want to submit the form as part of another transaction, use
 | 
			
		||||
// [NewCollectionsImportWithConfig] with explicitly set TxDao.
 | 
			
		||||
func NewCollectionsImport(app core.App) *CollectionsImport {
 | 
			
		||||
	return NewCollectionsImportWithConfig(CollectionsImportConfig{
 | 
			
		||||
		App: app,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewCollectionsImportWithConfig creates a new [CollectionsImport]
 | 
			
		||||
| 
						 | 
				
			
			@ -33,26 +44,17 @@ type CollectionsImportConfig struct {
 | 
			
		|||
func NewCollectionsImportWithConfig(config CollectionsImportConfig) *CollectionsImport {
 | 
			
		||||
	form := &CollectionsImport{config: config}
 | 
			
		||||
 | 
			
		||||
	if form.config.Dao == nil {
 | 
			
		||||
		panic("Invalid initializer config.")
 | 
			
		||||
	if form.config.App == nil {
 | 
			
		||||
		panic("Missing required config.App instance.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.TxDao == nil {
 | 
			
		||||
		form.config.TxDao = form.config.App.Dao()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return form
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewCollectionsImport creates a new [CollectionsImport] form with
 | 
			
		||||
// initializer config created from the provided [core.App] instance.
 | 
			
		||||
//
 | 
			
		||||
// This factory method is used primarily for convenience (and backward compatibility).
 | 
			
		||||
// If you want to submit the form as part of another transaction, use
 | 
			
		||||
// [NewCollectionsImportWithConfig] with Dao configured to your txDao.
 | 
			
		||||
func NewCollectionsImport(app core.App) *CollectionsImport {
 | 
			
		||||
	return NewCollectionsImportWithConfig(CollectionsImportConfig{
 | 
			
		||||
		Dao:     app.Dao(),
 | 
			
		||||
		IsDebug: app.IsDebug(),
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate makes the form validatable by implementing [validation.Validatable] interface.
 | 
			
		||||
func (form *CollectionsImport) Validate() error {
 | 
			
		||||
	return validation.ValidateStruct(form,
 | 
			
		||||
| 
						 | 
				
			
			@ -73,7 +75,7 @@ func (form *CollectionsImport) Submit() error {
 | 
			
		|||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return form.config.Dao.RunInTransaction(func(txDao *daos.Dao) error {
 | 
			
		||||
	return form.config.TxDao.RunInTransaction(func(txDao *daos.Dao) error {
 | 
			
		||||
		oldCollections := []*models.Collection{}
 | 
			
		||||
		if err := txDao.CollectionQuery().All(&oldCollections); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
| 
						 | 
				
			
			@ -95,7 +97,7 @@ func (form *CollectionsImport) Submit() error {
 | 
			
		|||
				if mappedFormCollections[old.GetId()] == nil {
 | 
			
		||||
					// delete the collection
 | 
			
		||||
					if err := txDao.DeleteCollection(old); err != nil {
 | 
			
		||||
						if form.config.IsDebug {
 | 
			
		||||
						if form.config.App.IsDebug() {
 | 
			
		||||
							log.Println("[CollectionsImport] DeleteOthers failure", old.Name, err)
 | 
			
		||||
						}
 | 
			
		||||
						return validation.Errors{"collections": validation.NewError(
 | 
			
		||||
| 
						 | 
				
			
			@ -117,7 +119,7 @@ func (form *CollectionsImport) Submit() error {
 | 
			
		|||
			}
 | 
			
		||||
 | 
			
		||||
			if err := txDao.Save(collection); err != nil {
 | 
			
		||||
				if form.config.IsDebug {
 | 
			
		||||
				if form.config.App.IsDebug() {
 | 
			
		||||
					log.Println("[CollectionsImport] Save failure", collection.Name, err)
 | 
			
		||||
				}
 | 
			
		||||
				return validation.Errors{"collections": validation.NewError(
 | 
			
		||||
| 
						 | 
				
			
			@ -141,7 +143,8 @@ func (form *CollectionsImport) Submit() error {
 | 
			
		|||
				upsertModel = collection
 | 
			
		||||
			}
 | 
			
		||||
			upsertForm := NewCollectionUpsertWithConfig(CollectionUpsertConfig{
 | 
			
		||||
				Dao: txDao,
 | 
			
		||||
				App:   form.config.App,
 | 
			
		||||
				TxDao: txDao,
 | 
			
		||||
			}, upsertModel)
 | 
			
		||||
			// load form fields with the refreshed collection state
 | 
			
		||||
			upsertForm.Id = collection.Id
 | 
			
		||||
| 
						 | 
				
			
			@ -168,7 +171,7 @@ func (form *CollectionsImport) Submit() error {
 | 
			
		|||
		for _, collection := range form.Collections {
 | 
			
		||||
			oldCollection := mappedOldCollections[collection.GetId()]
 | 
			
		||||
			if err := txDao.SyncRecordTableSchema(collection, oldCollection); err != nil {
 | 
			
		||||
				if form.config.IsDebug {
 | 
			
		||||
				if form.config.App.IsDebug() {
 | 
			
		||||
					log.Println("[CollectionsImport] Records table sync failure", collection.Name, err)
 | 
			
		||||
				}
 | 
			
		||||
				return validation.Errors{"collections": validation.NewError(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@ import (
 | 
			
		|||
	validation "github.com/go-ozzo/ozzo-validation/v4"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// RealtimeSubscribe defines a RealtimeSubscribe request form.
 | 
			
		||||
// RealtimeSubscribe specifies a RealtimeSubscribe request form.
 | 
			
		||||
type RealtimeSubscribe struct {
 | 
			
		||||
	ClientId      string   `form:"clientId" json:"clientId"`
 | 
			
		||||
	Subscriptions []string `form:"subscriptions" json:"subscriptions"`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,7 +16,6 @@ import (
 | 
			
		|||
	"github.com/pocketbase/pocketbase/forms/validators"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/models"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/models/schema"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/tools/filesystem"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/tools/list"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/tools/rest"
 | 
			
		||||
	"github.com/spf13/cast"
 | 
			
		||||
| 
						 | 
				
			
			@ -36,25 +35,21 @@ type RecordUpsert struct {
 | 
			
		|||
 | 
			
		||||
// RecordUpsertConfig is the [RecordUpsert] factory initializer config.
 | 
			
		||||
//
 | 
			
		||||
// NB! Dao and FilesystemFactory are required struct members.
 | 
			
		||||
// NB! App is required struct member.
 | 
			
		||||
type RecordUpsertConfig struct {
 | 
			
		||||
	Dao               *daos.Dao
 | 
			
		||||
	FilesystemFactory func() (*filesystem.System, error)
 | 
			
		||||
	IsDebug           bool
 | 
			
		||||
	App   core.App
 | 
			
		||||
	TxDao *daos.Dao
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewRecordUpsert creates a new [RecordUpsert] form with initializer
 | 
			
		||||
// config created from the provided [core.App] and [models.Record] instances
 | 
			
		||||
// (for create you could pass a pointer to an empty Record - `&models.Record{}`).
 | 
			
		||||
//
 | 
			
		||||
// This factory method is used primarily for convenience (and backward compatibility).
 | 
			
		||||
// If you want to submit the form as part of another transaction, use
 | 
			
		||||
// [NewRecordUpsertWithConfig] with Dao configured to your txDao.
 | 
			
		||||
// [NewRecordUpsertWithConfig] with explicitly set TxDao.
 | 
			
		||||
func NewRecordUpsert(app core.App, record *models.Record) *RecordUpsert {
 | 
			
		||||
	return NewRecordUpsertWithConfig(RecordUpsertConfig{
 | 
			
		||||
		Dao:               app.Dao(),
 | 
			
		||||
		FilesystemFactory: app.NewFilesystem,
 | 
			
		||||
		IsDebug:           app.IsDebug(),
 | 
			
		||||
		App: app,
 | 
			
		||||
	}, record)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -69,12 +64,14 @@ func NewRecordUpsertWithConfig(config RecordUpsertConfig, record *models.Record)
 | 
			
		|||
		filesToUpload: []*rest.UploadedFile{},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.Dao == nil ||
 | 
			
		||||
		form.config.FilesystemFactory == nil ||
 | 
			
		||||
		form.record == nil {
 | 
			
		||||
	if form.config.App == nil || form.record == nil {
 | 
			
		||||
		panic("Invalid initializer config or nil upsert model.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.TxDao == nil {
 | 
			
		||||
		form.config.TxDao = form.config.App.Dao()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	form.Id = record.Id
 | 
			
		||||
 | 
			
		||||
	form.Data = map[string]any{}
 | 
			
		||||
| 
						 | 
				
			
			@ -240,7 +237,7 @@ func (form *RecordUpsert) LoadData(r *http.Request) error {
 | 
			
		|||
		// check if there are any new uploaded form files
 | 
			
		||||
		files, err := rest.FindUploadedFiles(r, key)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if form.config.IsDebug {
 | 
			
		||||
			if form.config.App.IsDebug() {
 | 
			
		||||
				log.Printf("%q uploaded file error: %v\n", key, err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -288,7 +285,7 @@ func (form *RecordUpsert) Validate() error {
 | 
			
		|||
 | 
			
		||||
	// record data validator
 | 
			
		||||
	dataValidator := validators.NewRecordDataValidator(
 | 
			
		||||
		form.config.Dao,
 | 
			
		||||
		form.config.TxDao,
 | 
			
		||||
		form.record,
 | 
			
		||||
		form.filesToUpload,
 | 
			
		||||
	)
 | 
			
		||||
| 
						 | 
				
			
			@ -316,7 +313,7 @@ func (form *RecordUpsert) DrySubmit(callback func(txDao *daos.Dao) error) error
 | 
			
		|||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return form.config.Dao.RunInTransaction(func(txDao *daos.Dao) error {
 | 
			
		||||
	return form.config.TxDao.RunInTransaction(func(txDao *daos.Dao) error {
 | 
			
		||||
		tx, ok := txDao.DB().(*dbx.Tx)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return errors.New("failed to get transaction db")
 | 
			
		||||
| 
						 | 
				
			
			@ -356,7 +353,7 @@ func (form *RecordUpsert) Submit(interceptors ...InterceptorFunc) error {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	return runInterceptors(func() error {
 | 
			
		||||
		return form.config.Dao.RunInTransaction(func(txDao *daos.Dao) error {
 | 
			
		||||
		return form.config.TxDao.RunInTransaction(func(txDao *daos.Dao) error {
 | 
			
		||||
			// persist record model
 | 
			
		||||
			if err := txDao.SaveRecord(form.record); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
| 
						 | 
				
			
			@ -387,7 +384,7 @@ func (form *RecordUpsert) processFilesToUpload() error {
 | 
			
		|||
		return errors.New("The record is not persisted yet.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fs, err := form.config.FilesystemFactory()
 | 
			
		||||
	fs, err := form.config.App.NewFilesystem()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -423,7 +420,7 @@ func (form *RecordUpsert) processFilesToDelete() error {
 | 
			
		|||
		return errors.New("The record is not persisted yet.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fs, err := form.config.FilesystemFactory()
 | 
			
		||||
	fs, err := form.config.App.NewFilesystem()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,22 +5,56 @@ import (
 | 
			
		|||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/pocketbase/pocketbase/core"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/daos"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SettingsUpsert defines app settings upsert form.
 | 
			
		||||
// SettingsUpsert specifies a [core.Settings] upsert (create/update) form.
 | 
			
		||||
type SettingsUpsert struct {
 | 
			
		||||
	*core.Settings
 | 
			
		||||
 | 
			
		||||
	app core.App
 | 
			
		||||
	config SettingsUpsertConfig
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewSettingsUpsert creates new settings upsert form from the provided app.
 | 
			
		||||
// SettingsUpsertConfig is the [SettingsUpsert] factory initializer config.
 | 
			
		||||
//
 | 
			
		||||
// NB! App is required struct member.
 | 
			
		||||
type SettingsUpsertConfig struct {
 | 
			
		||||
	App       core.App
 | 
			
		||||
	TxDao     *daos.Dao
 | 
			
		||||
	TxLogsDao *daos.Dao
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewSettingsUpsert creates a new [SettingsUpsert] form with initializer
 | 
			
		||||
// config created from the provided [core.App] instance.
 | 
			
		||||
//
 | 
			
		||||
// If you want to submit the form as part of another transaction, use
 | 
			
		||||
// [NewSettingsUpsertWithConfig] with explicitly set TxDao.
 | 
			
		||||
func NewSettingsUpsert(app core.App) *SettingsUpsert {
 | 
			
		||||
	form := &SettingsUpsert{app: app}
 | 
			
		||||
	return NewSettingsUpsertWithConfig(SettingsUpsertConfig{
 | 
			
		||||
		App: app,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewSettingsUpsertWithConfig creates a new [SettingsUpsert] form
 | 
			
		||||
// with the provided config or panics on invalid configuration.
 | 
			
		||||
func NewSettingsUpsertWithConfig(config SettingsUpsertConfig) *SettingsUpsert {
 | 
			
		||||
	form := &SettingsUpsert{config: config}
 | 
			
		||||
 | 
			
		||||
	if form.config.App == nil {
 | 
			
		||||
		panic("Missing required config.App instance.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.TxDao == nil {
 | 
			
		||||
		form.config.TxDao = form.config.App.Dao()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.TxLogsDao == nil {
 | 
			
		||||
		form.config.TxLogsDao = form.config.App.LogsDao()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// load the application settings into the form
 | 
			
		||||
	form.Settings, _ = app.Settings().Clone()
 | 
			
		||||
	form.Settings, _ = config.App.Settings().Clone()
 | 
			
		||||
 | 
			
		||||
	return form
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -41,10 +75,10 @@ func (form *SettingsUpsert) Submit(interceptors ...InterceptorFunc) error {
 | 
			
		|||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	encryptionKey := os.Getenv(form.app.EncryptionEnv())
 | 
			
		||||
	encryptionKey := os.Getenv(form.config.App.EncryptionEnv())
 | 
			
		||||
 | 
			
		||||
	return runInterceptors(func() error {
 | 
			
		||||
		saveErr := form.app.Dao().SaveParam(
 | 
			
		||||
		saveErr := form.config.TxDao.SaveParam(
 | 
			
		||||
			models.ParamAppSettings,
 | 
			
		||||
			form.Settings,
 | 
			
		||||
			encryptionKey,
 | 
			
		||||
| 
						 | 
				
			
			@ -54,11 +88,11 @@ func (form *SettingsUpsert) Submit(interceptors ...InterceptorFunc) error {
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		// explicitly trigger old logs deletion
 | 
			
		||||
		form.app.LogsDao().DeleteOldRequests(
 | 
			
		||||
		form.config.TxLogsDao.DeleteOldRequests(
 | 
			
		||||
			time.Now().AddDate(0, 0, -1*form.Settings.Logs.MaxDays),
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		// merge the application settings with the form ones
 | 
			
		||||
		return form.app.Settings().Merge(form.Settings)
 | 
			
		||||
		return form.config.App.Settings().Merge(form.Settings)
 | 
			
		||||
	}, interceptors...)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,23 +3,53 @@ package forms
 | 
			
		|||
import (
 | 
			
		||||
	validation "github.com/go-ozzo/ozzo-validation/v4"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/core"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/daos"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/models"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/tools/security"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// UserEmailChangeConfirm defines a user email change confirmation form.
 | 
			
		||||
// UserEmailChangeConfirm specifies a user email change confirmation form.
 | 
			
		||||
type UserEmailChangeConfirm struct {
 | 
			
		||||
	app core.App
 | 
			
		||||
	config UserEmailChangeConfirmConfig
 | 
			
		||||
 | 
			
		||||
	Token    string `form:"token" json:"token"`
 | 
			
		||||
	Password string `form:"password" json:"password"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserEmailChangeConfirm creates new user email change confirmation form.
 | 
			
		||||
// UserEmailChangeConfirmConfig is the [UserEmailChangeConfirm] factory initializer config.
 | 
			
		||||
//
 | 
			
		||||
// NB! App is required struct member.
 | 
			
		||||
type UserEmailChangeConfirmConfig struct {
 | 
			
		||||
	App   core.App
 | 
			
		||||
	TxDao *daos.Dao
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserEmailChangeConfirm creates a new [UserEmailChangeConfirm]
 | 
			
		||||
// form with initializer config created from the provided [core.App] instance.
 | 
			
		||||
//
 | 
			
		||||
// This factory method is used primarily for convenience (and backward compatibility).
 | 
			
		||||
// If you want to submit the form as part of another transaction, use
 | 
			
		||||
// [NewUserEmailChangeConfirmWithConfig] with explicitly set TxDao.
 | 
			
		||||
func NewUserEmailChangeConfirm(app core.App) *UserEmailChangeConfirm {
 | 
			
		||||
	return &UserEmailChangeConfirm{
 | 
			
		||||
		app: app,
 | 
			
		||||
	return NewUserEmailChangeConfirmWithConfig(UserEmailChangeConfirmConfig{
 | 
			
		||||
		App: app,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserEmailChangeConfirmWithConfig creates a new [UserEmailChangeConfirm]
 | 
			
		||||
// form with the provided config or panics on invalid configuration.
 | 
			
		||||
func NewUserEmailChangeConfirmWithConfig(config UserEmailChangeConfirmConfig) *UserEmailChangeConfirm {
 | 
			
		||||
	form := &UserEmailChangeConfirm{config: config}
 | 
			
		||||
 | 
			
		||||
	if form.config.App == nil {
 | 
			
		||||
		panic("Missing required config.App instance.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.TxDao == nil {
 | 
			
		||||
		form.config.TxDao = form.config.App.Dao()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return form
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate makes the form validatable by implementing [validation.Validatable] interface.
 | 
			
		||||
| 
						 | 
				
			
			@ -73,14 +103,14 @@ func (form *UserEmailChangeConfirm) parseToken(token string) (*models.User, stri
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	// ensure that there aren't other users with the new email
 | 
			
		||||
	if !form.app.Dao().IsUserEmailUnique(newEmail, "") {
 | 
			
		||||
	if !form.config.TxDao.IsUserEmailUnique(newEmail, "") {
 | 
			
		||||
		return nil, "", validation.NewError("validation_existing_token_email", "The new email address is already registered: "+newEmail)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// verify that the token is not expired and its signiture is valid
 | 
			
		||||
	user, err := form.app.Dao().FindUserByToken(
 | 
			
		||||
	// verify that the token is not expired and its signature is valid
 | 
			
		||||
	user, err := form.config.TxDao.FindUserByToken(
 | 
			
		||||
		token,
 | 
			
		||||
		form.app.Settings().UserEmailChangeToken.Secret,
 | 
			
		||||
		form.config.App.Settings().UserEmailChangeToken.Secret,
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil || user == nil {
 | 
			
		||||
		return nil, "", validation.NewError("validation_invalid_token", "Invalid or expired token.")
 | 
			
		||||
| 
						 | 
				
			
			@ -105,7 +135,7 @@ func (form *UserEmailChangeConfirm) Submit() (*models.User, error) {
 | 
			
		|||
	user.Verified = true
 | 
			
		||||
	user.RefreshTokenKey() // invalidate old tokens
 | 
			
		||||
 | 
			
		||||
	if err := form.app.Dao().SaveUser(user); err != nil {
 | 
			
		||||
	if err := form.config.TxDao.SaveUser(user); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,24 +4,55 @@ import (
 | 
			
		|||
	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/mails"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// UserEmailChangeRequest defines a user email change request form.
 | 
			
		||||
type UserEmailChangeRequest struct {
 | 
			
		||||
	app  core.App
 | 
			
		||||
	config UserEmailChangeRequestConfig
 | 
			
		||||
	user   *models.User
 | 
			
		||||
 | 
			
		||||
	NewEmail string `form:"newEmail" json:"newEmail"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserEmailChangeRequest creates a new user email change request form.
 | 
			
		||||
// UserEmailChangeRequestConfig is the [UserEmailChangeRequest] factory initializer config.
 | 
			
		||||
//
 | 
			
		||||
// NB! App is required struct member.
 | 
			
		||||
type UserEmailChangeRequestConfig struct {
 | 
			
		||||
	App   core.App
 | 
			
		||||
	TxDao *daos.Dao
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserEmailChangeRequest creates a new [UserEmailChangeRequest]
 | 
			
		||||
// form with initializer config created from the provided [core.App] instance.
 | 
			
		||||
//
 | 
			
		||||
// If you want to submit the form as part of another transaction, use
 | 
			
		||||
// [NewUserEmailChangeConfirmWithConfig] with explicitly set TxDao.
 | 
			
		||||
func NewUserEmailChangeRequest(app core.App, user *models.User) *UserEmailChangeRequest {
 | 
			
		||||
	return &UserEmailChangeRequest{
 | 
			
		||||
		app:  app,
 | 
			
		||||
	return NewUserEmailChangeRequestWithConfig(UserEmailChangeRequestConfig{
 | 
			
		||||
		App: app,
 | 
			
		||||
	}, user)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserEmailChangeRequestWithConfig creates a new [UserEmailChangeRequest]
 | 
			
		||||
// form with the provided config or panics on invalid configuration.
 | 
			
		||||
func NewUserEmailChangeRequestWithConfig(config UserEmailChangeRequestConfig, user *models.User) *UserEmailChangeRequest {
 | 
			
		||||
	form := &UserEmailChangeRequest{
 | 
			
		||||
		config: config,
 | 
			
		||||
		user:   user,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.App == nil {
 | 
			
		||||
		panic("Missing required config.App instance.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.TxDao == nil {
 | 
			
		||||
		form.config.TxDao = form.config.App.Dao()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return form
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate makes the form validatable by implementing [validation.Validatable] interface.
 | 
			
		||||
| 
						 | 
				
			
			@ -40,7 +71,7 @@ func (form *UserEmailChangeRequest) Validate() error {
 | 
			
		|||
func (form *UserEmailChangeRequest) checkUniqueEmail(value any) error {
 | 
			
		||||
	v, _ := value.(string)
 | 
			
		||||
 | 
			
		||||
	if !form.app.Dao().IsUserEmailUnique(v, "") {
 | 
			
		||||
	if !form.config.TxDao.IsUserEmailUnique(v, "") {
 | 
			
		||||
		return validation.NewError("validation_user_email_exists", "User email already exists.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -53,5 +84,5 @@ func (form *UserEmailChangeRequest) Submit() error {
 | 
			
		|||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return mails.SendUserChangeEmail(form.app, form.user, form.NewEmail)
 | 
			
		||||
	return mails.SendUserChangeEmail(form.config.App, form.user, form.NewEmail)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,21 +4,49 @@ import (
 | 
			
		|||
	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/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// UserEmailLogin defines a user email/pass login form.
 | 
			
		||||
// UserEmailLogin specifies a user email/pass login form.
 | 
			
		||||
type UserEmailLogin struct {
 | 
			
		||||
	app core.App
 | 
			
		||||
	config UserEmailLoginConfig
 | 
			
		||||
 | 
			
		||||
	Email    string `form:"email" json:"email"`
 | 
			
		||||
	Password string `form:"password" json:"password"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserEmailLogin creates a new user email/pass login form.
 | 
			
		||||
// UserEmailLoginConfig is the [UserEmailLogin] factory initializer config.
 | 
			
		||||
//
 | 
			
		||||
// NB! App is required struct member.
 | 
			
		||||
type UserEmailLoginConfig struct {
 | 
			
		||||
	App   core.App
 | 
			
		||||
	TxDao *daos.Dao
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserEmailLogin creates a new [UserEmailLogin] form with
 | 
			
		||||
// initializer config created from the provided [core.App] instance.
 | 
			
		||||
//
 | 
			
		||||
// This factory method is used primarily for convenience (and backward compatibility).
 | 
			
		||||
// If you want to submit the form as part of another transaction, use
 | 
			
		||||
// [NewUserEmailLoginWithConfig] with explicitly set TxDao.
 | 
			
		||||
func NewUserEmailLogin(app core.App) *UserEmailLogin {
 | 
			
		||||
	form := &UserEmailLogin{
 | 
			
		||||
		app: app,
 | 
			
		||||
	return NewUserEmailLoginWithConfig(UserEmailLoginConfig{
 | 
			
		||||
		App: app,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserEmailLoginWithConfig creates a new [UserEmailLogin]
 | 
			
		||||
// form with the provided config or panics on invalid configuration.
 | 
			
		||||
func NewUserEmailLoginWithConfig(config UserEmailLoginConfig) *UserEmailLogin {
 | 
			
		||||
	form := &UserEmailLogin{config: config}
 | 
			
		||||
 | 
			
		||||
	if form.config.App == nil {
 | 
			
		||||
		panic("Missing required config.App instance.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.TxDao == nil {
 | 
			
		||||
		form.config.TxDao = form.config.App.Dao()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return form
 | 
			
		||||
| 
						 | 
				
			
			@ -39,7 +67,7 @@ func (form *UserEmailLogin) Submit() (*models.User, error) {
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	user, err := form.app.Dao().FindUserByEmail(form.Email)
 | 
			
		||||
	user, err := form.config.TxDao.FindUserByEmail(form.Email)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,15 +7,16 @@ import (
 | 
			
		|||
	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/models"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/tools/auth"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/tools/security"
 | 
			
		||||
	"golang.org/x/oauth2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// UserOauth2Login defines a user Oauth2 login form.
 | 
			
		||||
// UserOauth2Login specifies a user Oauth2 login form.
 | 
			
		||||
type UserOauth2Login struct {
 | 
			
		||||
	app core.App
 | 
			
		||||
	config UserOauth2LoginConfig
 | 
			
		||||
 | 
			
		||||
	// The name of the OAuth2 client provider (eg. "google")
 | 
			
		||||
	Provider string `form:"provider" json:"provider"`
 | 
			
		||||
| 
						 | 
				
			
			@ -30,9 +31,39 @@ type UserOauth2Login struct {
 | 
			
		|||
	RedirectUrl string `form:"redirectUrl" json:"redirectUrl"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserOauth2Login creates a new user Oauth2 login form.
 | 
			
		||||
// UserOauth2LoginConfig is the [UserOauth2Login] factory initializer config.
 | 
			
		||||
//
 | 
			
		||||
// NB! App is required struct member.
 | 
			
		||||
type UserOauth2LoginConfig struct {
 | 
			
		||||
	App   core.App
 | 
			
		||||
	TxDao *daos.Dao
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserOauth2Login creates a new [UserOauth2Login] form with
 | 
			
		||||
// initializer config created from the provided [core.App] instance.
 | 
			
		||||
//
 | 
			
		||||
// If you want to submit the form as part of another transaction, use
 | 
			
		||||
// [NewUserOauth2LoginWithConfig] with explicitly set TxDao.
 | 
			
		||||
func NewUserOauth2Login(app core.App) *UserOauth2Login {
 | 
			
		||||
	return &UserOauth2Login{app: app}
 | 
			
		||||
	return NewUserOauth2LoginWithConfig(UserOauth2LoginConfig{
 | 
			
		||||
		App: app,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserOauth2LoginWithConfig creates a new [UserOauth2Login]
 | 
			
		||||
// form with the provided config or panics on invalid configuration.
 | 
			
		||||
func NewUserOauth2LoginWithConfig(config UserOauth2LoginConfig) *UserOauth2Login {
 | 
			
		||||
	form := &UserOauth2Login{config: config}
 | 
			
		||||
 | 
			
		||||
	if form.config.App == nil {
 | 
			
		||||
		panic("Missing required config.App instance.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.TxDao == nil {
 | 
			
		||||
		form.config.TxDao = form.config.App.Dao()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return form
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate makes the form validatable by implementing [validation.Validatable] interface.
 | 
			
		||||
| 
						 | 
				
			
			@ -48,7 +79,7 @@ func (form *UserOauth2Login) Validate() error {
 | 
			
		|||
func (form *UserOauth2Login) checkProviderName(value any) error {
 | 
			
		||||
	name, _ := value.(string)
 | 
			
		||||
 | 
			
		||||
	config, ok := form.app.Settings().NamedAuthProviderConfigs()[name]
 | 
			
		||||
	config, ok := form.config.App.Settings().NamedAuthProviderConfigs()[name]
 | 
			
		||||
	if !ok || !config.Enabled {
 | 
			
		||||
		return validation.NewError("validation_invalid_provider", fmt.Sprintf("%q is missing or is not enabled.", name))
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -68,7 +99,7 @@ func (form *UserOauth2Login) Submit() (*models.User, *auth.AuthUser, error) {
 | 
			
		|||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	config := form.app.Settings().NamedAuthProviderConfigs()[form.Provider]
 | 
			
		||||
	config := form.config.App.Settings().NamedAuthProviderConfigs()[form.Provider]
 | 
			
		||||
	config.SetupProvider(provider)
 | 
			
		||||
 | 
			
		||||
	provider.SetRedirectUrl(form.RedirectUrl)
 | 
			
		||||
| 
						 | 
				
			
			@ -89,12 +120,12 @@ func (form *UserOauth2Login) Submit() (*models.User, *auth.AuthUser, error) {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	// login/register the auth user
 | 
			
		||||
	user, _ := form.app.Dao().FindUserByEmail(authData.Email)
 | 
			
		||||
	user, _ := form.config.TxDao.FindUserByEmail(authData.Email)
 | 
			
		||||
	if user != nil {
 | 
			
		||||
		// update the existing user's verified state
 | 
			
		||||
		if !user.Verified {
 | 
			
		||||
			user.Verified = true
 | 
			
		||||
			if err := form.app.Dao().SaveUser(user); err != nil {
 | 
			
		||||
			if err := form.config.TxDao.SaveUser(user); err != nil {
 | 
			
		||||
				return nil, authData, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -108,7 +139,10 @@ func (form *UserOauth2Login) Submit() (*models.User, *auth.AuthUser, error) {
 | 
			
		|||
 | 
			
		||||
	// create new user
 | 
			
		||||
	user = &models.User{Verified: true}
 | 
			
		||||
	upsertForm := NewUserUpsert(form.app, user)
 | 
			
		||||
	upsertForm := NewUserUpsertWithConfig(UserUpsertConfig{
 | 
			
		||||
		App:   form.config.App,
 | 
			
		||||
		TxDao: form.config.TxDao,
 | 
			
		||||
	}, user)
 | 
			
		||||
	upsertForm.Email = authData.Email
 | 
			
		||||
	upsertForm.Password = security.RandomString(30)
 | 
			
		||||
	upsertForm.PasswordConfirm = upsertForm.Password
 | 
			
		||||
| 
						 | 
				
			
			@ -118,7 +152,7 @@ func (form *UserOauth2Login) Submit() (*models.User, *auth.AuthUser, error) {
 | 
			
		|||
		AuthData: authData,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := form.app.OnUserBeforeOauth2Register().Trigger(event); err != nil {
 | 
			
		||||
	if err := form.config.App.OnUserBeforeOauth2Register().Trigger(event); err != nil {
 | 
			
		||||
		return nil, authData, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -126,7 +160,7 @@ func (form *UserOauth2Login) Submit() (*models.User, *auth.AuthUser, error) {
 | 
			
		|||
		return nil, authData, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := form.app.OnUserAfterOauth2Register().Trigger(event); err != nil {
 | 
			
		||||
	if err := form.config.App.OnUserAfterOauth2Register().Trigger(event); err != nil {
 | 
			
		||||
		return nil, authData, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,29 +3,59 @@ package forms
 | 
			
		|||
import (
 | 
			
		||||
	validation "github.com/go-ozzo/ozzo-validation/v4"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/core"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/daos"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/forms/validators"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// UserPasswordResetConfirm defines a user password reset confirmation form.
 | 
			
		||||
// UserPasswordResetConfirm specifies a user password reset confirmation form.
 | 
			
		||||
type UserPasswordResetConfirm struct {
 | 
			
		||||
	app core.App
 | 
			
		||||
	config UserPasswordResetConfirmConfig
 | 
			
		||||
 | 
			
		||||
	Token           string `form:"token" json:"token"`
 | 
			
		||||
	Password        string `form:"password" json:"password"`
 | 
			
		||||
	PasswordConfirm string `form:"passwordConfirm" json:"passwordConfirm"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserPasswordResetConfirm creates new user password reset confirmation form.
 | 
			
		||||
// UserPasswordResetConfirmConfig is the [UserPasswordResetConfirm]
 | 
			
		||||
// factory initializer config.
 | 
			
		||||
//
 | 
			
		||||
// NB! App is required struct member.
 | 
			
		||||
type UserPasswordResetConfirmConfig struct {
 | 
			
		||||
	App   core.App
 | 
			
		||||
	TxDao *daos.Dao
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserPasswordResetConfirm creates a new [UserPasswordResetConfirm]
 | 
			
		||||
// form with initializer config created from the provided [core.App] instance.
 | 
			
		||||
//
 | 
			
		||||
// If you want to submit the form as part of another transaction, use
 | 
			
		||||
// [NewUserPasswordResetConfirmWithConfig] with explicitly set TxDao.
 | 
			
		||||
func NewUserPasswordResetConfirm(app core.App) *UserPasswordResetConfirm {
 | 
			
		||||
	return &UserPasswordResetConfirm{
 | 
			
		||||
		app: app,
 | 
			
		||||
	return NewUserPasswordResetConfirmWithConfig(UserPasswordResetConfirmConfig{
 | 
			
		||||
		App: app,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserPasswordResetConfirmWithConfig creates a new [UserPasswordResetConfirm]
 | 
			
		||||
// form with the provided config or panics on invalid configuration.
 | 
			
		||||
func NewUserPasswordResetConfirmWithConfig(config UserPasswordResetConfirmConfig) *UserPasswordResetConfirm {
 | 
			
		||||
	form := &UserPasswordResetConfirm{config: config}
 | 
			
		||||
 | 
			
		||||
	if form.config.App == nil {
 | 
			
		||||
		panic("Missing required config.App instance.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.TxDao == nil {
 | 
			
		||||
		form.config.TxDao = form.config.App.Dao()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return form
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate makes the form validatable by implementing [validation.Validatable] interface.
 | 
			
		||||
func (form *UserPasswordResetConfirm) Validate() error {
 | 
			
		||||
	minPasswordLength := form.app.Settings().EmailAuth.MinPasswordLength
 | 
			
		||||
	minPasswordLength := form.config.App.Settings().EmailAuth.MinPasswordLength
 | 
			
		||||
 | 
			
		||||
	return validation.ValidateStruct(form,
 | 
			
		||||
		validation.Field(&form.Token, validation.Required, validation.By(form.checkToken)),
 | 
			
		||||
| 
						 | 
				
			
			@ -40,9 +70,9 @@ func (form *UserPasswordResetConfirm) checkToken(value any) error {
 | 
			
		|||
		return nil // nothing to check
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	user, err := form.app.Dao().FindUserByToken(
 | 
			
		||||
	user, err := form.config.TxDao.FindUserByToken(
 | 
			
		||||
		v,
 | 
			
		||||
		form.app.Settings().UserPasswordResetToken.Secret,
 | 
			
		||||
		form.config.App.Settings().UserPasswordResetToken.Secret,
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil || user == nil {
 | 
			
		||||
		return validation.NewError("validation_invalid_token", "Invalid or expired token.")
 | 
			
		||||
| 
						 | 
				
			
			@ -58,9 +88,9 @@ func (form *UserPasswordResetConfirm) Submit() (*models.User, error) {
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	user, err := form.app.Dao().FindUserByToken(
 | 
			
		||||
	user, err := form.config.TxDao.FindUserByToken(
 | 
			
		||||
		form.Token,
 | 
			
		||||
		form.app.Settings().UserPasswordResetToken.Secret,
 | 
			
		||||
		form.config.App.Settings().UserPasswordResetToken.Secret,
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
| 
						 | 
				
			
			@ -70,7 +100,7 @@ func (form *UserPasswordResetConfirm) Submit() (*models.User, error) {
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := form.app.Dao().SaveUser(user); err != nil {
 | 
			
		||||
	if err := form.config.TxDao.SaveUser(user); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,24 +7,54 @@ import (
 | 
			
		|||
	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/mails"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/tools/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// UserPasswordResetRequest defines a user password reset request form.
 | 
			
		||||
// UserPasswordResetRequest specifies a user password reset request form.
 | 
			
		||||
type UserPasswordResetRequest struct {
 | 
			
		||||
	app             core.App
 | 
			
		||||
	resendThreshold float64
 | 
			
		||||
	config UserPasswordResetRequestConfig
 | 
			
		||||
 | 
			
		||||
	Email string `form:"email" json:"email"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserPasswordResetRequest creates new user password reset request form.
 | 
			
		||||
// UserPasswordResetRequestConfig is the [UserPasswordResetRequest]
 | 
			
		||||
// factory initializer config.
 | 
			
		||||
//
 | 
			
		||||
// NB! App is required struct member.
 | 
			
		||||
type UserPasswordResetRequestConfig struct {
 | 
			
		||||
	App             core.App
 | 
			
		||||
	TxDao           *daos.Dao
 | 
			
		||||
	ResendThreshold float64 // in seconds
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserPasswordResetRequest creates a new [UserPasswordResetRequest]
 | 
			
		||||
// form with initializer config created from the provided [core.App] instance.
 | 
			
		||||
//
 | 
			
		||||
// If you want to submit the form as part of another transaction, use
 | 
			
		||||
// [NewUserPasswordResetRequestWithConfig] with explicitly set TxDao.
 | 
			
		||||
func NewUserPasswordResetRequest(app core.App) *UserPasswordResetRequest {
 | 
			
		||||
	return &UserPasswordResetRequest{
 | 
			
		||||
		app:             app,
 | 
			
		||||
		resendThreshold: 120, // 2 min
 | 
			
		||||
	return NewUserPasswordResetRequestWithConfig(UserPasswordResetRequestConfig{
 | 
			
		||||
		App:             app,
 | 
			
		||||
		ResendThreshold: 120, // 2 min
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserPasswordResetRequestWithConfig creates a new [UserPasswordResetRequest]
 | 
			
		||||
// form with the provided config or panics on invalid configuration.
 | 
			
		||||
func NewUserPasswordResetRequestWithConfig(config UserPasswordResetRequestConfig) *UserPasswordResetRequest {
 | 
			
		||||
	form := &UserPasswordResetRequest{config: config}
 | 
			
		||||
 | 
			
		||||
	if form.config.App == nil {
 | 
			
		||||
		panic("Missing required config.App instance.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.TxDao == nil {
 | 
			
		||||
		form.config.TxDao = form.config.App.Dao()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return form
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate makes the form validatable by implementing [validation.Validatable] interface.
 | 
			
		||||
| 
						 | 
				
			
			@ -48,23 +78,23 @@ func (form *UserPasswordResetRequest) Submit() error {
 | 
			
		|||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	user, err := form.app.Dao().FindUserByEmail(form.Email)
 | 
			
		||||
	user, err := form.config.TxDao.FindUserByEmail(form.Email)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	now := time.Now().UTC()
 | 
			
		||||
	lastResetSentAt := user.LastResetSentAt.Time()
 | 
			
		||||
	if now.Sub(lastResetSentAt).Seconds() < form.resendThreshold {
 | 
			
		||||
	if now.Sub(lastResetSentAt).Seconds() < form.config.ResendThreshold {
 | 
			
		||||
		return errors.New("You've already requested a password reset.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := mails.SendUserPasswordReset(form.app, user); err != nil {
 | 
			
		||||
	if err := mails.SendUserPasswordReset(form.config.App, user); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// update last sent timestamp
 | 
			
		||||
	user.LastResetSentAt = types.NowDateTime()
 | 
			
		||||
 | 
			
		||||
	return form.app.Dao().SaveUser(user)
 | 
			
		||||
	return form.config.TxDao.SaveUser(user)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,28 +26,26 @@ type UserUpsert struct {
 | 
			
		|||
 | 
			
		||||
// UserUpsertConfig is the [UserUpsert] factory initializer config.
 | 
			
		||||
//
 | 
			
		||||
// NB! Dao and Settings are required struct members.
 | 
			
		||||
// NB! App is required struct member.
 | 
			
		||||
type UserUpsertConfig struct {
 | 
			
		||||
	Dao      *daos.Dao
 | 
			
		||||
	Settings *core.Settings
 | 
			
		||||
	App   core.App
 | 
			
		||||
	TxDao *daos.Dao
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserUpsert creates a new [UserUpsert] form with initializer
 | 
			
		||||
// config created from the provided [core.App] and [models.User] instances
 | 
			
		||||
// config created from the provided [core.App] instance
 | 
			
		||||
// (for create you could pass a pointer to an empty User - `&models.User{}`).
 | 
			
		||||
//
 | 
			
		||||
// This factory method is used primarily for convenience (and backward compatibility).
 | 
			
		||||
// If you want to submit the form as part of another transaction, use
 | 
			
		||||
// [NewUserUpsertWithConfig] with Dao configured to your txDao.
 | 
			
		||||
// [NewUserEmailChangeConfirmWithConfig] with explicitly set TxDao.
 | 
			
		||||
func NewUserUpsert(app core.App, user *models.User) *UserUpsert {
 | 
			
		||||
	return NewUserUpsertWithConfig(UserUpsertConfig{
 | 
			
		||||
		Dao:      app.Dao(),
 | 
			
		||||
		Settings: app.Settings(),
 | 
			
		||||
		App: app,
 | 
			
		||||
	}, user)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserUpsertWithConfig creates a new [UserUpsert] form
 | 
			
		||||
// with the provided config and [models.User] instance or panics on invalid configuration
 | 
			
		||||
// 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{
 | 
			
		||||
| 
						 | 
				
			
			@ -55,12 +53,14 @@ func NewUserUpsertWithConfig(config UserUpsertConfig, user *models.User) *UserUp
 | 
			
		|||
		user:   user,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.Dao == nil ||
 | 
			
		||||
		form.config.Settings == nil ||
 | 
			
		||||
		form.user == nil {
 | 
			
		||||
	if form.config.App == nil || form.user == nil {
 | 
			
		||||
		panic("Invalid initializer config or nil upsert model.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.TxDao == nil {
 | 
			
		||||
		form.config.TxDao = form.config.App.Dao()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// load defaults
 | 
			
		||||
	form.Id = user.Id
 | 
			
		||||
	form.Email = user.Email
 | 
			
		||||
| 
						 | 
				
			
			@ -89,7 +89,7 @@ func (form *UserUpsert) Validate() error {
 | 
			
		|||
		validation.Field(
 | 
			
		||||
			&form.Password,
 | 
			
		||||
			validation.When(form.user.IsNew(), validation.Required),
 | 
			
		||||
			validation.Length(form.config.Settings.EmailAuth.MinPasswordLength, 100),
 | 
			
		||||
			validation.Length(form.config.App.Settings().EmailAuth.MinPasswordLength, 100),
 | 
			
		||||
		),
 | 
			
		||||
		validation.Field(
 | 
			
		||||
			&form.PasswordConfirm,
 | 
			
		||||
| 
						 | 
				
			
			@ -102,7 +102,7 @@ func (form *UserUpsert) Validate() error {
 | 
			
		|||
func (form *UserUpsert) checkUniqueEmail(value any) error {
 | 
			
		||||
	v, _ := value.(string)
 | 
			
		||||
 | 
			
		||||
	if v == "" || form.config.Dao.IsUserEmailUnique(v, form.user.Id) {
 | 
			
		||||
	if v == "" || form.config.TxDao.IsUserEmailUnique(v, form.user.Id) {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -116,8 +116,8 @@ func (form *UserUpsert) checkEmailDomain(value any) error {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	domain := val[strings.LastIndex(val, "@")+1:]
 | 
			
		||||
	only := form.config.Settings.EmailAuth.OnlyDomains
 | 
			
		||||
	except := form.config.Settings.EmailAuth.ExceptDomains
 | 
			
		||||
	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) {
 | 
			
		||||
| 
						 | 
				
			
			@ -159,6 +159,6 @@ func (form *UserUpsert) Submit(interceptors ...InterceptorFunc) error {
 | 
			
		|||
	form.user.Email = form.Email
 | 
			
		||||
 | 
			
		||||
	return runInterceptors(func() error {
 | 
			
		||||
		return form.config.Dao.SaveUser(form.user)
 | 
			
		||||
		return form.config.TxDao.SaveUser(form.user)
 | 
			
		||||
	}, interceptors...)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,21 +3,51 @@ package forms
 | 
			
		|||
import (
 | 
			
		||||
	validation "github.com/go-ozzo/ozzo-validation/v4"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/core"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/daos"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// UserVerificationConfirm defines a user email confirmation form.
 | 
			
		||||
// UserVerificationConfirm specifies a user email verification confirmation form.
 | 
			
		||||
type UserVerificationConfirm struct {
 | 
			
		||||
	app core.App
 | 
			
		||||
	config UserVerificationConfirmConfig
 | 
			
		||||
 | 
			
		||||
	Token string `form:"token" json:"token"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserVerificationConfirm creates a new user email confirmation form.
 | 
			
		||||
// UserVerificationConfirmConfig is the [UserVerificationConfirm]
 | 
			
		||||
// factory initializer config.
 | 
			
		||||
//
 | 
			
		||||
// NB! App is required struct member.
 | 
			
		||||
type UserVerificationConfirmConfig struct {
 | 
			
		||||
	App   core.App
 | 
			
		||||
	TxDao *daos.Dao
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserVerificationConfirm creates a new [UserVerificationConfirm]
 | 
			
		||||
// form with initializer config created from the provided [core.App] instance.
 | 
			
		||||
//
 | 
			
		||||
// If you want to submit the form as part of another transaction, use
 | 
			
		||||
// [NewUserVerificationConfirmWithConfig] with explicitly set TxDao.
 | 
			
		||||
func NewUserVerificationConfirm(app core.App) *UserVerificationConfirm {
 | 
			
		||||
	return &UserVerificationConfirm{
 | 
			
		||||
		app: app,
 | 
			
		||||
	return NewUserVerificationConfirmWithConfig(UserVerificationConfirmConfig{
 | 
			
		||||
		App: app,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserVerificationConfirmWithConfig creates a new [UserVerificationConfirmConfig]
 | 
			
		||||
// form with the provided config or panics on invalid configuration.
 | 
			
		||||
func NewUserVerificationConfirmWithConfig(config UserVerificationConfirmConfig) *UserVerificationConfirm {
 | 
			
		||||
	form := &UserVerificationConfirm{config: config}
 | 
			
		||||
 | 
			
		||||
	if form.config.App == nil {
 | 
			
		||||
		panic("Missing required config.App instance.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.TxDao == nil {
 | 
			
		||||
		form.config.TxDao = form.config.App.Dao()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return form
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate makes the form validatable by implementing [validation.Validatable] interface.
 | 
			
		||||
| 
						 | 
				
			
			@ -33,9 +63,9 @@ func (form *UserVerificationConfirm) checkToken(value any) error {
 | 
			
		|||
		return nil // nothing to check
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	user, err := form.app.Dao().FindUserByToken(
 | 
			
		||||
	user, err := form.config.TxDao.FindUserByToken(
 | 
			
		||||
		v,
 | 
			
		||||
		form.app.Settings().UserVerificationToken.Secret,
 | 
			
		||||
		form.config.App.Settings().UserVerificationToken.Secret,
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil || user == nil {
 | 
			
		||||
		return validation.NewError("validation_invalid_token", "Invalid or expired token.")
 | 
			
		||||
| 
						 | 
				
			
			@ -51,9 +81,9 @@ func (form *UserVerificationConfirm) Submit() (*models.User, error) {
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	user, err := form.app.Dao().FindUserByToken(
 | 
			
		||||
	user, err := form.config.TxDao.FindUserByToken(
 | 
			
		||||
		form.Token,
 | 
			
		||||
		form.app.Settings().UserVerificationToken.Secret,
 | 
			
		||||
		form.config.App.Settings().UserVerificationToken.Secret,
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
| 
						 | 
				
			
			@ -65,7 +95,7 @@ func (form *UserVerificationConfirm) Submit() (*models.User, error) {
 | 
			
		|||
 | 
			
		||||
	user.Verified = true
 | 
			
		||||
 | 
			
		||||
	if err := form.app.Dao().SaveUser(user); err != nil {
 | 
			
		||||
	if err := form.config.TxDao.SaveUser(user); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,24 +7,54 @@ import (
 | 
			
		|||
	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/mails"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/tools/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// UserVerificationRequest defines a user email verification request form.
 | 
			
		||||
type UserVerificationRequest struct {
 | 
			
		||||
	app             core.App
 | 
			
		||||
	resendThreshold float64
 | 
			
		||||
	config UserVerificationRequestConfig
 | 
			
		||||
 | 
			
		||||
	Email string `form:"email" json:"email"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserVerificationRequest creates a new user email verification request form.
 | 
			
		||||
// UserVerificationRequestConfig is the [UserVerificationRequest]
 | 
			
		||||
// factory initializer config.
 | 
			
		||||
//
 | 
			
		||||
// NB! App is required struct member.
 | 
			
		||||
type UserVerificationRequestConfig struct {
 | 
			
		||||
	App             core.App
 | 
			
		||||
	TxDao           *daos.Dao
 | 
			
		||||
	ResendThreshold float64 // in seconds
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserVerificationRequest creates a new [UserVerificationRequest]
 | 
			
		||||
// form with initializer config created from the provided [core.App] instance.
 | 
			
		||||
//
 | 
			
		||||
// If you want to submit the form as part of another transaction, use
 | 
			
		||||
// [NewUserVerificationRequestWithConfig] with explicitly set TxDao.
 | 
			
		||||
func NewUserVerificationRequest(app core.App) *UserVerificationRequest {
 | 
			
		||||
	return &UserVerificationRequest{
 | 
			
		||||
		app:             app,
 | 
			
		||||
		resendThreshold: 120, // 2 min
 | 
			
		||||
	return NewUserVerificationRequestWithConfig(UserVerificationRequestConfig{
 | 
			
		||||
		App:             app,
 | 
			
		||||
		ResendThreshold: 120, // 2 min
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUserVerificationRequestWithConfig creates a new [UserVerificationRequest]
 | 
			
		||||
// form with the provided config or panics on invalid configuration.
 | 
			
		||||
func NewUserVerificationRequestWithConfig(config UserVerificationRequestConfig) *UserVerificationRequest {
 | 
			
		||||
	form := &UserVerificationRequest{config: config}
 | 
			
		||||
 | 
			
		||||
	if form.config.App == nil {
 | 
			
		||||
		panic("Missing required config.App instance.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.config.TxDao == nil {
 | 
			
		||||
		form.config.TxDao = form.config.App.Dao()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return form
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate makes the form validatable by implementing [validation.Validatable] interface.
 | 
			
		||||
| 
						 | 
				
			
			@ -48,7 +78,7 @@ func (form *UserVerificationRequest) Submit() error {
 | 
			
		|||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	user, err := form.app.Dao().FindUserByEmail(form.Email)
 | 
			
		||||
	user, err := form.config.TxDao.FindUserByEmail(form.Email)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -59,16 +89,16 @@ func (form *UserVerificationRequest) Submit() error {
 | 
			
		|||
 | 
			
		||||
	now := time.Now().UTC()
 | 
			
		||||
	lastVerificationSentAt := user.LastVerificationSentAt.Time()
 | 
			
		||||
	if (now.Sub(lastVerificationSentAt)).Seconds() < form.resendThreshold {
 | 
			
		||||
	if (now.Sub(lastVerificationSentAt)).Seconds() < form.config.ResendThreshold {
 | 
			
		||||
		return errors.New("A verification email was already sent.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := mails.SendUserVerification(form.app, user); err != nil {
 | 
			
		||||
	if err := mails.SendUserVerification(form.config.App, user); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// update last sent timestamp
 | 
			
		||||
	user.LastVerificationSentAt = types.NowDateTime()
 | 
			
		||||
 | 
			
		||||
	return form.app.Dao().SaveUser(user)
 | 
			
		||||
	return form.config.TxDao.SaveUser(user)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue