1
0
mirror of https://github.com/pocketbase/pocketbase.git synced 2025-02-09 20:24:02 +02:00

added option to insert/move fields at specific position

This commit is contained in:
Gani Georgiev 2024-11-24 12:41:57 +02:00
parent e9ece220d6
commit 1e92b51cf7
6 changed files with 3827 additions and 3639 deletions

View File

@ -4,6 +4,7 @@ import (
"database/sql/driver"
"encoding/json"
"fmt"
"slices"
"strconv"
)
@ -12,7 +13,7 @@ func NewFieldsList(fields ...Field) FieldsList {
l := make(FieldsList, 0, len(fields))
for _, f := range fields {
l.Add(f)
l.add(-1, f)
}
return l
@ -116,7 +117,26 @@ func (l *FieldsList) RemoveByName(fieldName string) {
// (the id value doesn't really matter and it is mostly used as a stable identifier in case of a field rename).
func (l *FieldsList) Add(fields ...Field) {
for _, f := range fields {
l.add(f)
l.add(-1, f)
}
}
// AddAt is the same as Add but insert/move the fields at the specific position.
//
// If pos < 0, then this method acts the same as calling Add.
//
// If pos > FieldsList total items, then the specified fields are inserted/moved at the end of the list.
func (l *FieldsList) AddAt(pos int, fields ...Field) {
total := len(*l)
for i, f := range fields {
if pos < 0 {
l.add(-1, f)
} else if pos > total {
l.add(total+i, f)
} else {
l.add(pos+i, f)
}
}
}
@ -132,13 +152,42 @@ func (l *FieldsList) Add(fields ...Field) {
// l.AddMarshaledJSON([]byte{`{"type":"text", name: "test"}`})
// l.AddMarshaledJSON([]byte{`[{"type":"text", name: "test1"}, {"type":"text", name: "test2"}]`})
func (l *FieldsList) AddMarshaledJSON(rawJSON []byte) error {
extractedFields, err := marshaledJSONtoFieldsList(rawJSON)
if err != nil {
return err
}
l.Add(extractedFields...)
return nil
}
// AddMarshaledJSONAt is the same as AddMarshaledJSON but insert/move the fields at the specific position.
//
// If pos < 0, then this method acts the same as calling AddMarshaledJSON.
//
// If pos > FieldsList total items, then the specified fields are inserted/moved at the end of the list.
func (l *FieldsList) AddMarshaledJSONAt(pos int, rawJSON []byte) error {
extractedFields, err := marshaledJSONtoFieldsList(rawJSON)
if err != nil {
return err
}
l.AddAt(pos, extractedFields...)
return nil
}
func marshaledJSONtoFieldsList(rawJSON []byte) (FieldsList, error) {
extractedFields := FieldsList{}
// nothing to add
if len(rawJSON) == 0 {
return nil // nothing to add
return extractedFields, nil
}
// try to unmarshal first into a new fieds list
// (assuming that rawJSON is array of objects)
extractedFields := FieldsList{}
err := json.Unmarshal(rawJSON, &extractedFields)
if err != nil {
// try again but wrap the rawJSON in []
@ -149,21 +198,25 @@ func (l *FieldsList) AddMarshaledJSON(rawJSON []byte) error {
wrapped = append(wrapped, ']')
err = json.Unmarshal(wrapped, &extractedFields)
if err != nil {
return fmt.Errorf("failed to unmarshal the provided JSON - expects array of objects or just single object: %w", err)
return nil, fmt.Errorf("failed to unmarshal the provided JSON - expects array of objects or just single object: %w", err)
}
}
for _, f := range extractedFields {
l.add(f)
}
return nil
return extractedFields, nil
}
func (l *FieldsList) add(newField Field) {
func (l *FieldsList) add(pos int, newField Field) {
fields := *l
var replaceByName bool
var replaceInPlace bool
if pos < 0 {
replaceInPlace = true
pos = len(fields)
} else if pos > len(fields) {
pos = len(fields)
}
newFieldId := newField.GetId()
@ -182,24 +235,44 @@ func (l *FieldsList) add(newField Field) {
newField.SetId(newFieldId)
}
// replace existing
// try to replace existing
for i, field := range fields {
if replaceByName {
if name := newField.GetName(); name != "" && field.GetName() == name {
// reuse the original id
newField.SetId(field.GetId())
(*l)[i] = newField
return
if replaceInPlace {
(*l)[i] = newField
return
} else {
// remove the current field and insert it later at the specific position
*l = slices.Delete(*l, i, i+1)
if total := len(*l); pos > total {
pos = total
}
break
}
}
} else {
if field.GetId() == newFieldId {
(*l)[i] = newField
return
if replaceInPlace {
(*l)[i] = newField
return
} else {
// remove the current field and insert it later at the specific position
*l = slices.Delete(*l, i, i+1)
if total := len(*l); pos > total {
pos = total
}
break
}
}
}
}
// add new field
*l = append(fields, newField)
// insert the new field
*l = slices.Insert(*l, pos, newField)
}
// String returns the string representation of the current list.
@ -252,7 +325,7 @@ func (l *FieldsList) UnmarshalJSON(data []byte) error {
*l = []Field{} // reset
for _, fwt := range fwts {
l.Add(fwt.Field)
l.add(-1, fwt.Field)
}
return nil

View File

@ -1,7 +1,10 @@
package core_test
import (
"bytes"
"encoding/json"
"slices"
"strconv"
"strings"
"testing"
@ -301,6 +304,96 @@ func TestFieldsListAddMarshaledJSON(t *testing.T) {
}
}
func TestFieldsListAddAt(t *testing.T) {
scenarios := []struct {
position int
expected []string
}{
{-2, []string{"test1", "test2_new", "test3", "test4"}},
{-1, []string{"test1", "test2_new", "test3", "test4"}},
{0, []string{"test2_new", "test4", "test1", "test3"}},
{1, []string{"test1", "test2_new", "test4", "test3"}},
{2, []string{"test1", "test3", "test2_new", "test4"}},
{3, []string{"test1", "test3", "test2_new", "test4"}},
{4, []string{"test1", "test3", "test2_new", "test4"}},
{5, []string{"test1", "test3", "test2_new", "test4"}},
}
for _, s := range scenarios {
t.Run(strconv.Itoa(s.position), func(t *testing.T) {
f1 := &core.TextField{Id: "f1Id", Name: "test1"}
f2 := &core.TextField{Id: "f2Id", Name: "test2"}
f3 := &core.TextField{Id: "f3Id", Name: "test3"}
testFieldsList := core.NewFieldsList(f1, f2, f3)
f2New := &core.EmailField{Id: "f2Id", Name: "test2_new"}
f4 := &core.URLField{Name: "test4"}
testFieldsList.AddAt(s.position, f2New, f4)
rawNames, err := json.Marshal(testFieldsList.FieldNames())
if err != nil {
t.Fatal(err)
}
rawExpected, err := json.Marshal(s.expected)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(rawNames, rawExpected) {
t.Fatalf("Expected fields\n%s\ngot\n%s", rawExpected, rawNames)
}
})
}
}
func TestFieldsListAddMarshaledJSONAt(t *testing.T) {
scenarios := []struct {
position int
expected []string
}{
{-2, []string{"test1", "test2_new", "test3", "test4"}},
{-1, []string{"test1", "test2_new", "test3", "test4"}},
{0, []string{"test2_new", "test4", "test1", "test3"}},
{1, []string{"test1", "test2_new", "test4", "test3"}},
{2, []string{"test1", "test3", "test2_new", "test4"}},
{3, []string{"test1", "test3", "test2_new", "test4"}},
{4, []string{"test1", "test3", "test2_new", "test4"}},
{5, []string{"test1", "test3", "test2_new", "test4"}},
}
for _, s := range scenarios {
t.Run(strconv.Itoa(s.position), func(t *testing.T) {
f1 := &core.TextField{Id: "f1Id", Name: "test1"}
f2 := &core.TextField{Id: "f2Id", Name: "test2"}
f3 := &core.TextField{Id: "f3Id", Name: "test3"}
testFieldsList := core.NewFieldsList(f1, f2, f3)
err := testFieldsList.AddMarshaledJSONAt(s.position, []byte(`[
{"id":"f2Id", "name":"test2_new", "type": "text"},
{"name": "test4", "type": "text"}
]`))
if err != nil {
t.Fatal(err)
}
rawNames, err := json.Marshal(testFieldsList.FieldNames())
if err != nil {
t.Fatal(err)
}
rawExpected, err := json.Marshal(s.expected)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(rawNames, rawExpected) {
t.Fatalf("Expected fields\n%s\ngot\n%s", rawExpected, rawNames)
}
})
}
}
func TestFieldsListStringAndValue(t *testing.T) {
t.Run("empty list", func(t *testing.T) {
testFieldsList := core.NewFieldsList()

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
// Example:
//
// jsvm.MustRegister(app, jsvm.Config{
// WatchHooks: true,
// HooksWatch: true,
// })
package jsvm

View File

@ -921,7 +921,7 @@ migrate((app) => {
collection.fields.removeById("f3_id")
// add field
collection.fields.add(new Field({
collection.fields.addAt(8, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "f4_id",
@ -975,7 +975,7 @@ migrate((app) => {
}, collection)
// add field
collection.fields.add(new Field({
collection.fields.addAt(8, new Field({
"hidden": false,
"id": "f3_id",
"name": "f3_name",
@ -1052,7 +1052,7 @@ func init() {
collection.Fields.RemoveById("f3_id")
// add field
if err := collection.Fields.AddMarshaledJSON([]byte(` + "`" + `{
if err := collection.Fields.AddMarshaledJSONAt(8, []byte(` + "`" + `{
"autogeneratePattern": "",
"hidden": false,
"id": "f4_id",
@ -1115,7 +1115,7 @@ func init() {
}
// add field
if err := collection.Fields.AddMarshaledJSON([]byte(` + "`" + `{
if err := collection.Fields.AddMarshaledJSONAt(8, []byte(` + "`" + `{
"hidden": false,
"id": "f3_id",
"name": "f3_name",

View File

@ -208,7 +208,7 @@ func (p *plugin) jsDiffTemplate(new *core.Collection, old *core.Collection) (str
upParts = append(upParts, fmt.Sprintf("%s.fields.removeById(%q)\n", varName, oldField.GetId()))
downParts = append(downParts, "// add field")
downParts = append(downParts, fmt.Sprintf("%s.fields.add(new Field(%s))\n", varName, rawOldField))
downParts = append(downParts, fmt.Sprintf("%s.fields.addAt(%d, new Field(%s))\n", varName, i, rawOldField))
}
// created fields
@ -223,13 +223,14 @@ func (p *plugin) jsDiffTemplate(new *core.Collection, old *core.Collection) (str
}
upParts = append(upParts, "// add field")
upParts = append(upParts, fmt.Sprintf("%s.fields.add(new Field(%s))\n", varName, rawNewField))
upParts = append(upParts, fmt.Sprintf("%s.fields.addAt(%d, new Field(%s))\n", varName, i, rawNewField))
downParts = append(downParts, "// remove field")
downParts = append(downParts, fmt.Sprintf("%s.fields.removeById(%q)\n", varName, newField.GetId()))
}
// modified fields
// (note currently ignoring order-only changes as it comes with too many edge-cases)
for i, newField := range new.Fields {
var rawNewField, rawOldField []byte
@ -546,7 +547,7 @@ func (p *plugin) goDiffTemplate(new *core.Collection, old *core.Collection) (str
upParts = append(upParts, fmt.Sprintf("%s.Fields.RemoveById(%q)\n", varName, oldField.GetId()))
downParts = append(downParts, "// add field")
downParts = append(downParts, goErrIf(fmt.Sprintf("%s.Fields.AddMarshaledJSON([]byte(`%s`))", varName, escapeBacktick(string(rawOldField)))))
downParts = append(downParts, goErrIf(fmt.Sprintf("%s.Fields.AddMarshaledJSONAt(%d, []byte(`%s`))", varName, i, escapeBacktick(string(rawOldField)))))
}
// created fields
@ -561,13 +562,14 @@ func (p *plugin) goDiffTemplate(new *core.Collection, old *core.Collection) (str
}
upParts = append(upParts, "// add field")
upParts = append(upParts, goErrIf(fmt.Sprintf("%s.Fields.AddMarshaledJSON([]byte(`%s`))", varName, escapeBacktick(string(rawNewField)))))
upParts = append(upParts, goErrIf(fmt.Sprintf("%s.Fields.AddMarshaledJSONAt(%d, []byte(`%s`))", varName, i, escapeBacktick(string(rawNewField)))))
downParts = append(downParts, "// remove field")
downParts = append(downParts, fmt.Sprintf("%s.Fields.RemoveById(%q)\n", varName, newField.GetId()))
}
// modified fields
// (note currently ignoring order-only changes as it comes with too many edge-cases)
for i, newField := range new.Fields {
var rawNewField, rawOldField []byte