mirror of
https://github.com/pocketbase/pocketbase.git
synced 2025-02-06 16:31:34 +00:00
691 lines
19 KiB
Go
691 lines
19 KiB
Go
package core
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
validation "github.com/go-ozzo/ozzo-validation/v4"
|
|
"github.com/pocketbase/dbx"
|
|
"github.com/pocketbase/pocketbase/core/validators"
|
|
"github.com/pocketbase/pocketbase/tools/dbutils"
|
|
"github.com/pocketbase/pocketbase/tools/list"
|
|
"github.com/pocketbase/pocketbase/tools/search"
|
|
"github.com/pocketbase/pocketbase/tools/types"
|
|
)
|
|
|
|
var collectionNameRegex = regexp.MustCompile(`^\w+$`)
|
|
|
|
func onCollectionValidate(e *CollectionEvent) error {
|
|
var original *Collection
|
|
if !e.Collection.IsNew() {
|
|
original = &Collection{}
|
|
if err := e.App.ModelQuery(original).Model(e.Collection.LastSavedPK(), original); err != nil {
|
|
return fmt.Errorf("failed to fetch old collection state: %w", err)
|
|
}
|
|
}
|
|
|
|
validator := newCollectionValidator(
|
|
e.Context,
|
|
e.App,
|
|
e.Collection,
|
|
original,
|
|
)
|
|
|
|
return validator.run()
|
|
}
|
|
|
|
func newCollectionValidator(ctx context.Context, app App, new, original *Collection) *collectionValidator {
|
|
validator := &collectionValidator{
|
|
ctx: ctx,
|
|
app: app,
|
|
new: new,
|
|
original: original,
|
|
}
|
|
|
|
// load old/original collection
|
|
if validator.original == nil {
|
|
validator.original = NewCollection(validator.new.Type, "")
|
|
}
|
|
|
|
return validator
|
|
}
|
|
|
|
type collectionValidator struct {
|
|
original *Collection
|
|
new *Collection
|
|
app App
|
|
ctx context.Context
|
|
}
|
|
|
|
type optionsValidator interface {
|
|
validate(cv *collectionValidator) error
|
|
}
|
|
|
|
func (validator *collectionValidator) run() error {
|
|
if validator.original.IsNew() {
|
|
validator.new.updateGeneratedIdIfExists(validator.app)
|
|
}
|
|
|
|
// generate fields from the query (overwriting any explicit user defined fields)
|
|
if validator.new.IsView() {
|
|
validator.new.Fields, _ = validator.app.CreateViewFields(validator.new.ViewQuery)
|
|
}
|
|
|
|
// validate base fields
|
|
baseErr := validation.ValidateStruct(validator.new,
|
|
validation.Field(
|
|
&validator.new.Id,
|
|
validation.Required,
|
|
validation.When(
|
|
validator.original.IsNew(),
|
|
validation.Length(1, 100),
|
|
validation.Match(DefaultIdRegex),
|
|
validation.By(validators.UniqueId(validator.app.DB(), validator.new.TableName())),
|
|
).Else(
|
|
validation.By(validators.Equal(validator.original.Id)),
|
|
),
|
|
),
|
|
validation.Field(
|
|
&validator.new.System,
|
|
validation.By(validator.ensureNoSystemFlagChange),
|
|
),
|
|
validation.Field(
|
|
&validator.new.Type,
|
|
validation.Required,
|
|
validation.In(
|
|
CollectionTypeBase,
|
|
CollectionTypeAuth,
|
|
CollectionTypeView,
|
|
),
|
|
validation.By(validator.ensureNoTypeChange),
|
|
),
|
|
validation.Field(
|
|
&validator.new.Name,
|
|
validation.Required,
|
|
validation.Length(1, 255),
|
|
validation.By(checkForVia),
|
|
validation.Match(collectionNameRegex),
|
|
validation.By(validator.ensureNoSystemNameChange),
|
|
validation.By(validator.checkUniqueName),
|
|
),
|
|
validation.Field(
|
|
&validator.new.Fields,
|
|
validation.By(validator.checkFieldDuplicates),
|
|
validation.By(validator.checkMinFields),
|
|
validation.When(
|
|
!validator.new.IsView(),
|
|
validation.By(validator.ensureNoSystemFieldsChange),
|
|
validation.By(validator.ensureNoFieldsTypeChange),
|
|
),
|
|
validation.When(validator.new.IsAuth(), validation.By(validator.checkReservedAuthKeys)),
|
|
validation.By(validator.checkFieldValidators),
|
|
),
|
|
validation.Field(
|
|
&validator.new.ListRule,
|
|
validation.By(validator.checkRule),
|
|
validation.By(validator.ensureNoSystemRuleChange(validator.original.ListRule)),
|
|
),
|
|
validation.Field(
|
|
&validator.new.ViewRule,
|
|
validation.By(validator.checkRule),
|
|
validation.By(validator.ensureNoSystemRuleChange(validator.original.ViewRule)),
|
|
),
|
|
validation.Field(
|
|
&validator.new.CreateRule,
|
|
validation.When(validator.new.IsView(), validation.Nil),
|
|
validation.By(validator.checkRule),
|
|
validation.By(validator.ensureNoSystemRuleChange(validator.original.CreateRule)),
|
|
),
|
|
validation.Field(
|
|
&validator.new.UpdateRule,
|
|
validation.When(validator.new.IsView(), validation.Nil),
|
|
validation.By(validator.checkRule),
|
|
validation.By(validator.ensureNoSystemRuleChange(validator.original.UpdateRule)),
|
|
),
|
|
validation.Field(
|
|
&validator.new.DeleteRule,
|
|
validation.When(validator.new.IsView(), validation.Nil),
|
|
validation.By(validator.checkRule),
|
|
validation.By(validator.ensureNoSystemRuleChange(validator.original.DeleteRule)),
|
|
),
|
|
validation.Field(&validator.new.Indexes, validation.By(validator.checkIndexes)),
|
|
)
|
|
|
|
optionsErr := validator.validateOptions()
|
|
|
|
return validators.JoinValidationErrors(baseErr, optionsErr)
|
|
}
|
|
|
|
func (validator *collectionValidator) checkUniqueName(value any) error {
|
|
v, _ := value.(string)
|
|
|
|
// ensure unique collection name
|
|
if !validator.app.IsCollectionNameUnique(v, validator.original.Id) {
|
|
return validation.NewError("validation_collection_name_exists", "Collection name must be unique (case insensitive).")
|
|
}
|
|
|
|
// ensure that the collection name doesn't collide with the id of any collection
|
|
dummyCollection := &Collection{}
|
|
if validator.app.ModelQuery(dummyCollection).Model(v, dummyCollection) == nil {
|
|
return validation.NewError("validation_collection_name_id_duplicate", "The name must not match an existing collection id.")
|
|
}
|
|
|
|
// ensure that there is no existing internal table with the provided name
|
|
if validator.original.Name != v && // has changed
|
|
validator.app.IsCollectionNameUnique(v) && // is not a collection (in case it was presaved)
|
|
validator.app.HasTable(v) {
|
|
return validation.NewError("validation_collection_name_invalid", "The name shouldn't match with an existing internal table.")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (validator *collectionValidator) ensureNoSystemNameChange(value any) error {
|
|
v, _ := value.(string)
|
|
|
|
if !validator.original.IsNew() && validator.original.System && v != validator.original.Name {
|
|
return validation.NewError("validation_collection_system_name_change", "System collection name cannot be changed.")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (validator *collectionValidator) ensureNoSystemFlagChange(value any) error {
|
|
v, _ := value.(bool)
|
|
|
|
if !validator.original.IsNew() && v != validator.original.System {
|
|
return validation.NewError("validation_collection_system_flag_change", "System collection state cannot be changed.")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (validator *collectionValidator) ensureNoTypeChange(value any) error {
|
|
v, _ := value.(string)
|
|
|
|
if !validator.original.IsNew() && v != validator.original.Type {
|
|
return validation.NewError("validation_collection_type_change", "Collection type cannot be changed.")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (validator *collectionValidator) ensureNoFieldsTypeChange(value any) error {
|
|
v, ok := value.(FieldsList)
|
|
if !ok {
|
|
return validators.ErrUnsupportedValueType
|
|
}
|
|
|
|
errs := validation.Errors{}
|
|
|
|
for i, field := range v {
|
|
oldField := validator.original.Fields.GetById(field.GetId())
|
|
|
|
if oldField != nil && oldField.Type() != field.Type() {
|
|
errs[strconv.Itoa(i)] = validation.NewError(
|
|
"validation_field_type_change",
|
|
"Field type cannot be changed.",
|
|
)
|
|
}
|
|
}
|
|
if len(errs) > 0 {
|
|
return errs
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (validator *collectionValidator) checkFieldDuplicates(value any) error {
|
|
fields, ok := value.(FieldsList)
|
|
if !ok {
|
|
return validators.ErrUnsupportedValueType
|
|
}
|
|
|
|
totalFields := len(fields)
|
|
ids := make([]string, 0, totalFields)
|
|
names := make([]string, 0, totalFields)
|
|
|
|
for i, field := range fields {
|
|
if list.ExistInSlice(field.GetId(), ids) {
|
|
return validation.Errors{
|
|
strconv.Itoa(i): validation.Errors{
|
|
"id": validation.NewError(
|
|
"validation_duplicated_field_id",
|
|
fmt.Sprintf("Duplicated or invalid field id %q", field.GetId()),
|
|
),
|
|
},
|
|
}
|
|
}
|
|
|
|
// field names are used as db columns and should be case insensitive
|
|
nameLower := strings.ToLower(field.GetName())
|
|
|
|
if list.ExistInSlice(nameLower, names) {
|
|
return validation.Errors{
|
|
strconv.Itoa(i): validation.Errors{
|
|
"name": validation.NewError(
|
|
"validation_duplicated_field_name",
|
|
"Duplicated or invalid field name {{.fieldName}}",
|
|
).SetParams(map[string]any{
|
|
"fieldName": field.GetName(),
|
|
}),
|
|
},
|
|
}
|
|
}
|
|
|
|
ids = append(ids, field.GetId())
|
|
names = append(names, nameLower)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (validator *collectionValidator) checkFieldValidators(value any) error {
|
|
fields, ok := value.(FieldsList)
|
|
if !ok {
|
|
return validators.ErrUnsupportedValueType
|
|
}
|
|
|
|
errs := validation.Errors{}
|
|
|
|
for i, field := range fields {
|
|
if err := field.ValidateSettings(validator.ctx, validator.app, validator.new); err != nil {
|
|
errs[strconv.Itoa(i)] = err
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return errs
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (cv *collectionValidator) checkViewQuery(value any) error {
|
|
v, _ := value.(string)
|
|
if v == "" {
|
|
return nil // nothing to check
|
|
}
|
|
|
|
if _, err := cv.app.CreateViewFields(v); err != nil {
|
|
return validation.NewError(
|
|
"validation_invalid_view_query",
|
|
fmt.Sprintf("Invalid query - %s", err.Error()),
|
|
)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
var reservedAuthKeys = []string{"passwordConfirm", "oldPassword"}
|
|
|
|
func (cv *collectionValidator) checkReservedAuthKeys(value any) error {
|
|
fields, ok := value.(FieldsList)
|
|
if !ok {
|
|
return validators.ErrUnsupportedValueType
|
|
}
|
|
|
|
if !cv.new.IsAuth() {
|
|
return nil // not an auth collection
|
|
}
|
|
|
|
errs := validation.Errors{}
|
|
for i, field := range fields {
|
|
if list.ExistInSlice(field.GetName(), reservedAuthKeys) {
|
|
errs[strconv.Itoa(i)] = validation.Errors{
|
|
"name": validation.NewError(
|
|
"validation_reserved_field_name",
|
|
"The field name is reserved and cannot be used.",
|
|
),
|
|
}
|
|
}
|
|
}
|
|
if len(errs) > 0 {
|
|
return errs
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (cv *collectionValidator) checkMinFields(value any) error {
|
|
fields, ok := value.(FieldsList)
|
|
if !ok {
|
|
return validators.ErrUnsupportedValueType
|
|
}
|
|
|
|
if len(fields) == 0 {
|
|
return validation.ErrRequired
|
|
}
|
|
|
|
// all collections must have an "id" PK field
|
|
idField, _ := fields.GetByName(FieldNameId).(*TextField)
|
|
if idField == nil || !idField.PrimaryKey {
|
|
return validation.NewError("validation_missing_primary_key", `Missing or invalid "id" PK field.`)
|
|
}
|
|
|
|
switch cv.new.Type {
|
|
case CollectionTypeAuth:
|
|
passwordField, _ := fields.GetByName(FieldNamePassword).(*PasswordField)
|
|
if passwordField == nil {
|
|
return validation.NewError("validation_missing_password_field", `System "password" field is required.`)
|
|
}
|
|
if !passwordField.Hidden || !passwordField.System {
|
|
return validation.Errors{FieldNamePassword: ErrMustBeSystemAndHidden}
|
|
}
|
|
|
|
tokenKeyField, _ := fields.GetByName(FieldNameTokenKey).(*TextField)
|
|
if tokenKeyField == nil {
|
|
return validation.NewError("validation_missing_tokenKey_field", `System "tokenKey" field is required.`)
|
|
}
|
|
if !tokenKeyField.Hidden || !tokenKeyField.System {
|
|
return validation.Errors{FieldNameTokenKey: ErrMustBeSystemAndHidden}
|
|
}
|
|
|
|
emailField, _ := fields.GetByName(FieldNameEmail).(*EmailField)
|
|
if emailField == nil {
|
|
return validation.NewError("validation_missing_email_field", `System "email" field is required.`)
|
|
}
|
|
if !emailField.System {
|
|
return validation.Errors{FieldNameEmail: ErrMustBeSystem}
|
|
}
|
|
|
|
emailVisibilityField, _ := fields.GetByName(FieldNameEmailVisibility).(*BoolField)
|
|
if emailVisibilityField == nil {
|
|
return validation.NewError("validation_missing_emailVisibility_field", `System "emailVisibility" field is required.`)
|
|
}
|
|
if !emailVisibilityField.System {
|
|
return validation.Errors{FieldNameEmailVisibility: ErrMustBeSystem}
|
|
}
|
|
|
|
verifiedField, _ := fields.GetByName(FieldNameVerified).(*BoolField)
|
|
if verifiedField == nil {
|
|
return validation.NewError("validation_missing_verified_field", `System "verified" field is required.`)
|
|
}
|
|
if !verifiedField.System {
|
|
return validation.Errors{FieldNameVerified: ErrMustBeSystem}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (validator *collectionValidator) ensureNoSystemFieldsChange(value any) error {
|
|
fields, ok := value.(FieldsList)
|
|
if !ok {
|
|
return validators.ErrUnsupportedValueType
|
|
}
|
|
|
|
if validator.original.IsNew() {
|
|
return nil // not an update
|
|
}
|
|
|
|
for _, oldField := range validator.original.Fields {
|
|
if !oldField.GetSystem() {
|
|
continue
|
|
}
|
|
|
|
newField := fields.GetById(oldField.GetId())
|
|
|
|
if newField == nil || oldField.GetName() != newField.GetName() {
|
|
return validation.NewError("validation_system_field_change", "System fields cannot be deleted or renamed.")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (cv *collectionValidator) checkFieldsForUniqueIndex(value any) error {
|
|
names, ok := value.([]string)
|
|
if !ok {
|
|
return validators.ErrUnsupportedValueType
|
|
}
|
|
|
|
if len(names) == 0 {
|
|
return nil // nothing to check
|
|
}
|
|
|
|
for _, name := range names {
|
|
field := cv.new.Fields.GetByName(name)
|
|
if field == nil {
|
|
return validation.NewError("validation_missing_field", "Invalid or missing field {{.fieldName}}").
|
|
SetParams(map[string]any{"fieldName": name})
|
|
}
|
|
|
|
if !dbutils.HasSingleColumnUniqueIndex(name, cv.new.Indexes) {
|
|
return validation.NewError("validation_missing_unique_constraint", "The field {{.fieldName}} doesn't have a UNIQUE constraint.").
|
|
SetParams(map[string]any{"fieldName": name})
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// note: value could be either *string or string
|
|
func (validator *collectionValidator) checkRule(value any) error {
|
|
var vStr string
|
|
|
|
v, ok := value.(*string)
|
|
if ok {
|
|
if v != nil {
|
|
vStr = *v
|
|
}
|
|
} else {
|
|
vStr, ok = value.(string)
|
|
}
|
|
if !ok {
|
|
return validators.ErrUnsupportedValueType
|
|
}
|
|
|
|
if vStr == "" {
|
|
return nil // nothing to check
|
|
}
|
|
|
|
r := NewRecordFieldResolver(validator.app, validator.new, nil, true)
|
|
_, err := search.FilterData(vStr).BuildExpr(r)
|
|
if err != nil {
|
|
return validation.NewError("validation_invalid_rule", "Invalid rule. Raw error: "+err.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (validator *collectionValidator) ensureNoSystemRuleChange(oldRule *string) validation.RuleFunc {
|
|
return func(value any) error {
|
|
if validator.original.IsNew() || !validator.original.System {
|
|
return nil // not an update of a system collection
|
|
}
|
|
|
|
rule, ok := value.(*string)
|
|
if !ok {
|
|
return validators.ErrUnsupportedValueType
|
|
}
|
|
|
|
if (rule == nil && oldRule == nil) ||
|
|
(rule != nil && oldRule != nil && *rule == *oldRule) {
|
|
return nil
|
|
}
|
|
|
|
return validation.NewError("validation_collection_system_rule_change", "System collection API rule cannot be changed.")
|
|
}
|
|
}
|
|
|
|
func (cv *collectionValidator) checkIndexes(value any) error {
|
|
indexes, _ := value.(types.JSONArray[string])
|
|
|
|
if cv.new.IsView() && len(indexes) > 0 {
|
|
return validation.NewError(
|
|
"validation_indexes_not_supported",
|
|
"View collections don't support indexes.",
|
|
)
|
|
}
|
|
|
|
duplicatedNames := make(map[string]struct{}, len(indexes))
|
|
duplicatedDefinitions := make(map[string]struct{}, len(indexes))
|
|
|
|
for i, rawIndex := range indexes {
|
|
parsed := dbutils.ParseIndex(rawIndex)
|
|
|
|
// always set a table name because it is ignored anyway in order to keep it in sync with the collection name
|
|
parsed.TableName = "validator"
|
|
|
|
if !parsed.IsValid() {
|
|
return validation.Errors{
|
|
strconv.Itoa(i): validation.NewError(
|
|
"validation_invalid_index_expression",
|
|
"Invalid CREATE INDEX expression.",
|
|
),
|
|
}
|
|
}
|
|
|
|
if _, isDuplicated := duplicatedNames[strings.ToLower(parsed.IndexName)]; isDuplicated {
|
|
return validation.Errors{
|
|
strconv.Itoa(i): validation.NewError(
|
|
"validation_duplicated_index_name",
|
|
"The index name already exists.",
|
|
),
|
|
}
|
|
}
|
|
duplicatedNames[strings.ToLower(parsed.IndexName)] = struct{}{}
|
|
|
|
// ensure that the index name is not used in another collection
|
|
var usedTblName string
|
|
_ = cv.app.DB().Select("tbl_name").
|
|
From("sqlite_master").
|
|
AndWhere(dbx.HashExp{"type": "index"}).
|
|
AndWhere(dbx.NewExp("LOWER([[tbl_name]])!=LOWER({:oldName})", dbx.Params{"oldName": cv.original.Name})).
|
|
AndWhere(dbx.NewExp("LOWER([[tbl_name]])!=LOWER({:newName})", dbx.Params{"newName": cv.new.Name})).
|
|
AndWhere(dbx.NewExp("LOWER([[name]])=LOWER({:indexName})", dbx.Params{"indexName": parsed.IndexName})).
|
|
Limit(1).
|
|
Row(&usedTblName)
|
|
if usedTblName != "" {
|
|
return validation.Errors{
|
|
strconv.Itoa(i): validation.NewError(
|
|
"validation_existing_index_name",
|
|
"The index name is already used in {{.usedTableName}} collection.",
|
|
).SetParams(map[string]any{"usedTableName": usedTblName}),
|
|
}
|
|
}
|
|
|
|
// reset non-important identifiers
|
|
parsed.SchemaName = "validator"
|
|
parsed.IndexName = "validator"
|
|
parsedDef := parsed.Build()
|
|
|
|
if _, isDuplicated := duplicatedDefinitions[parsedDef]; isDuplicated {
|
|
return validation.Errors{
|
|
strconv.Itoa(i): validation.NewError(
|
|
"validation_duplicated_index_definition",
|
|
"The index definition already exists.",
|
|
),
|
|
}
|
|
}
|
|
duplicatedDefinitions[parsedDef] = struct{}{}
|
|
|
|
// note: we don't check the index table name because it is always
|
|
// overwritten by the SyncRecordTableSchema to allow
|
|
// easier partial modifications (eg. changing only the collection name).
|
|
// if !strings.EqualFold(parsed.TableName, form.Name) {
|
|
// return validation.Errors{
|
|
// strconv.Itoa(i): validation.NewError(
|
|
// "validation_invalid_index_table",
|
|
// fmt.Sprintf("The index table must be the same as the collection name."),
|
|
// ),
|
|
// }
|
|
// }
|
|
}
|
|
|
|
// ensure that unique indexes on system fields are not changed or removed
|
|
if !cv.original.IsNew() {
|
|
OLD_INDEXES_LOOP:
|
|
for _, oldIndex := range cv.original.Indexes {
|
|
oldParsed := dbutils.ParseIndex(oldIndex)
|
|
if !oldParsed.Unique {
|
|
continue
|
|
}
|
|
|
|
// reset collate and sort since they are not important for the unique constraint
|
|
for i := range oldParsed.Columns {
|
|
oldParsed.Columns[i].Collate = ""
|
|
oldParsed.Columns[i].Sort = ""
|
|
}
|
|
|
|
oldParsedStr := oldParsed.Build()
|
|
|
|
for _, column := range oldParsed.Columns {
|
|
for _, f := range cv.original.Fields {
|
|
if !f.GetSystem() || !strings.EqualFold(column.Name, f.GetName()) {
|
|
continue
|
|
}
|
|
|
|
var hasMatch bool
|
|
for _, newIndex := range cv.new.Indexes {
|
|
newParsed := dbutils.ParseIndex(newIndex)
|
|
|
|
// exclude the non-important identifiers from the check
|
|
newParsed.SchemaName = oldParsed.SchemaName
|
|
newParsed.IndexName = oldParsed.IndexName
|
|
newParsed.TableName = oldParsed.TableName
|
|
|
|
// exclude partial constraints
|
|
newParsed.Where = oldParsed.Where
|
|
|
|
// reset collate and sort
|
|
for i := range newParsed.Columns {
|
|
newParsed.Columns[i].Collate = ""
|
|
newParsed.Columns[i].Sort = ""
|
|
}
|
|
|
|
if oldParsedStr == newParsed.Build() {
|
|
hasMatch = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !hasMatch {
|
|
return validation.NewError(
|
|
"validation_invalid_unique_system_field_index",
|
|
"Unique index definition on system fields ({{.fieldName}}) is invalid or missing.",
|
|
).SetParams(map[string]any{"fieldName": f.GetName()})
|
|
}
|
|
|
|
continue OLD_INDEXES_LOOP
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// check for required indexes
|
|
//
|
|
// note: this is in case the indexes were removed manually when creating/importing new auth collections
|
|
// and technically it is not necessary because on app.Save() the missing indexes will be reinserted by the system collection hook
|
|
if cv.new.IsAuth() {
|
|
requiredNames := []string{FieldNameTokenKey, FieldNameEmail}
|
|
for _, name := range requiredNames {
|
|
if !dbutils.HasSingleColumnUniqueIndex(name, indexes) {
|
|
return validation.NewError(
|
|
"validation_missing_required_unique_index",
|
|
`Missing required unique index for field "{{.fieldName}}".`,
|
|
).SetParams(map[string]any{"fieldName": name})
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (validator *collectionValidator) validateOptions() error {
|
|
switch validator.new.Type {
|
|
case CollectionTypeAuth:
|
|
return validator.new.collectionAuthOptions.validate(validator)
|
|
case CollectionTypeView:
|
|
return validator.new.collectionViewOptions.validate(validator)
|
|
}
|
|
|
|
return nil
|
|
}
|