Adding upstream version 0.28.1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
88f1d47ab6
commit
e28c88ef14
933 changed files with 194711 additions and 0 deletions
200
core/collection_import.go
Normal file
200
core/collection_import.go
Normal file
|
@ -0,0 +1,200 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
validation "github.com/go-ozzo/ozzo-validation/v4"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
// ImportCollectionsByMarshaledJSON is the same as [ImportCollections]
|
||||
// but accept marshaled json array as import data (usually used for the autogenerated snapshots).
|
||||
func (app *BaseApp) ImportCollectionsByMarshaledJSON(rawSliceOfMaps []byte, deleteMissing bool) error {
|
||||
data := []map[string]any{}
|
||||
|
||||
err := json.Unmarshal(rawSliceOfMaps, &data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.ImportCollections(data, deleteMissing)
|
||||
}
|
||||
|
||||
// ImportCollections imports the provided collections data in a single transaction.
|
||||
//
|
||||
// For existing matching collections, the imported data is unmarshaled on top of the existing model.
|
||||
//
|
||||
// NB! If deleteMissing is true, ALL NON-SYSTEM COLLECTIONS AND SCHEMA FIELDS,
|
||||
// that are not present in the imported configuration, WILL BE DELETED
|
||||
// (this includes their related records data).
|
||||
func (app *BaseApp) ImportCollections(toImport []map[string]any, deleteMissing bool) error {
|
||||
if len(toImport) == 0 {
|
||||
// prevent accidentally deleting all collections
|
||||
return errors.New("no collections to import")
|
||||
}
|
||||
|
||||
importedCollections := make([]*Collection, len(toImport))
|
||||
mappedImported := make(map[string]*Collection, len(toImport))
|
||||
|
||||
// normalize imported collections data to ensure that all
|
||||
// collection fields are present and properly initialized
|
||||
for i, data := range toImport {
|
||||
var imported *Collection
|
||||
|
||||
identifier := cast.ToString(data["id"])
|
||||
if identifier == "" {
|
||||
identifier = cast.ToString(data["name"])
|
||||
}
|
||||
|
||||
existing, err := app.FindCollectionByNameOrId(identifier)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return err
|
||||
}
|
||||
|
||||
if existing != nil {
|
||||
// refetch for deep copy
|
||||
imported, err = app.FindCollectionByNameOrId(existing.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ensure that the fields will be cleared
|
||||
if data["fields"] == nil && deleteMissing {
|
||||
data["fields"] = []map[string]any{}
|
||||
}
|
||||
|
||||
rawData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// load the imported data
|
||||
err = json.Unmarshal(rawData, imported)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// extend with the existing fields if necessary
|
||||
for _, f := range existing.Fields {
|
||||
if !f.GetSystem() && deleteMissing {
|
||||
continue
|
||||
}
|
||||
if imported.Fields.GetById(f.GetId()) == nil {
|
||||
// replace with the existing id to prevent accidental column deletion
|
||||
// since otherwise the imported field will be treated as a new one
|
||||
found := imported.Fields.GetByName(f.GetName())
|
||||
if found != nil && found.Type() == f.Type() {
|
||||
found.SetId(f.GetId())
|
||||
}
|
||||
imported.Fields.Add(f)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
imported = &Collection{}
|
||||
|
||||
rawData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// load the imported data
|
||||
err = json.Unmarshal(rawData, imported)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
imported.IntegrityChecks(false)
|
||||
|
||||
importedCollections[i] = imported
|
||||
mappedImported[imported.Id] = imported
|
||||
}
|
||||
|
||||
// reorder views last since the view query could depend on some of the other collections
|
||||
slices.SortStableFunc(importedCollections, func(a, b *Collection) int {
|
||||
cmpA := -1
|
||||
if a.IsView() {
|
||||
cmpA = 1
|
||||
}
|
||||
|
||||
cmpB := -1
|
||||
if b.IsView() {
|
||||
cmpB = 1
|
||||
}
|
||||
|
||||
res := cmp.Compare(cmpA, cmpB)
|
||||
if res == 0 {
|
||||
res = a.Created.Compare(b.Created)
|
||||
if res == 0 {
|
||||
res = a.Updated.Compare(b.Updated)
|
||||
}
|
||||
}
|
||||
return res
|
||||
})
|
||||
|
||||
return app.RunInTransaction(func(txApp App) error {
|
||||
existingCollections := []*Collection{}
|
||||
if err := txApp.CollectionQuery().OrderBy("updated ASC").All(&existingCollections); err != nil {
|
||||
return err
|
||||
}
|
||||
mappedExisting := make(map[string]*Collection, len(existingCollections))
|
||||
for _, existing := range existingCollections {
|
||||
existing.IntegrityChecks(false)
|
||||
mappedExisting[existing.Id] = existing
|
||||
}
|
||||
|
||||
// delete old collections not available in the new configuration
|
||||
// (before saving the imports in case a deleted collection name is being reused)
|
||||
if deleteMissing {
|
||||
for _, existing := range existingCollections {
|
||||
if mappedImported[existing.Id] != nil || existing.System {
|
||||
continue // exist or system
|
||||
}
|
||||
|
||||
// delete collection
|
||||
if err := txApp.Delete(existing); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// upsert imported collections
|
||||
for _, imported := range importedCollections {
|
||||
if err := txApp.SaveNoValidate(imported); err != nil {
|
||||
return fmt.Errorf("failed to save collection %q: %w", imported.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// run validations
|
||||
for _, imported := range importedCollections {
|
||||
original := mappedExisting[imported.Id]
|
||||
if original == nil {
|
||||
original = imported
|
||||
}
|
||||
|
||||
validator := newCollectionValidator(
|
||||
context.Background(),
|
||||
txApp,
|
||||
imported,
|
||||
original,
|
||||
)
|
||||
if err := validator.run(); err != nil {
|
||||
// serialize the validation error(s)
|
||||
serializedErr, _ := json.MarshalIndent(err, "", " ")
|
||||
|
||||
return validation.Errors{"collections": validation.NewError(
|
||||
"validation_collections_import_failure",
|
||||
fmt.Sprintf("Data validations failed for collection %q (%s):\n%s", imported.Name, imported.Id, serializedErr),
|
||||
)}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue