(no tests) collection indexes scaffoldings

This commit is contained in:
Gani Georgiev
2023-03-19 16:18:33 +02:00
parent 695c20a969
commit a0ec5707d1
11 changed files with 350 additions and 87 deletions

View File

@@ -4,6 +4,8 @@ import (
"encoding/json"
"fmt"
"regexp"
"strconv"
"strings"
validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/pocketbase/pocketbase/core"
@@ -12,6 +14,7 @@ import (
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/models/schema"
"github.com/pocketbase/pocketbase/resolvers"
"github.com/pocketbase/pocketbase/tools/dbutils"
"github.com/pocketbase/pocketbase/tools/list"
"github.com/pocketbase/pocketbase/tools/search"
"github.com/pocketbase/pocketbase/tools/types"
@@ -30,6 +33,7 @@ type CollectionUpsert struct {
Name string `form:"name" json:"name"`
System bool `form:"system" json:"system"`
Schema schema.Schema `form:"schema" json:"schema"`
Indexes []string `form:"indexes" json:"indexes"`
ListRule *string `form:"listRule" json:"listRule"`
ViewRule *string `form:"viewRule" json:"viewRule"`
CreateRule *string `form:"createRule" json:"createRule"`
@@ -56,6 +60,7 @@ func NewCollectionUpsert(app core.App, collection *models.Collection) *Collectio
form.Type = form.collection.Type
form.Name = form.collection.Name
form.System = form.collection.System
form.Indexes = list.ToUniqueStringSlice(form.collection.Indexes)
form.ListRule = form.collection.ListRule
form.ViewRule = form.collection.ViewRule
form.CreateRule = form.collection.CreateRule
@@ -154,6 +159,9 @@ func (form *CollectionUpsert) Validate() error {
validation.When(isView, validation.Nil),
validation.By(form.checkRule),
),
validation.Field(&form.Indexes,
validation.When(isView, validation.Length(0, 0)).Else(validation.By(form.checkIndexes)),
),
validation.Field(&form.Options, validation.By(form.checkOptions)),
)
}
@@ -380,6 +388,45 @@ func (form *CollectionUpsert) checkRule(value any) error {
return nil
}
func (form *CollectionUpsert) checkIndexes(value any) error {
v, _ := value.([]string)
for i, rawIndex := range v {
parsed := dbutils.ParseIndex(rawIndex)
if !parsed.IsValid() {
return validation.Errors{
strconv.Itoa(i): validation.NewError(
"validation_invalid_index_expression",
fmt.Sprintf("Invalid CREATE INDEX expression."),
),
}
}
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."),
),
}
}
}
return nil
}
func (form *CollectionUpsert) dryDao() *daos.Dao {
if form.dao.ConcurrentDB() == form.dao.NonconcurrentDB() {
// it is already in a transaction and therefore use the app concurrent db pool
// to prevent "transaction has already been committed or rolled back" error
return daos.New(form.app.Dao().ConcurrentDB())
}
// otherwise use the form noncurrent dao db pool
return daos.New(form.dao.NonconcurrentDB())
}
func (form *CollectionUpsert) checkOptions(value any) error {
v, _ := value.(types.JsonMap)
@@ -467,9 +514,13 @@ func (form *CollectionUpsert) Submit(interceptors ...InterceptorFunc[*models.Col
form.collection.Name = form.Name
}
// view schema is autogenerated on save
// view schema is autogenerated on save and cannot have indexes
if !form.collection.IsView() {
form.collection.Schema = form.Schema
form.collection.Indexes = append(
types.JsonArray{},
list.ToInterfaceSlice(list.ToUniqueStringSlice(form.Indexes))...,
)
}
form.collection.ListRule = form.ListRule

View File

@@ -741,10 +741,16 @@ func (form *RecordUpsert) Submit(interceptors ...InterceptorFunc[*models.Record]
}
// persist the record model
if saveErr := form.dao.SaveRecord(form.record); saveErr != nil {
return fmt.Errorf("failed to save the record: %w", saveErr)
if err := form.dao.SaveRecord(form.record); err != nil {
preparedErr := form.prepareError(err)
if _, ok := preparedErr.(validation.Errors); ok {
return preparedErr
}
return fmt.Errorf("failed to save the record: %w", err)
}
// @todo exec before the record save (it is after because of eventual record id change)?
//
// upload new files (if any)
if err := form.processFilesToUpload(); err != nil {
return fmt.Errorf("failed to process the uploaded files: %w", err)
@@ -849,3 +855,30 @@ func (form *RecordUpsert) deleteFilesByNamesList(filenames []string) ([]string,
return filenames, nil
}
// prepareError parses the provided error and tries to return
// user-friendly validation error(s).
func (form *RecordUpsert) prepareError(err error) error {
msg := strings.ToLower(err.Error())
validationErrs := validation.Errors{}
// check for unique constraint failure
if strings.Contains(msg, "unique constraint failed") {
msg = strings.ReplaceAll(strings.TrimSpace(msg), ",", " ")
c := form.record.Collection()
for _, f := range c.Schema.Fields() {
// blank space to unify multi-columns lookup
if strings.Contains(msg+" ", fmt.Sprintf("%s.%s ", strings.ToLower(c.Name), f.Name)) {
validationErrs[f.Name] = validation.NewError("validation_not_unique", "Value must be unique")
}
}
}
if len(validationErrs) > 0 {
return validationErrs
}
return err
}