diff --git a/forms/admin_upsert.go b/forms/admin_upsert.go index b1212c09..1180b480 100644 --- a/forms/admin_upsert.go +++ b/forms/admin_upsert.go @@ -57,6 +57,7 @@ func (form *AdminUpsert) Validate() error { form.admin.IsNew(), validation.Length(models.DefaultIdLength, models.DefaultIdLength), validation.Match(idRegex), + validation.By(validators.UniqueId(form.dao, form.admin.TableName())), ).Else(validation.In(form.admin.Id)), ), validation.Field( diff --git a/forms/collection_upsert.go b/forms/collection_upsert.go index ca18948e..48ea4927 100644 --- a/forms/collection_upsert.go +++ b/forms/collection_upsert.go @@ -9,6 +9,7 @@ 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" "github.com/pocketbase/pocketbase/models/schema" "github.com/pocketbase/pocketbase/resolvers" @@ -93,6 +94,7 @@ func (form *CollectionUpsert) Validate() error { form.collection.IsNew(), validation.Length(models.DefaultIdLength, models.DefaultIdLength), validation.Match(idRegex), + validation.By(validators.UniqueId(form.dao, form.collection.TableName())), ).Else(validation.In(form.collection.Id)), ), validation.Field( diff --git a/forms/record_upsert.go b/forms/record_upsert.go index ce32e6f8..4a8e8020 100644 --- a/forms/record_upsert.go +++ b/forms/record_upsert.go @@ -455,6 +455,7 @@ func (form *RecordUpsert) Validate() error { form.record.IsNew(), validation.Length(models.DefaultIdLength, models.DefaultIdLength), validation.Match(idRegex), + validation.By(validators.UniqueId(form.dao, form.record.TableName())), ).Else(validation.In(form.record.Id)), ), } diff --git a/forms/validators/model.go b/forms/validators/model.go new file mode 100644 index 00000000..928207d5 --- /dev/null +++ b/forms/validators/model.go @@ -0,0 +1,38 @@ +package validators + +import ( + "database/sql" + "errors" + + validation "github.com/go-ozzo/ozzo-validation/v4" + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" +) + +// Compare checks whether the provided model id exists. +// +// Example: +// validation.Field(&form.Id, validation.By(validators.UniqueId(form.dao, tableName))) +func UniqueId(dao *daos.Dao, tableName string) validation.RuleFunc { + return func(value any) error { + v, _ := value.(string) + if v == "" { + return nil // nothing to check + } + + var foundId string + + err := dao.DB(). + Select("id"). + From(tableName). + Where(dbx.HashExp{"id": v}). + Limit(1). + Row(&foundId) + + if !errors.Is(err, sql.ErrNoRows) || foundId != "" { + return validation.NewError("validation_invalid_id", "The model id is invalid or already exists.") + } + + return nil + } +} diff --git a/forms/validators/model_test.go b/forms/validators/model_test.go new file mode 100644 index 00000000..45b486d5 --- /dev/null +++ b/forms/validators/model_test.go @@ -0,0 +1,34 @@ +package validators_test + +import ( + "testing" + + "github.com/pocketbase/pocketbase/forms/validators" + "github.com/pocketbase/pocketbase/tests" +) + +func TestUniqueId(t *testing.T) { + app, _ := tests.NewTestApp() + defer app.Cleanup() + + scenarios := []struct { + id string + tableName string + expectError bool + }{ + {"", "", false}, + {"test", "", true}, + {"wsmn24bux7wo113", "_collections", true}, + {"test_unique_id", "unknown_table", true}, + {"test_unique_id", "_collections", false}, + } + + for i, s := range scenarios { + err := validators.UniqueId(app.Dao(), s.tableName)(s.id) + + hasErr := err != nil + if hasErr != s.expectError { + t.Errorf("(%d) Expected hasErr to be %v, got %v (%v)", i, s.expectError, hasErr, err) + } + } +}