added WithConfig factory to all forms

This commit is contained in:
Gani Georgiev 2022-08-07 15:38:21 +03:00
parent b0ca9b2f1b
commit a426484916
18 changed files with 542 additions and 195 deletions

View File

@ -10,7 +10,7 @@ import (
"github.com/pocketbase/pocketbase/models" "github.com/pocketbase/pocketbase/models"
) )
// AdminLogin defines an admin email/pass login form. // AdminLogin specifies an admin email/pass login form.
type AdminLogin struct { type AdminLogin struct {
config AdminLoginConfig config AdminLoginConfig
@ -20,20 +20,20 @@ type AdminLogin struct {
// AdminLoginConfig is the [AdminLogin] factory initializer config. // AdminLoginConfig is the [AdminLogin] factory initializer config.
// //
// NB! Dao is a required struct member. // NB! App is a required struct member.
type AdminLoginConfig struct { type AdminLoginConfig struct {
Dao *daos.Dao App core.App
TxDao *daos.Dao
} }
// NewAdminLogin creates a new [AdminLogin] form with initializer // NewAdminLogin creates a new [AdminLogin] form with initializer
// config created from the provided [core.App] instance. // 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 // 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 { func NewAdminLogin(app core.App) *AdminLogin {
return NewAdminLoginWithConfig(AdminLoginConfig{ return NewAdminLoginWithConfig(AdminLoginConfig{
Dao: app.Dao(), App: app,
}) })
} }
@ -42,8 +42,12 @@ func NewAdminLogin(app core.App) *AdminLogin {
func NewAdminLoginWithConfig(config AdminLoginConfig) *AdminLogin { func NewAdminLoginWithConfig(config AdminLoginConfig) *AdminLogin {
form := &AdminLogin{config: config} form := &AdminLogin{config: config}
if form.config.Dao == nil { if form.config.App == nil {
panic("Invalid initializer config.") panic("Missing required config.App instance.")
}
if form.config.TxDao == nil {
form.config.TxDao = form.config.App.Dao()
} }
return form return form
@ -64,7 +68,7 @@ func (form *AdminLogin) Submit() (*models.Admin, error) {
return nil, err return nil, err
} }
admin, err := form.config.Dao.FindAdminByEmail(form.Email) admin, err := form.config.TxDao.FindAdminByEmail(form.Email)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -3,24 +3,53 @@ package forms
import ( import (
validation "github.com/go-ozzo/ozzo-validation/v4" validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/daos"
"github.com/pocketbase/pocketbase/forms/validators" "github.com/pocketbase/pocketbase/forms/validators"
"github.com/pocketbase/pocketbase/models" "github.com/pocketbase/pocketbase/models"
) )
// AdminPasswordResetConfirm defines an admin password reset confirmation form. // AdminPasswordResetConfirm specifies an admin password reset confirmation form.
type AdminPasswordResetConfirm struct { type AdminPasswordResetConfirm struct {
app core.App config AdminPasswordResetConfirmConfig
Token string `form:"token" json:"token"` Token string `form:"token" json:"token"`
Password string `form:"password" json:"password"` Password string `form:"password" json:"password"`
PasswordConfirm string `form:"passwordConfirm" json:"passwordConfirm"` 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 { func NewAdminPasswordResetConfirm(app core.App) *AdminPasswordResetConfirm {
return &AdminPasswordResetConfirm{ return NewAdminPasswordResetConfirmWithConfig(AdminPasswordResetConfirmConfig{
app: app, 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. // 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 return nil // nothing to check
} }
admin, err := form.app.Dao().FindAdminByToken( admin, err := form.config.TxDao.FindAdminByToken(
v, v,
form.app.Settings().AdminPasswordResetToken.Secret, form.config.App.Settings().AdminPasswordResetToken.Secret,
) )
if err != nil || admin == nil { if err != nil || admin == nil {
return validation.NewError("validation_invalid_token", "Invalid or expired token.") return validation.NewError("validation_invalid_token", "Invalid or expired token.")
@ -56,9 +85,9 @@ func (form *AdminPasswordResetConfirm) Submit() (*models.Admin, error) {
return nil, err return nil, err
} }
admin, err := form.app.Dao().FindAdminByToken( admin, err := form.config.TxDao.FindAdminByToken(
form.Token, form.Token,
form.app.Settings().AdminPasswordResetToken.Secret, form.config.App.Settings().AdminPasswordResetToken.Secret,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -68,7 +97,7 @@ func (form *AdminPasswordResetConfirm) Submit() (*models.Admin, error) {
return nil, err return nil, err
} }
if err := form.app.Dao().SaveAdmin(admin); err != nil { if err := form.config.TxDao.SaveAdmin(admin); err != nil {
return nil, err return nil, err
} }

View File

@ -7,24 +7,53 @@ import (
validation "github.com/go-ozzo/ozzo-validation/v4" validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/go-ozzo/ozzo-validation/v4/is" "github.com/go-ozzo/ozzo-validation/v4/is"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/daos"
"github.com/pocketbase/pocketbase/mails" "github.com/pocketbase/pocketbase/mails"
"github.com/pocketbase/pocketbase/tools/types" "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 { type AdminPasswordResetRequest struct {
app core.App config AdminPasswordResetRequestConfig
resendThreshold float64
Email string `form:"email" json:"email"` 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 { func NewAdminPasswordResetRequest(app core.App) *AdminPasswordResetRequest {
return &AdminPasswordResetRequest{ return NewAdminPasswordResetRequestWithConfig(AdminPasswordResetRequestConfig{
app: app, App: app,
resendThreshold: 120, // 2 min 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. // Validate makes the form validatable by implementing [validation.Validatable] interface.
@ -48,23 +77,23 @@ func (form *AdminPasswordResetRequest) Submit() error {
return err return err
} }
admin, err := form.app.Dao().FindAdminByEmail(form.Email) admin, err := form.config.TxDao.FindAdminByEmail(form.Email)
if err != nil { if err != nil {
return err return err
} }
now := time.Now().UTC() now := time.Now().UTC()
lastResetSentAt := admin.LastResetSentAt.Time() 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.") 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 return err
} }
// update last sent timestamp // update last sent timestamp
admin.LastResetSentAt = types.NowDateTime() admin.LastResetSentAt = types.NowDateTime()
return form.app.Dao().SaveAdmin(admin) return form.config.TxDao.SaveAdmin(admin)
} }

View File

@ -23,21 +23,21 @@ type AdminUpsert struct {
// AdminUpsertConfig is the [AdminUpsert] factory initializer config. // AdminUpsertConfig is the [AdminUpsert] factory initializer config.
// //
// NB! Dao is a required struct member. // NB! App is a required struct member.
type AdminUpsertConfig struct { type AdminUpsertConfig struct {
Dao *daos.Dao App core.App
TxDao *daos.Dao
} }
// NewAdminUpsert creates a new [AdminUpsert] form with initializer // NewAdminUpsert creates a new [AdminUpsert] form with initializer
// config created from the provided [core.App] and [models.Admin] instances // config created from the provided [core.App] and [models.Admin] instances
// (for create you could pass a pointer to an empty Admin - `&models.Admin{}`). // (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 // 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 { func NewAdminUpsert(app core.App, admin *models.Admin) *AdminUpsert {
return NewAdminUpsertWithConfig(AdminUpsertConfig{ return NewAdminUpsertWithConfig(AdminUpsertConfig{
Dao: app.Dao(), App: app,
}, admin) }, admin)
} }
@ -50,10 +50,14 @@ func NewAdminUpsertWithConfig(config AdminUpsertConfig, admin *models.Admin) *Ad
admin: admin, 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.") panic("Invalid initializer config or nil upsert model.")
} }
if form.config.TxDao == nil {
form.config.TxDao = form.config.App.Dao()
}
// load defaults // load defaults
form.Id = admin.Id form.Id = admin.Id
form.Avatar = admin.Avatar form.Avatar = admin.Avatar
@ -100,7 +104,7 @@ func (form *AdminUpsert) Validate() error {
func (form *AdminUpsert) checkUniqueEmail(value any) error { func (form *AdminUpsert) checkUniqueEmail(value any) error {
v, _ := value.(string) v, _ := value.(string)
if form.config.Dao.IsAdminEmailUnique(v, form.admin.Id) { if form.config.TxDao.IsAdminEmailUnique(v, form.admin.Id) {
return nil return nil
} }
@ -130,6 +134,6 @@ func (form *AdminUpsert) Submit(interceptors ...InterceptorFunc) error {
} }
return runInterceptors(func() error { return runInterceptors(func() error {
return form.config.Dao.SaveAdmin(form.admin) return form.config.TxDao.SaveAdmin(form.admin)
}, interceptors...) }, interceptors...)
} }

View File

@ -33,21 +33,21 @@ type CollectionUpsert struct {
// CollectionUpsertConfig is the [CollectionUpsert] factory initializer config. // CollectionUpsertConfig is the [CollectionUpsert] factory initializer config.
// //
// NB! Dao is a required struct member. // NB! App is a required struct member.
type CollectionUpsertConfig struct { type CollectionUpsertConfig struct {
Dao *daos.Dao App core.App
TxDao *daos.Dao
} }
// NewCollectionUpsert creates a new [CollectionUpsert] form with initializer // NewCollectionUpsert creates a new [CollectionUpsert] form with initializer
// config created from the provided [core.App] and [models.Collection] instances // config created from the provided [core.App] and [models.Collection] instances
// (for create you could pass a pointer to an empty Collection - `&models.Collection{}`). // (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 // 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 { func NewCollectionUpsert(app core.App, collection *models.Collection) *CollectionUpsert {
return NewCollectionUpsertWithConfig(CollectionUpsertConfig{ return NewCollectionUpsertWithConfig(CollectionUpsertConfig{
Dao: app.Dao(), App: app,
}, collection) }, collection)
} }
@ -60,10 +60,14 @@ func NewCollectionUpsertWithConfig(config CollectionUpsertConfig, collection *mo
collection: collection, 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.") panic("Invalid initializer config or nil upsert model.")
} }
if form.config.TxDao == nil {
form.config.TxDao = form.config.App.Dao()
}
// load defaults // load defaults
form.Id = form.collection.Id form.Id = form.collection.Id
form.Name = form.collection.Name form.Name = form.collection.Name
@ -123,11 +127,11 @@ func (form *CollectionUpsert) Validate() error {
func (form *CollectionUpsert) checkUniqueName(value any) error { func (form *CollectionUpsert) checkUniqueName(value any) error {
v, _ := value.(string) 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).") 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.") 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} 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) _, err := search.FilterData(*v).BuildExpr(r)
if err != nil { if err != nil {
@ -239,6 +243,6 @@ func (form *CollectionUpsert) Submit(interceptors ...InterceptorFunc) error {
form.collection.DeleteRule = form.DeleteRule form.collection.DeleteRule = form.DeleteRule
return runInterceptors(func() error { return runInterceptors(func() error {
return form.config.Dao.SaveCollection(form.collection) return form.config.TxDao.SaveCollection(form.collection)
}, interceptors...) }, interceptors...)
} }

View File

@ -22,10 +22,21 @@ type CollectionsImport struct {
// CollectionsImportConfig is the [CollectionsImport] factory initializer config. // CollectionsImportConfig is the [CollectionsImport] factory initializer config.
// //
// NB! Dao is a required struct member. // NB! App is a required struct member.
type CollectionsImportConfig struct { type CollectionsImportConfig struct {
Dao *daos.Dao App core.App
IsDebug bool 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] // NewCollectionsImportWithConfig creates a new [CollectionsImport]
@ -33,26 +44,17 @@ type CollectionsImportConfig struct {
func NewCollectionsImportWithConfig(config CollectionsImportConfig) *CollectionsImport { func NewCollectionsImportWithConfig(config CollectionsImportConfig) *CollectionsImport {
form := &CollectionsImport{config: config} form := &CollectionsImport{config: config}
if form.config.Dao == nil { if form.config.App == nil {
panic("Invalid initializer config.") panic("Missing required config.App instance.")
}
if form.config.TxDao == nil {
form.config.TxDao = form.config.App.Dao()
} }
return form 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. // Validate makes the form validatable by implementing [validation.Validatable] interface.
func (form *CollectionsImport) Validate() error { func (form *CollectionsImport) Validate() error {
return validation.ValidateStruct(form, return validation.ValidateStruct(form,
@ -73,7 +75,7 @@ func (form *CollectionsImport) Submit() error {
return err 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{} oldCollections := []*models.Collection{}
if err := txDao.CollectionQuery().All(&oldCollections); err != nil { if err := txDao.CollectionQuery().All(&oldCollections); err != nil {
return err return err
@ -95,7 +97,7 @@ func (form *CollectionsImport) Submit() error {
if mappedFormCollections[old.GetId()] == nil { if mappedFormCollections[old.GetId()] == nil {
// delete the collection // delete the collection
if err := txDao.DeleteCollection(old); err != nil { if err := txDao.DeleteCollection(old); err != nil {
if form.config.IsDebug { if form.config.App.IsDebug() {
log.Println("[CollectionsImport] DeleteOthers failure", old.Name, err) log.Println("[CollectionsImport] DeleteOthers failure", old.Name, err)
} }
return validation.Errors{"collections": validation.NewError( return validation.Errors{"collections": validation.NewError(
@ -117,7 +119,7 @@ func (form *CollectionsImport) Submit() error {
} }
if err := txDao.Save(collection); err != nil { if err := txDao.Save(collection); err != nil {
if form.config.IsDebug { if form.config.App.IsDebug() {
log.Println("[CollectionsImport] Save failure", collection.Name, err) log.Println("[CollectionsImport] Save failure", collection.Name, err)
} }
return validation.Errors{"collections": validation.NewError( return validation.Errors{"collections": validation.NewError(
@ -141,7 +143,8 @@ func (form *CollectionsImport) Submit() error {
upsertModel = collection upsertModel = collection
} }
upsertForm := NewCollectionUpsertWithConfig(CollectionUpsertConfig{ upsertForm := NewCollectionUpsertWithConfig(CollectionUpsertConfig{
Dao: txDao, App: form.config.App,
TxDao: txDao,
}, upsertModel) }, upsertModel)
// load form fields with the refreshed collection state // load form fields with the refreshed collection state
upsertForm.Id = collection.Id upsertForm.Id = collection.Id
@ -168,7 +171,7 @@ func (form *CollectionsImport) Submit() error {
for _, collection := range form.Collections { for _, collection := range form.Collections {
oldCollection := mappedOldCollections[collection.GetId()] oldCollection := mappedOldCollections[collection.GetId()]
if err := txDao.SyncRecordTableSchema(collection, oldCollection); err != nil { 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) log.Println("[CollectionsImport] Records table sync failure", collection.Name, err)
} }
return validation.Errors{"collections": validation.NewError( return validation.Errors{"collections": validation.NewError(

View File

@ -4,7 +4,7 @@ import (
validation "github.com/go-ozzo/ozzo-validation/v4" validation "github.com/go-ozzo/ozzo-validation/v4"
) )
// RealtimeSubscribe defines a RealtimeSubscribe request form. // RealtimeSubscribe specifies a RealtimeSubscribe request form.
type RealtimeSubscribe struct { type RealtimeSubscribe struct {
ClientId string `form:"clientId" json:"clientId"` ClientId string `form:"clientId" json:"clientId"`
Subscriptions []string `form:"subscriptions" json:"subscriptions"` Subscriptions []string `form:"subscriptions" json:"subscriptions"`

View File

@ -16,7 +16,6 @@ import (
"github.com/pocketbase/pocketbase/forms/validators" "github.com/pocketbase/pocketbase/forms/validators"
"github.com/pocketbase/pocketbase/models" "github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/models/schema" "github.com/pocketbase/pocketbase/models/schema"
"github.com/pocketbase/pocketbase/tools/filesystem"
"github.com/pocketbase/pocketbase/tools/list" "github.com/pocketbase/pocketbase/tools/list"
"github.com/pocketbase/pocketbase/tools/rest" "github.com/pocketbase/pocketbase/tools/rest"
"github.com/spf13/cast" "github.com/spf13/cast"
@ -36,25 +35,21 @@ type RecordUpsert struct {
// RecordUpsertConfig is the [RecordUpsert] factory initializer config. // RecordUpsertConfig is the [RecordUpsert] factory initializer config.
// //
// NB! Dao and FilesystemFactory are required struct members. // NB! App is required struct member.
type RecordUpsertConfig struct { type RecordUpsertConfig struct {
Dao *daos.Dao App core.App
FilesystemFactory func() (*filesystem.System, error) TxDao *daos.Dao
IsDebug bool
} }
// NewRecordUpsert creates a new [RecordUpsert] form with initializer // NewRecordUpsert creates a new [RecordUpsert] form with initializer
// config created from the provided [core.App] and [models.Record] instances // config created from the provided [core.App] and [models.Record] instances
// (for create you could pass a pointer to an empty Record - `&models.Record{}`). // (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 // 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 { func NewRecordUpsert(app core.App, record *models.Record) *RecordUpsert {
return NewRecordUpsertWithConfig(RecordUpsertConfig{ return NewRecordUpsertWithConfig(RecordUpsertConfig{
Dao: app.Dao(), App: app,
FilesystemFactory: app.NewFilesystem,
IsDebug: app.IsDebug(),
}, record) }, record)
} }
@ -69,12 +64,14 @@ func NewRecordUpsertWithConfig(config RecordUpsertConfig, record *models.Record)
filesToUpload: []*rest.UploadedFile{}, filesToUpload: []*rest.UploadedFile{},
} }
if form.config.Dao == nil || if form.config.App == nil || form.record == nil {
form.config.FilesystemFactory == nil ||
form.record == nil {
panic("Invalid initializer config or nil upsert model.") 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.Id = record.Id
form.Data = map[string]any{} 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 // check if there are any new uploaded form files
files, err := rest.FindUploadedFiles(r, key) files, err := rest.FindUploadedFiles(r, key)
if err != nil { if err != nil {
if form.config.IsDebug { if form.config.App.IsDebug() {
log.Printf("%q uploaded file error: %v\n", key, err) log.Printf("%q uploaded file error: %v\n", key, err)
} }
@ -288,7 +285,7 @@ func (form *RecordUpsert) Validate() error {
// record data validator // record data validator
dataValidator := validators.NewRecordDataValidator( dataValidator := validators.NewRecordDataValidator(
form.config.Dao, form.config.TxDao,
form.record, form.record,
form.filesToUpload, form.filesToUpload,
) )
@ -316,7 +313,7 @@ func (form *RecordUpsert) DrySubmit(callback func(txDao *daos.Dao) error) error
return err 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) tx, ok := txDao.DB().(*dbx.Tx)
if !ok { if !ok {
return errors.New("failed to get transaction db") return errors.New("failed to get transaction db")
@ -356,7 +353,7 @@ func (form *RecordUpsert) Submit(interceptors ...InterceptorFunc) error {
} }
return runInterceptors(func() 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 // persist record model
if err := txDao.SaveRecord(form.record); err != nil { if err := txDao.SaveRecord(form.record); err != nil {
return err return err
@ -387,7 +384,7 @@ func (form *RecordUpsert) processFilesToUpload() error {
return errors.New("The record is not persisted yet.") return errors.New("The record is not persisted yet.")
} }
fs, err := form.config.FilesystemFactory() fs, err := form.config.App.NewFilesystem()
if err != nil { if err != nil {
return err return err
} }
@ -423,7 +420,7 @@ func (form *RecordUpsert) processFilesToDelete() error {
return errors.New("The record is not persisted yet.") return errors.New("The record is not persisted yet.")
} }
fs, err := form.config.FilesystemFactory() fs, err := form.config.App.NewFilesystem()
if err != nil { if err != nil {
return err return err
} }

View File

@ -5,22 +5,56 @@ import (
"time" "time"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/daos"
"github.com/pocketbase/pocketbase/models" "github.com/pocketbase/pocketbase/models"
) )
// SettingsUpsert defines app settings upsert form. // SettingsUpsert specifies a [core.Settings] upsert (create/update) form.
type SettingsUpsert struct { type SettingsUpsert struct {
*core.Settings *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 { 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 // load the application settings into the form
form.Settings, _ = app.Settings().Clone() form.Settings, _ = config.App.Settings().Clone()
return form return form
} }
@ -41,10 +75,10 @@ func (form *SettingsUpsert) Submit(interceptors ...InterceptorFunc) error {
return err return err
} }
encryptionKey := os.Getenv(form.app.EncryptionEnv()) encryptionKey := os.Getenv(form.config.App.EncryptionEnv())
return runInterceptors(func() error { return runInterceptors(func() error {
saveErr := form.app.Dao().SaveParam( saveErr := form.config.TxDao.SaveParam(
models.ParamAppSettings, models.ParamAppSettings,
form.Settings, form.Settings,
encryptionKey, encryptionKey,
@ -54,11 +88,11 @@ func (form *SettingsUpsert) Submit(interceptors ...InterceptorFunc) error {
} }
// explicitly trigger old logs deletion // explicitly trigger old logs deletion
form.app.LogsDao().DeleteOldRequests( form.config.TxLogsDao.DeleteOldRequests(
time.Now().AddDate(0, 0, -1*form.Settings.Logs.MaxDays), time.Now().AddDate(0, 0, -1*form.Settings.Logs.MaxDays),
) )
// merge the application settings with the form ones // merge the application settings with the form ones
return form.app.Settings().Merge(form.Settings) return form.config.App.Settings().Merge(form.Settings)
}, interceptors...) }, interceptors...)
} }

View File

@ -3,23 +3,53 @@ package forms
import ( import (
validation "github.com/go-ozzo/ozzo-validation/v4" validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/daos"
"github.com/pocketbase/pocketbase/models" "github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/tools/security" "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 { type UserEmailChangeConfirm struct {
app core.App config UserEmailChangeConfirmConfig
Token string `form:"token" json:"token"` Token string `form:"token" json:"token"`
Password string `form:"password" json:"password"` 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 { func NewUserEmailChangeConfirm(app core.App) *UserEmailChangeConfirm {
return &UserEmailChangeConfirm{ return NewUserEmailChangeConfirmWithConfig(UserEmailChangeConfirmConfig{
app: app, 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. // 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 // 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) 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 // verify that the token is not expired and its signature is valid
user, err := form.app.Dao().FindUserByToken( user, err := form.config.TxDao.FindUserByToken(
token, token,
form.app.Settings().UserEmailChangeToken.Secret, form.config.App.Settings().UserEmailChangeToken.Secret,
) )
if err != nil || user == nil { if err != nil || user == nil {
return nil, "", validation.NewError("validation_invalid_token", "Invalid or expired token.") 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.Verified = true
user.RefreshTokenKey() // invalidate old tokens 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 return nil, err
} }

View File

@ -4,24 +4,55 @@ import (
validation "github.com/go-ozzo/ozzo-validation/v4" validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/go-ozzo/ozzo-validation/v4/is" "github.com/go-ozzo/ozzo-validation/v4/is"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/daos"
"github.com/pocketbase/pocketbase/mails" "github.com/pocketbase/pocketbase/mails"
"github.com/pocketbase/pocketbase/models" "github.com/pocketbase/pocketbase/models"
) )
// UserEmailChangeRequest defines a user email change request form. // UserEmailChangeRequest defines a user email change request form.
type UserEmailChangeRequest struct { type UserEmailChangeRequest struct {
app core.App config UserEmailChangeRequestConfig
user *models.User user *models.User
NewEmail string `form:"newEmail" json:"newEmail"` 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 { func NewUserEmailChangeRequest(app core.App, user *models.User) *UserEmailChangeRequest {
return &UserEmailChangeRequest{ return NewUserEmailChangeRequestWithConfig(UserEmailChangeRequestConfig{
app: app, 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, 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. // 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 { func (form *UserEmailChangeRequest) checkUniqueEmail(value any) error {
v, _ := value.(string) 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.") return validation.NewError("validation_user_email_exists", "User email already exists.")
} }
@ -53,5 +84,5 @@ func (form *UserEmailChangeRequest) Submit() error {
return err return err
} }
return mails.SendUserChangeEmail(form.app, form.user, form.NewEmail) return mails.SendUserChangeEmail(form.config.App, form.user, form.NewEmail)
} }

View File

@ -4,21 +4,49 @@ import (
validation "github.com/go-ozzo/ozzo-validation/v4" validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/go-ozzo/ozzo-validation/v4/is" "github.com/go-ozzo/ozzo-validation/v4/is"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/daos"
"github.com/pocketbase/pocketbase/models" "github.com/pocketbase/pocketbase/models"
) )
// UserEmailLogin defines a user email/pass login form. // UserEmailLogin specifies a user email/pass login form.
type UserEmailLogin struct { type UserEmailLogin struct {
app core.App config UserEmailLoginConfig
Email string `form:"email" json:"email"` Email string `form:"email" json:"email"`
Password string `form:"password" json:"password"` 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 { func NewUserEmailLogin(app core.App) *UserEmailLogin {
form := &UserEmailLogin{ return NewUserEmailLoginWithConfig(UserEmailLoginConfig{
app: app, 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 return form
@ -39,7 +67,7 @@ func (form *UserEmailLogin) Submit() (*models.User, error) {
return nil, err return nil, err
} }
user, err := form.app.Dao().FindUserByEmail(form.Email) user, err := form.config.TxDao.FindUserByEmail(form.Email)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -7,15 +7,16 @@ import (
validation "github.com/go-ozzo/ozzo-validation/v4" validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/go-ozzo/ozzo-validation/v4/is" "github.com/go-ozzo/ozzo-validation/v4/is"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/daos"
"github.com/pocketbase/pocketbase/models" "github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/tools/auth" "github.com/pocketbase/pocketbase/tools/auth"
"github.com/pocketbase/pocketbase/tools/security" "github.com/pocketbase/pocketbase/tools/security"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
// UserOauth2Login defines a user Oauth2 login form. // UserOauth2Login specifies a user Oauth2 login form.
type UserOauth2Login struct { type UserOauth2Login struct {
app core.App config UserOauth2LoginConfig
// The name of the OAuth2 client provider (eg. "google") // The name of the OAuth2 client provider (eg. "google")
Provider string `form:"provider" json:"provider"` Provider string `form:"provider" json:"provider"`
@ -30,9 +31,39 @@ type UserOauth2Login struct {
RedirectUrl string `form:"redirectUrl" json:"redirectUrl"` 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 { 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. // 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 { func (form *UserOauth2Login) checkProviderName(value any) error {
name, _ := value.(string) name, _ := value.(string)
config, ok := form.app.Settings().NamedAuthProviderConfigs()[name] config, ok := form.config.App.Settings().NamedAuthProviderConfigs()[name]
if !ok || !config.Enabled { if !ok || !config.Enabled {
return validation.NewError("validation_invalid_provider", fmt.Sprintf("%q is missing or is not enabled.", name)) 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 return nil, nil, err
} }
config := form.app.Settings().NamedAuthProviderConfigs()[form.Provider] config := form.config.App.Settings().NamedAuthProviderConfigs()[form.Provider]
config.SetupProvider(provider) config.SetupProvider(provider)
provider.SetRedirectUrl(form.RedirectUrl) provider.SetRedirectUrl(form.RedirectUrl)
@ -89,12 +120,12 @@ func (form *UserOauth2Login) Submit() (*models.User, *auth.AuthUser, error) {
} }
// login/register the auth user // login/register the auth user
user, _ := form.app.Dao().FindUserByEmail(authData.Email) user, _ := form.config.TxDao.FindUserByEmail(authData.Email)
if user != nil { if user != nil {
// update the existing user's verified state // update the existing user's verified state
if !user.Verified { if !user.Verified {
user.Verified = true 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 return nil, authData, err
} }
} }
@ -108,7 +139,10 @@ func (form *UserOauth2Login) Submit() (*models.User, *auth.AuthUser, error) {
// create new user // create new user
user = &models.User{Verified: true} 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.Email = authData.Email
upsertForm.Password = security.RandomString(30) upsertForm.Password = security.RandomString(30)
upsertForm.PasswordConfirm = upsertForm.Password upsertForm.PasswordConfirm = upsertForm.Password
@ -118,7 +152,7 @@ func (form *UserOauth2Login) Submit() (*models.User, *auth.AuthUser, error) {
AuthData: authData, 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 return nil, authData, err
} }
@ -126,7 +160,7 @@ func (form *UserOauth2Login) Submit() (*models.User, *auth.AuthUser, error) {
return nil, authData, err 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 return nil, authData, err
} }

View File

@ -3,29 +3,59 @@ package forms
import ( import (
validation "github.com/go-ozzo/ozzo-validation/v4" validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/daos"
"github.com/pocketbase/pocketbase/forms/validators" "github.com/pocketbase/pocketbase/forms/validators"
"github.com/pocketbase/pocketbase/models" "github.com/pocketbase/pocketbase/models"
) )
// UserPasswordResetConfirm defines a user password reset confirmation form. // UserPasswordResetConfirm specifies a user password reset confirmation form.
type UserPasswordResetConfirm struct { type UserPasswordResetConfirm struct {
app core.App config UserPasswordResetConfirmConfig
Token string `form:"token" json:"token"` Token string `form:"token" json:"token"`
Password string `form:"password" json:"password"` Password string `form:"password" json:"password"`
PasswordConfirm string `form:"passwordConfirm" json:"passwordConfirm"` 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 { func NewUserPasswordResetConfirm(app core.App) *UserPasswordResetConfirm {
return &UserPasswordResetConfirm{ return NewUserPasswordResetConfirmWithConfig(UserPasswordResetConfirmConfig{
app: app, 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. // Validate makes the form validatable by implementing [validation.Validatable] interface.
func (form *UserPasswordResetConfirm) Validate() error { func (form *UserPasswordResetConfirm) Validate() error {
minPasswordLength := form.app.Settings().EmailAuth.MinPasswordLength minPasswordLength := form.config.App.Settings().EmailAuth.MinPasswordLength
return validation.ValidateStruct(form, return validation.ValidateStruct(form,
validation.Field(&form.Token, validation.Required, validation.By(form.checkToken)), 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 return nil // nothing to check
} }
user, err := form.app.Dao().FindUserByToken( user, err := form.config.TxDao.FindUserByToken(
v, v,
form.app.Settings().UserPasswordResetToken.Secret, form.config.App.Settings().UserPasswordResetToken.Secret,
) )
if err != nil || user == nil { if err != nil || user == nil {
return validation.NewError("validation_invalid_token", "Invalid or expired token.") return validation.NewError("validation_invalid_token", "Invalid or expired token.")
@ -58,9 +88,9 @@ func (form *UserPasswordResetConfirm) Submit() (*models.User, error) {
return nil, err return nil, err
} }
user, err := form.app.Dao().FindUserByToken( user, err := form.config.TxDao.FindUserByToken(
form.Token, form.Token,
form.app.Settings().UserPasswordResetToken.Secret, form.config.App.Settings().UserPasswordResetToken.Secret,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -70,7 +100,7 @@ func (form *UserPasswordResetConfirm) Submit() (*models.User, error) {
return nil, err return nil, err
} }
if err := form.app.Dao().SaveUser(user); err != nil { if err := form.config.TxDao.SaveUser(user); err != nil {
return nil, err return nil, err
} }

View File

@ -7,24 +7,54 @@ import (
validation "github.com/go-ozzo/ozzo-validation/v4" validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/go-ozzo/ozzo-validation/v4/is" "github.com/go-ozzo/ozzo-validation/v4/is"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/daos"
"github.com/pocketbase/pocketbase/mails" "github.com/pocketbase/pocketbase/mails"
"github.com/pocketbase/pocketbase/tools/types" "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 { type UserPasswordResetRequest struct {
app core.App config UserPasswordResetRequestConfig
resendThreshold float64
Email string `form:"email" json:"email"` 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 { func NewUserPasswordResetRequest(app core.App) *UserPasswordResetRequest {
return &UserPasswordResetRequest{ return NewUserPasswordResetRequestWithConfig(UserPasswordResetRequestConfig{
app: app, App: app,
resendThreshold: 120, // 2 min 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. // Validate makes the form validatable by implementing [validation.Validatable] interface.
@ -48,23 +78,23 @@ func (form *UserPasswordResetRequest) Submit() error {
return err return err
} }
user, err := form.app.Dao().FindUserByEmail(form.Email) user, err := form.config.TxDao.FindUserByEmail(form.Email)
if err != nil { if err != nil {
return err return err
} }
now := time.Now().UTC() now := time.Now().UTC()
lastResetSentAt := user.LastResetSentAt.Time() 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.") 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 return err
} }
// update last sent timestamp // update last sent timestamp
user.LastResetSentAt = types.NowDateTime() user.LastResetSentAt = types.NowDateTime()
return form.app.Dao().SaveUser(user) return form.config.TxDao.SaveUser(user)
} }

View File

@ -26,28 +26,26 @@ type UserUpsert struct {
// UserUpsertConfig is the [UserUpsert] factory initializer config. // UserUpsertConfig is the [UserUpsert] factory initializer config.
// //
// NB! Dao and Settings are required struct members. // NB! App is required struct member.
type UserUpsertConfig struct { type UserUpsertConfig struct {
Dao *daos.Dao App core.App
Settings *core.Settings TxDao *daos.Dao
} }
// NewUserUpsert creates a new [UserUpsert] form with initializer // 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{}`). // (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 // 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 { func NewUserUpsert(app core.App, user *models.User) *UserUpsert {
return NewUserUpsertWithConfig(UserUpsertConfig{ return NewUserUpsertWithConfig(UserUpsertConfig{
Dao: app.Dao(), App: app,
Settings: app.Settings(),
}, user) }, user)
} }
// NewUserUpsertWithConfig creates a new [UserUpsert] form // NewUserUpsertWithConfig creates a new [UserUpsert] form with the provided
// with the provided config and [models.User] instance or panics on invalid configuration // config and [models.User] instance or panics on invalid configuration
// (for create you could pass a pointer to an empty User - `&models.User{}`). // (for create you could pass a pointer to an empty User - `&models.User{}`).
func NewUserUpsertWithConfig(config UserUpsertConfig, user *models.User) *UserUpsert { func NewUserUpsertWithConfig(config UserUpsertConfig, user *models.User) *UserUpsert {
form := &UserUpsert{ form := &UserUpsert{
@ -55,12 +53,14 @@ func NewUserUpsertWithConfig(config UserUpsertConfig, user *models.User) *UserUp
user: user, user: user,
} }
if form.config.Dao == nil || if form.config.App == nil || form.user == nil {
form.config.Settings == nil ||
form.user == nil {
panic("Invalid initializer config or nil upsert model.") panic("Invalid initializer config or nil upsert model.")
} }
if form.config.TxDao == nil {
form.config.TxDao = form.config.App.Dao()
}
// load defaults // load defaults
form.Id = user.Id form.Id = user.Id
form.Email = user.Email form.Email = user.Email
@ -89,7 +89,7 @@ func (form *UserUpsert) Validate() error {
validation.Field( validation.Field(
&form.Password, &form.Password,
validation.When(form.user.IsNew(), validation.Required), 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( validation.Field(
&form.PasswordConfirm, &form.PasswordConfirm,
@ -102,7 +102,7 @@ func (form *UserUpsert) Validate() error {
func (form *UserUpsert) checkUniqueEmail(value any) error { func (form *UserUpsert) checkUniqueEmail(value any) error {
v, _ := value.(string) 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 return nil
} }
@ -116,8 +116,8 @@ func (form *UserUpsert) checkEmailDomain(value any) error {
} }
domain := val[strings.LastIndex(val, "@")+1:] domain := val[strings.LastIndex(val, "@")+1:]
only := form.config.Settings.EmailAuth.OnlyDomains only := form.config.App.Settings().EmailAuth.OnlyDomains
except := form.config.Settings.EmailAuth.ExceptDomains except := form.config.App.Settings().EmailAuth.ExceptDomains
// only domains check // only domains check
if len(only) > 0 && !list.ExistInSlice(domain, only) { if len(only) > 0 && !list.ExistInSlice(domain, only) {
@ -159,6 +159,6 @@ func (form *UserUpsert) Submit(interceptors ...InterceptorFunc) error {
form.user.Email = form.Email form.user.Email = form.Email
return runInterceptors(func() error { return runInterceptors(func() error {
return form.config.Dao.SaveUser(form.user) return form.config.TxDao.SaveUser(form.user)
}, interceptors...) }, interceptors...)
} }

View File

@ -3,21 +3,51 @@ package forms
import ( import (
validation "github.com/go-ozzo/ozzo-validation/v4" validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/daos"
"github.com/pocketbase/pocketbase/models" "github.com/pocketbase/pocketbase/models"
) )
// UserVerificationConfirm defines a user email confirmation form. // UserVerificationConfirm specifies a user email verification confirmation form.
type UserVerificationConfirm struct { type UserVerificationConfirm struct {
app core.App config UserVerificationConfirmConfig
Token string `form:"token" json:"token"` 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 { func NewUserVerificationConfirm(app core.App) *UserVerificationConfirm {
return &UserVerificationConfirm{ return NewUserVerificationConfirmWithConfig(UserVerificationConfirmConfig{
app: app, 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. // 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 return nil // nothing to check
} }
user, err := form.app.Dao().FindUserByToken( user, err := form.config.TxDao.FindUserByToken(
v, v,
form.app.Settings().UserVerificationToken.Secret, form.config.App.Settings().UserVerificationToken.Secret,
) )
if err != nil || user == nil { if err != nil || user == nil {
return validation.NewError("validation_invalid_token", "Invalid or expired token.") return validation.NewError("validation_invalid_token", "Invalid or expired token.")
@ -51,9 +81,9 @@ func (form *UserVerificationConfirm) Submit() (*models.User, error) {
return nil, err return nil, err
} }
user, err := form.app.Dao().FindUserByToken( user, err := form.config.TxDao.FindUserByToken(
form.Token, form.Token,
form.app.Settings().UserVerificationToken.Secret, form.config.App.Settings().UserVerificationToken.Secret,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -65,7 +95,7 @@ func (form *UserVerificationConfirm) Submit() (*models.User, error) {
user.Verified = true user.Verified = true
if err := form.app.Dao().SaveUser(user); err != nil { if err := form.config.TxDao.SaveUser(user); err != nil {
return nil, err return nil, err
} }

View File

@ -7,24 +7,54 @@ import (
validation "github.com/go-ozzo/ozzo-validation/v4" validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/go-ozzo/ozzo-validation/v4/is" "github.com/go-ozzo/ozzo-validation/v4/is"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/daos"
"github.com/pocketbase/pocketbase/mails" "github.com/pocketbase/pocketbase/mails"
"github.com/pocketbase/pocketbase/tools/types" "github.com/pocketbase/pocketbase/tools/types"
) )
// UserVerificationRequest defines a user email verification request form. // UserVerificationRequest defines a user email verification request form.
type UserVerificationRequest struct { type UserVerificationRequest struct {
app core.App config UserVerificationRequestConfig
resendThreshold float64
Email string `form:"email" json:"email"` 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 { func NewUserVerificationRequest(app core.App) *UserVerificationRequest {
return &UserVerificationRequest{ return NewUserVerificationRequestWithConfig(UserVerificationRequestConfig{
app: app, App: app,
resendThreshold: 120, // 2 min 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. // Validate makes the form validatable by implementing [validation.Validatable] interface.
@ -48,7 +78,7 @@ func (form *UserVerificationRequest) Submit() error {
return err return err
} }
user, err := form.app.Dao().FindUserByEmail(form.Email) user, err := form.config.TxDao.FindUserByEmail(form.Email)
if err != nil { if err != nil {
return err return err
} }
@ -59,16 +89,16 @@ func (form *UserVerificationRequest) Submit() error {
now := time.Now().UTC() now := time.Now().UTC()
lastVerificationSentAt := user.LastVerificationSentAt.Time() 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.") 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 return err
} }
// update last sent timestamp // update last sent timestamp
user.LastVerificationSentAt = types.NowDateTime() user.LastVerificationSentAt = types.NowDateTime()
return form.app.Dao().SaveUser(user) return form.config.TxDao.SaveUser(user)
} }