updated CollectionsImport and CollectionUpsert forms
This commit is contained in:
		
							parent
							
								
									4e58e7ad6a
								
							
						
					
					
						commit
						5fb45a1864
					
				| 
						 | 
					@ -183,6 +183,9 @@ func (dao *Dao) create(m models.Model) error {
 | 
				
			||||||
		m.RefreshId()
 | 
							m.RefreshId()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// mark the model as "new" since the model now always has an ID
 | 
				
			||||||
 | 
						m.MarkAsNew()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if m.GetCreated().IsZero() {
 | 
						if m.GetCreated().IsZero() {
 | 
				
			||||||
		m.RefreshCreated()
 | 
							m.RefreshCreated()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -213,7 +216,7 @@ func (dao *Dao) create(m models.Model) error {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// clears the internal isNewFlag
 | 
						// clears the "new" model flag
 | 
				
			||||||
	m.UnmarkAsNew()
 | 
						m.UnmarkAsNew()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if dao.AfterCreateFunc != nil {
 | 
						if dao.AfterCreateFunc != nil {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,7 @@ import (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	validation "github.com/go-ozzo/ozzo-validation/v4"
 | 
						validation "github.com/go-ozzo/ozzo-validation/v4"
 | 
				
			||||||
	"github.com/pocketbase/pocketbase/core"
 | 
						"github.com/pocketbase/pocketbase/core"
 | 
				
			||||||
 | 
						"github.com/pocketbase/pocketbase/daos"
 | 
				
			||||||
	"github.com/pocketbase/pocketbase/models"
 | 
						"github.com/pocketbase/pocketbase/models"
 | 
				
			||||||
	"github.com/pocketbase/pocketbase/models/schema"
 | 
						"github.com/pocketbase/pocketbase/models/schema"
 | 
				
			||||||
	"github.com/pocketbase/pocketbase/resolvers"
 | 
						"github.com/pocketbase/pocketbase/resolvers"
 | 
				
			||||||
| 
						 | 
					@ -14,12 +15,12 @@ import (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var collectionNameRegex = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_]*$`)
 | 
					var collectionNameRegex = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_]*$`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CollectionUpsert defines a collection upsert (create/update) form.
 | 
					// CollectionUpsert specifies a [models.Collection] upsert (create/update) form.
 | 
				
			||||||
type CollectionUpsert struct {
 | 
					type CollectionUpsert struct {
 | 
				
			||||||
	app        core.App
 | 
						config     CollectionUpsertConfig
 | 
				
			||||||
	collection *models.Collection
 | 
						collection *models.Collection
 | 
				
			||||||
	isCreate   bool
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Id         string        `form:"id" json:"id"`
 | 
				
			||||||
	Name       string        `form:"name" json:"name"`
 | 
						Name       string        `form:"name" json:"name"`
 | 
				
			||||||
	System     bool          `form:"system" json:"system"`
 | 
						System     bool          `form:"system" json:"system"`
 | 
				
			||||||
	Schema     schema.Schema `form:"schema" json:"schema"`
 | 
						Schema     schema.Schema `form:"schema" json:"schema"`
 | 
				
			||||||
| 
						 | 
					@ -30,25 +31,48 @@ type CollectionUpsert struct {
 | 
				
			||||||
	DeleteRule *string       `form:"deleteRule" json:"deleteRule"`
 | 
						DeleteRule *string       `form:"deleteRule" json:"deleteRule"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewCollectionUpsert creates new collection upsert form for the provided Collection model
 | 
					// CollectionUpsertConfig is the [CollectionUpsert] factory initializer config.
 | 
				
			||||||
// (pass an empty Collection model instance (`&models.Collection{}`) for create).
 | 
					//
 | 
				
			||||||
 | 
					// NB! Dao is a required struct member.
 | 
				
			||||||
 | 
					type CollectionUpsertConfig struct {
 | 
				
			||||||
 | 
						Dao *daos.Dao
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewCollectionUpsert creates a new [CollectionUpsert] form with initializer
 | 
				
			||||||
 | 
					// config created from the provided [core.App] and [models.Collection] instances.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// This factory method is used primarily for convenience (and backward compatibility).
 | 
				
			||||||
 | 
					// If you want to submit the form as part of another transaction, use
 | 
				
			||||||
 | 
					// [NewCollectionUpsertWithConfig] with Dao configured to your txDao.
 | 
				
			||||||
func NewCollectionUpsert(app core.App, collection *models.Collection) *CollectionUpsert {
 | 
					func NewCollectionUpsert(app core.App, collection *models.Collection) *CollectionUpsert {
 | 
				
			||||||
	form := &CollectionUpsert{
 | 
						form := NewCollectionUpsertWithConfig(CollectionUpsertConfig{
 | 
				
			||||||
		app:        app,
 | 
							Dao: app.Dao(),
 | 
				
			||||||
		collection: collection,
 | 
						}, collection)
 | 
				
			||||||
		isCreate:   collection.IsNew(),
 | 
					
 | 
				
			||||||
 | 
						return form
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewCollectionUpsertWithConfig creates a new [CollectionUpsert] form
 | 
				
			||||||
 | 
					// with the provided config and [models.Collection] instance or panics on invalid configuration
 | 
				
			||||||
 | 
					// (for create you could pass a pointer to an empty Collection instance - `&models.Collection{}`).
 | 
				
			||||||
 | 
					func NewCollectionUpsertWithConfig(config CollectionUpsertConfig, collection *models.Collection) *CollectionUpsert {
 | 
				
			||||||
 | 
						form := &CollectionUpsert{config: config}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if form.config.Dao == nil || collection == nil {
 | 
				
			||||||
 | 
							panic("Invalid initializer config or nil upsert model.")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// load defaults
 | 
						// load defaults
 | 
				
			||||||
	form.Name = collection.Name
 | 
						form.Id = form.collection.Id
 | 
				
			||||||
	form.System = collection.System
 | 
						form.Name = form.collection.Name
 | 
				
			||||||
	form.ListRule = collection.ListRule
 | 
						form.System = form.collection.System
 | 
				
			||||||
	form.ViewRule = collection.ViewRule
 | 
						form.ListRule = form.collection.ListRule
 | 
				
			||||||
	form.CreateRule = collection.CreateRule
 | 
						form.ViewRule = form.collection.ViewRule
 | 
				
			||||||
	form.UpdateRule = collection.UpdateRule
 | 
						form.CreateRule = form.collection.CreateRule
 | 
				
			||||||
	form.DeleteRule = collection.DeleteRule
 | 
						form.UpdateRule = form.collection.UpdateRule
 | 
				
			||||||
 | 
						form.DeleteRule = form.collection.DeleteRule
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	clone, _ := collection.Schema.Clone()
 | 
						clone, _ := form.collection.Schema.Clone()
 | 
				
			||||||
	if clone != nil {
 | 
						if clone != nil {
 | 
				
			||||||
		form.Schema = *clone
 | 
							form.Schema = *clone
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
| 
						 | 
					@ -61,6 +85,10 @@ func NewCollectionUpsert(app core.App, collection *models.Collection) *Collectio
 | 
				
			||||||
// Validate makes the form validatable by implementing [validation.Validatable] interface.
 | 
					// Validate makes the form validatable by implementing [validation.Validatable] interface.
 | 
				
			||||||
func (form *CollectionUpsert) Validate() error {
 | 
					func (form *CollectionUpsert) Validate() error {
 | 
				
			||||||
	return validation.ValidateStruct(form,
 | 
						return validation.ValidateStruct(form,
 | 
				
			||||||
 | 
							validation.Field(
 | 
				
			||||||
 | 
								&form.Id,
 | 
				
			||||||
 | 
								validation.Length(models.DefaultIdLength, models.DefaultIdLength),
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
		validation.Field(
 | 
							validation.Field(
 | 
				
			||||||
			&form.System,
 | 
								&form.System,
 | 
				
			||||||
			validation.By(form.ensureNoSystemFlagChange),
 | 
								validation.By(form.ensureNoSystemFlagChange),
 | 
				
			||||||
| 
						 | 
					@ -90,11 +118,11 @@ func (form *CollectionUpsert) Validate() error {
 | 
				
			||||||
func (form *CollectionUpsert) checkUniqueName(value any) error {
 | 
					func (form *CollectionUpsert) checkUniqueName(value any) error {
 | 
				
			||||||
	v, _ := value.(string)
 | 
						v, _ := value.(string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !form.app.Dao().IsCollectionNameUnique(v, form.collection.Id) {
 | 
						if !form.config.Dao.IsCollectionNameUnique(v, form.collection.Id) {
 | 
				
			||||||
		return validation.NewError("validation_collection_name_exists", "Collection name must be unique (case insensitive).")
 | 
							return validation.NewError("validation_collection_name_exists", "Collection name must be unique (case insensitive).")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (form.isCreate || !strings.EqualFold(v, form.collection.Name)) && form.app.Dao().HasTable(v) {
 | 
						if (form.collection.IsNew() || !strings.EqualFold(v, form.collection.Name)) && form.config.Dao.HasTable(v) {
 | 
				
			||||||
		return validation.NewError("validation_collection_name_table_exists", "The collection name must be also unique table name.")
 | 
							return validation.NewError("validation_collection_name_table_exists", "The collection name must be also unique table name.")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -104,7 +132,7 @@ func (form *CollectionUpsert) checkUniqueName(value any) error {
 | 
				
			||||||
func (form *CollectionUpsert) ensureNoSystemNameChange(value any) error {
 | 
					func (form *CollectionUpsert) ensureNoSystemNameChange(value any) error {
 | 
				
			||||||
	v, _ := value.(string)
 | 
						v, _ := value.(string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if form.isCreate || !form.collection.System || v == form.collection.Name {
 | 
						if form.collection.IsNew() || !form.collection.System || v == form.collection.Name {
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -114,7 +142,7 @@ func (form *CollectionUpsert) ensureNoSystemNameChange(value any) error {
 | 
				
			||||||
func (form *CollectionUpsert) ensureNoSystemFlagChange(value any) error {
 | 
					func (form *CollectionUpsert) ensureNoSystemFlagChange(value any) error {
 | 
				
			||||||
	v, _ := value.(bool)
 | 
						v, _ := value.(bool)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if form.isCreate || v == form.collection.System {
 | 
						if form.collection.IsNew() || v == form.collection.System {
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -161,7 +189,7 @@ func (form *CollectionUpsert) checkRule(value any) error {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	dummy := &models.Collection{Schema: form.Schema}
 | 
						dummy := &models.Collection{Schema: form.Schema}
 | 
				
			||||||
	r := resolvers.NewRecordFieldResolver(form.app.Dao(), dummy, nil)
 | 
						r := resolvers.NewRecordFieldResolver(form.config.Dao, dummy, nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err := search.FilterData(*v).BuildExpr(r)
 | 
						_, err := search.FilterData(*v).BuildExpr(r)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
| 
						 | 
					@ -182,13 +210,19 @@ func (form *CollectionUpsert) Submit(interceptors ...InterceptorFunc) error {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// system flag can be set only for create
 | 
						if form.collection.IsNew() {
 | 
				
			||||||
	if form.isCreate {
 | 
							// system flag can be set only on create
 | 
				
			||||||
		form.collection.System = form.System
 | 
							form.collection.System = form.System
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// custom insertion id can be set only on create
 | 
				
			||||||
 | 
							if form.Id != "" {
 | 
				
			||||||
 | 
								form.collection.MarkAsNew()
 | 
				
			||||||
 | 
								form.collection.SetId(form.Id)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// system collections cannot be renamed
 | 
						// system collections cannot be renamed
 | 
				
			||||||
	if form.isCreate || !form.collection.System {
 | 
						if form.collection.IsNew() || !form.collection.System {
 | 
				
			||||||
		form.collection.Name = form.Name
 | 
							form.collection.Name = form.Name
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -200,6 +234,6 @@ func (form *CollectionUpsert) Submit(interceptors ...InterceptorFunc) error {
 | 
				
			||||||
	form.collection.DeleteRule = form.DeleteRule
 | 
						form.collection.DeleteRule = form.DeleteRule
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return runInterceptors(func() error {
 | 
						return runInterceptors(func() error {
 | 
				
			||||||
		return form.app.Dao().SaveCollection(form.collection)
 | 
							return form.config.Dao.SaveCollection(form.collection)
 | 
				
			||||||
	}, interceptors...)
 | 
						}, interceptors...)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,24 +11,48 @@ import (
 | 
				
			||||||
	"github.com/pocketbase/pocketbase/models"
 | 
						"github.com/pocketbase/pocketbase/models"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CollectionsImport defines a bulk collections import form.
 | 
					// CollectionsImport specifies a form model to bulk import
 | 
				
			||||||
 | 
					// (create, replace and delete) collections from a user provided list.
 | 
				
			||||||
type CollectionsImport struct {
 | 
					type CollectionsImport struct {
 | 
				
			||||||
	app core.App
 | 
						config CollectionsImportConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Collections  []*models.Collection `form:"collections" json:"collections"`
 | 
						Collections  []*models.Collection `form:"collections" json:"collections"`
 | 
				
			||||||
	DeleteOthers bool                 `form:"deleteOthers" json:"deleteOthers"`
 | 
						DeleteOthers bool                 `form:"deleteOthers" json:"deleteOthers"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewCollectionsImport bulk imports (create, replace and delete)
 | 
					// CollectionsImportConfig is the [CollectionsImport] factory initializer config.
 | 
				
			||||||
// a user provided list with collections data.
 | 
					//
 | 
				
			||||||
func NewCollectionsImport(app core.App) *CollectionsImport {
 | 
					// NB! Dao is a required struct member.
 | 
				
			||||||
	form := &CollectionsImport{
 | 
					type CollectionsImportConfig struct {
 | 
				
			||||||
		app: app,
 | 
						Dao     *daos.Dao
 | 
				
			||||||
 | 
						IsDebug bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewCollectionsImportWithConfig creates a new [CollectionsImport]
 | 
				
			||||||
 | 
					// form with the provided config or panics on invalid configuration.
 | 
				
			||||||
 | 
					func NewCollectionsImportWithConfig(config CollectionsImportConfig) *CollectionsImport {
 | 
				
			||||||
 | 
						form := &CollectionsImport{config: config}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if form.config.Dao == nil {
 | 
				
			||||||
 | 
							panic("Invalid initializer config.")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return form
 | 
						return form
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewCollectionsImport creates a new [CollectionsImport] form with
 | 
				
			||||||
 | 
					// initializer config created from the provided [core.App] instance.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// This factory method is used primarily for convenience (and backward compatibility).
 | 
				
			||||||
 | 
					// If you want to submit the form as part of another transaction, use
 | 
				
			||||||
 | 
					// [NewCollectionsImportWithConfig] with Dao configured to your txDao.
 | 
				
			||||||
 | 
					func NewCollectionsImport(app core.App) *CollectionsImport {
 | 
				
			||||||
 | 
						return NewCollectionsImportWithConfig(CollectionsImportConfig{
 | 
				
			||||||
 | 
							Dao:     app.Dao(),
 | 
				
			||||||
 | 
							IsDebug: app.IsDebug(),
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Validate makes the form validatable by implementing [validation.Validatable] interface.
 | 
					// Validate makes the form validatable by implementing [validation.Validatable] interface.
 | 
				
			||||||
func (form *CollectionsImport) Validate() error {
 | 
					func (form *CollectionsImport) Validate() error {
 | 
				
			||||||
	return validation.ValidateStruct(form,
 | 
						return validation.ValidateStruct(form,
 | 
				
			||||||
| 
						 | 
					@ -49,12 +73,12 @@ func (form *CollectionsImport) Submit() error {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// @todo validate id length in the form
 | 
						return form.config.Dao.RunInTransaction(func(txDao *daos.Dao) error {
 | 
				
			||||||
	return form.app.Dao().RunInTransaction(func(txDao *daos.Dao) error {
 | 
					 | 
				
			||||||
		oldCollections := []*models.Collection{}
 | 
							oldCollections := []*models.Collection{}
 | 
				
			||||||
		if err := txDao.CollectionQuery().All(&oldCollections); err != nil {
 | 
							if err := txDao.CollectionQuery().All(&oldCollections); err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		mappedOldCollections := make(map[string]*models.Collection, len(oldCollections))
 | 
							mappedOldCollections := make(map[string]*models.Collection, len(oldCollections))
 | 
				
			||||||
		for _, old := range oldCollections {
 | 
							for _, old := range oldCollections {
 | 
				
			||||||
			mappedOldCollections[old.GetId()] = old
 | 
								mappedOldCollections[old.GetId()] = old
 | 
				
			||||||
| 
						 | 
					@ -71,7 +95,7 @@ func (form *CollectionsImport) Submit() error {
 | 
				
			||||||
				if mappedFormCollections[old.GetId()] == nil {
 | 
									if mappedFormCollections[old.GetId()] == nil {
 | 
				
			||||||
					// delete the collection
 | 
										// delete the collection
 | 
				
			||||||
					if err := txDao.DeleteCollection(old); err != nil {
 | 
										if err := txDao.DeleteCollection(old); err != nil {
 | 
				
			||||||
						if form.app.IsDebug() {
 | 
											if form.config.IsDebug {
 | 
				
			||||||
							log.Println("[CollectionsImport] DeleteOthers failure", old.Name, err)
 | 
												log.Println("[CollectionsImport] DeleteOthers failure", old.Name, err)
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
						return validation.Errors{"collections": validation.NewError(
 | 
											return validation.Errors{"collections": validation.NewError(
 | 
				
			||||||
| 
						 | 
					@ -79,6 +103,8 @@ func (form *CollectionsImport) Submit() error {
 | 
				
			||||||
							fmt.Sprintf("Failed to delete collection %q (%s). Make sure that the collection is not system or referenced by other collections.", old.Name, old.Id),
 | 
												fmt.Sprintf("Failed to delete collection %q (%s). Make sure that the collection is not system or referenced by other collections.", old.Name, old.Id),
 | 
				
			||||||
						)}
 | 
											)}
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										delete(mappedOldCollections, old.GetId())
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -91,7 +117,7 @@ func (form *CollectionsImport) Submit() error {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err := txDao.Save(collection); err != nil {
 | 
								if err := txDao.Save(collection); err != nil {
 | 
				
			||||||
				if form.app.IsDebug() {
 | 
									if form.config.IsDebug {
 | 
				
			||||||
					log.Println("[CollectionsImport] Save failure", collection.Name, err)
 | 
										log.Println("[CollectionsImport] Save failure", collection.Name, err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				return validation.Errors{"collections": validation.NewError(
 | 
									return validation.Errors{"collections": validation.NewError(
 | 
				
			||||||
| 
						 | 
					@ -112,10 +138,13 @@ func (form *CollectionsImport) Submit() error {
 | 
				
			||||||
		for _, collection := range refreshedCollections {
 | 
							for _, collection := range refreshedCollections {
 | 
				
			||||||
			upsertModel := mappedOldCollections[collection.GetId()]
 | 
								upsertModel := mappedOldCollections[collection.GetId()]
 | 
				
			||||||
			if upsertModel == nil {
 | 
								if upsertModel == nil {
 | 
				
			||||||
				upsertModel = &models.Collection{}
 | 
									upsertModel = collection
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			upsertForm := NewCollectionUpsert(form.app, upsertModel)
 | 
								upsertForm := NewCollectionUpsertWithConfig(CollectionUpsertConfig{
 | 
				
			||||||
 | 
									Dao: txDao,
 | 
				
			||||||
 | 
								}, upsertModel)
 | 
				
			||||||
			// load form fields with the refreshed collection state
 | 
								// load form fields with the refreshed collection state
 | 
				
			||||||
 | 
								upsertForm.Id = collection.Id
 | 
				
			||||||
			upsertForm.Name = collection.Name
 | 
								upsertForm.Name = collection.Name
 | 
				
			||||||
			upsertForm.System = collection.System
 | 
								upsertForm.System = collection.System
 | 
				
			||||||
			upsertForm.ListRule = collection.ListRule
 | 
								upsertForm.ListRule = collection.ListRule
 | 
				
			||||||
| 
						 | 
					@ -125,10 +154,6 @@ func (form *CollectionsImport) Submit() error {
 | 
				
			||||||
			upsertForm.DeleteRule = collection.DeleteRule
 | 
								upsertForm.DeleteRule = collection.DeleteRule
 | 
				
			||||||
			upsertForm.Schema = collection.Schema
 | 
								upsertForm.Schema = collection.Schema
 | 
				
			||||||
			if err := upsertForm.Validate(); err != nil {
 | 
								if err := upsertForm.Validate(); err != nil {
 | 
				
			||||||
				if form.app.IsDebug() {
 | 
					 | 
				
			||||||
					log.Println("[CollectionsImport] Validate failure", collection.Name, err)
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				// serialize the validation error(s)
 | 
									// serialize the validation error(s)
 | 
				
			||||||
				serializedErr, _ := json.Marshal(err)
 | 
									serializedErr, _ := json.Marshal(err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -143,7 +168,7 @@ func (form *CollectionsImport) Submit() error {
 | 
				
			||||||
		for _, collection := range form.Collections {
 | 
							for _, collection := range form.Collections {
 | 
				
			||||||
			oldCollection := mappedOldCollections[collection.GetId()]
 | 
								oldCollection := mappedOldCollections[collection.GetId()]
 | 
				
			||||||
			if err := txDao.SyncRecordTableSchema(collection, oldCollection); err != nil {
 | 
								if err := txDao.SyncRecordTableSchema(collection, oldCollection); err != nil {
 | 
				
			||||||
				if form.app.IsDebug() {
 | 
									if form.config.IsDebug {
 | 
				
			||||||
					log.Println("[CollectionsImport] Records table sync failure", collection.Name, err)
 | 
										log.Println("[CollectionsImport] Records table sync failure", collection.Name, err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				return validation.Errors{"collections": validation.NewError(
 | 
									return validation.Errors{"collections": validation.NewError(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,9 @@ import (
 | 
				
			||||||
	"golang.org/x/crypto/bcrypt"
 | 
						"golang.org/x/crypto/bcrypt"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DefaultIdLength is the default length of the generated model id.
 | 
				
			||||||
 | 
					const DefaultIdLength = 15
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ColumnValueMapper defines an interface for custom db model data serialization.
 | 
					// ColumnValueMapper defines an interface for custom db model data serialization.
 | 
				
			||||||
type ColumnValueMapper interface {
 | 
					type ColumnValueMapper interface {
 | 
				
			||||||
	// ColumnValueMap returns the data to be used when persisting the model.
 | 
						// ColumnValueMap returns the data to be used when persisting the model.
 | 
				
			||||||
| 
						 | 
					@ -96,7 +99,7 @@ func (m *BaseModel) GetUpdated() types.DateTime {
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// The generated id is a cryptographically random 15 characters length string.
 | 
					// The generated id is a cryptographically random 15 characters length string.
 | 
				
			||||||
func (m *BaseModel) RefreshId() {
 | 
					func (m *BaseModel) RefreshId() {
 | 
				
			||||||
	m.Id = security.RandomString(15)
 | 
						m.Id = security.RandomString(DefaultIdLength)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// RefreshCreated updates the model's Created field with the current datetime.
 | 
					// RefreshCreated updates the model's Created field with the current datetime.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,6 @@
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@codemirror/autocomplete": "^6.0.0",
 | 
					    "@codemirror/autocomplete": "^6.0.0",
 | 
				
			||||||
    "@codemirror/commands": "^6.0.0",
 | 
					    "@codemirror/commands": "^6.0.0",
 | 
				
			||||||
    "@codemirror/lang-javascript": "^6.0.2",
 | 
					 | 
				
			||||||
    "@codemirror/language": "^6.0.0",
 | 
					    "@codemirror/language": "^6.0.0",
 | 
				
			||||||
    "@codemirror/legacy-modes": "^6.0.0",
 | 
					    "@codemirror/legacy-modes": "^6.0.0",
 | 
				
			||||||
    "@codemirror/search": "^6.0.0",
 | 
					    "@codemirror/search": "^6.0.0",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -101,7 +101,8 @@
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        confirm(`Do you really want to delete the selected admin?`, () => {
 | 
					        confirm(`Do you really want to delete the selected admin?`, () => {
 | 
				
			||||||
            return ApiClient.admins.delete(admin.id)
 | 
					            return ApiClient.admins
 | 
				
			||||||
 | 
					                .delete(admin.id)
 | 
				
			||||||
                .then(() => {
 | 
					                .then(() => {
 | 
				
			||||||
                    confirmClose = false;
 | 
					                    confirmClose = false;
 | 
				
			||||||
                    hide();
 | 
					                    hide();
 | 
				
			||||||
| 
						 | 
					@ -190,7 +191,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        {#if admin.isNew || changePasswordToggle}
 | 
					        {#if admin.isNew || changePasswordToggle}
 | 
				
			||||||
            <div class="col-12">
 | 
					            <div class="col-12">
 | 
				
			||||||
                <div class="grid" transition:slide={{ duration: 150 }}>
 | 
					                <div class="grid" transition:slide|local={{ duration: 150 }}>
 | 
				
			||||||
                    <div class="col-sm-6">
 | 
					                    <div class="col-sm-6">
 | 
				
			||||||
                        <Field class="form-field required" name="password" let:uniqueId>
 | 
					                        <Field class="form-field required" name="password" let:uniqueId>
 | 
				
			||||||
                            <label for={uniqueId}>
 | 
					                            <label for={uniqueId}>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,7 +36,8 @@
 | 
				
			||||||
    export async function load() {
 | 
					    export async function load() {
 | 
				
			||||||
        isLoading = true;
 | 
					        isLoading = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return ApiClient.logs.getRequestsStats({
 | 
					        return ApiClient.logs
 | 
				
			||||||
 | 
					            .getRequestsStats({
 | 
				
			||||||
                filter: [presets, filter].filter(Boolean).join("&&"),
 | 
					                filter: [presets, filter].filter(Boolean).join("&&"),
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .then((result) => {
 | 
					            .then((result) => {
 | 
				
			||||||
| 
						 | 
					@ -147,7 +148,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div class="chart-wrapper" class:loading={isLoading}>
 | 
					<div class="chart-wrapper" class:loading={isLoading}>
 | 
				
			||||||
    {#if isLoading}
 | 
					    {#if isLoading}
 | 
				
			||||||
        <div class="chart-loader loader" transition:scale={{ duration: 150 }} />
 | 
					        <div class="chart-loader loader" transition:scale|local={{ duration: 150 }} />
 | 
				
			||||||
    {/if}
 | 
					    {/if}
 | 
				
			||||||
    <canvas bind:this={chartCanvas} class="chart-canvas" style="height: 250px; width: 100%;" />
 | 
					    <canvas bind:this={chartCanvas} class="chart-canvas" style="height: 250px; width: 100%;" />
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
    import { createEventDispatcher } from "svelte";
 | 
					    import { createEventDispatcher } from "svelte";
 | 
				
			||||||
    import ApiClient from "@/utils/ApiClient";
 | 
					    import ApiClient from "@/utils/ApiClient";
 | 
				
			||||||
 | 
					    import CommonHelper from "@/utils/CommonHelper";
 | 
				
			||||||
    import OverlayPanel from "@/components/base/OverlayPanel.svelte";
 | 
					    import OverlayPanel from "@/components/base/OverlayPanel.svelte";
 | 
				
			||||||
    import { addSuccessToast } from "@/stores/toasts";
 | 
					    import { addSuccessToast } from "@/stores/toasts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,8 +10,13 @@
 | 
				
			||||||
    let panel;
 | 
					    let panel;
 | 
				
			||||||
    let oldCollections = [];
 | 
					    let oldCollections = [];
 | 
				
			||||||
    let newCollections = [];
 | 
					    let newCollections = [];
 | 
				
			||||||
 | 
					    let changes = [];
 | 
				
			||||||
    let isImporting = false;
 | 
					    let isImporting = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $: if (Array.isArray(oldCollections) && Array.isArray(newCollections)) {
 | 
				
			||||||
 | 
					        loadChanges();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    export function show(a, b) {
 | 
					    export function show(a, b) {
 | 
				
			||||||
        oldCollections = a;
 | 
					        oldCollections = a;
 | 
				
			||||||
        newCollections = b;
 | 
					        newCollections = b;
 | 
				
			||||||
| 
						 | 
					@ -22,6 +28,32 @@
 | 
				
			||||||
        return panel?.hide();
 | 
					        return panel?.hide();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function loadChanges() {
 | 
				
			||||||
 | 
					        changes = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // add deleted and modified collections
 | 
				
			||||||
 | 
					        for (const oldCollection of oldCollections) {
 | 
				
			||||||
 | 
					            const newCollection = CommonHelper.findByKey(newCollections, "id", oldCollection.id) || null;
 | 
				
			||||||
 | 
					            if (!newCollection?.id || JSON.stringify(oldCollection) != JSON.stringify(newCollection)) {
 | 
				
			||||||
 | 
					                changes.push({
 | 
				
			||||||
 | 
					                    old: oldCollection,
 | 
				
			||||||
 | 
					                    new: newCollection,
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // add only new collections
 | 
				
			||||||
 | 
					        for (const newCollection of newCollections) {
 | 
				
			||||||
 | 
					            const oldCollection = CommonHelper.findByKey(oldCollections, "id", newCollection.id) || null;
 | 
				
			||||||
 | 
					            if (!oldCollection?.id) {
 | 
				
			||||||
 | 
					                changes.push({
 | 
				
			||||||
 | 
					                    old: oldCollection,
 | 
				
			||||||
 | 
					                    new: newCollection,
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function diffsToHtml(diffs, ops = [window.DIFF_INSERT, window.DIFF_DELETE, window.DIFF_EQUAL]) {
 | 
					    function diffsToHtml(diffs, ops = [window.DIFF_INSERT, window.DIFF_DELETE, window.DIFF_EQUAL]) {
 | 
				
			||||||
        const html = [];
 | 
					        const html = [];
 | 
				
			||||||
        const pattern_amp = /&/g;
 | 
					        const pattern_amp = /&/g;
 | 
				
			||||||
| 
						 | 
					@ -58,11 +90,11 @@
 | 
				
			||||||
        return html.join("");
 | 
					        return html.join("");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function diff(ops = [window.DIFF_INSERT, window.DIFF_DELETE, window.DIFF_EQUAL]) {
 | 
					    function diff(obj1, obj2, ops = [window.DIFF_INSERT, window.DIFF_DELETE, window.DIFF_EQUAL]) {
 | 
				
			||||||
        const dmp = new diff_match_patch();
 | 
					        const dmp = new diff_match_patch();
 | 
				
			||||||
        const lines = dmp.diff_linesToChars_(
 | 
					        const lines = dmp.diff_linesToChars_(
 | 
				
			||||||
            JSON.stringify(oldCollections, null, 4),
 | 
					            obj1 ? JSON.stringify(obj1, null, 4) : "",
 | 
				
			||||||
            JSON.stringify(newCollections, null, 4)
 | 
					            obj2 ? JSON.stringify(obj2, null, 4) : ""
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        const diffs = dmp.diff_main(lines.chars1, lines.chars2, false);
 | 
					        const diffs = dmp.diff_main(lines.chars1, lines.chars2, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -80,7 +112,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            await ApiClient.collections.import(newCollections);
 | 
					            await ApiClient.collections.import(newCollections);
 | 
				
			||||||
            addSuccessToast("Successfully imported the provided schema.");
 | 
					            addSuccessToast("Successfully imported the provided collections.");
 | 
				
			||||||
            dispatch("submit");
 | 
					            dispatch("submit");
 | 
				
			||||||
        } catch (err) {
 | 
					        } catch (err) {
 | 
				
			||||||
            ApiClient.errorResponseHandler(err);
 | 
					            ApiClient.errorResponseHandler(err);
 | 
				
			||||||
| 
						 | 
					@ -92,27 +124,58 @@
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<OverlayPanel bind:this={panel} class="full-width-popup import-popup" popup on:show on:hide>
 | 
					<OverlayPanel
 | 
				
			||||||
 | 
					    bind:this={panel}
 | 
				
			||||||
 | 
					    class="full-width-popup import-popup"
 | 
				
			||||||
 | 
					    overlayClose={false}
 | 
				
			||||||
 | 
					    popup
 | 
				
			||||||
 | 
					    on:show
 | 
				
			||||||
 | 
					    on:hide
 | 
				
			||||||
 | 
					>
 | 
				
			||||||
    <svelte:fragment slot="header">
 | 
					    <svelte:fragment slot="header">
 | 
				
			||||||
        <h4 class="center txt-break">Side-by-side diff</h4>
 | 
					        <h4 class="center txt-break">Side-by-side diff</h4>
 | 
				
			||||||
    </svelte:fragment>
 | 
					    </svelte:fragment>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div class="grid m-b-base">
 | 
					    <div class="grid grid-sm m-b-sm">
 | 
				
			||||||
        <div class="col-6">
 | 
					        {#each changes as pair (pair.old?.id + pair.new?.id)}
 | 
				
			||||||
            <div class="section-title">Old schema</div>
 | 
					            <div class="col-12">
 | 
				
			||||||
            <code class="code-block">{@html diff([window.DIFF_DELETE, window.DIFF_EQUAL])}</code>
 | 
					                <div class="flex flex-gap-10">
 | 
				
			||||||
 | 
					                    {#if !pair.old?.id}
 | 
				
			||||||
 | 
					                        <span class="label label-success">New</span>
 | 
				
			||||||
 | 
					                        <strong>{pair.new?.name}</strong>
 | 
				
			||||||
 | 
					                    {:else if !pair.new?.id}
 | 
				
			||||||
 | 
					                        <span class="label label-danger">Deleted</span>
 | 
				
			||||||
 | 
					                        <strong>{pair.old?.name}</strong>
 | 
				
			||||||
 | 
					                    {:else}
 | 
				
			||||||
 | 
					                        <span class="label label-warning">Modified</span>
 | 
				
			||||||
 | 
					                        <div class="inline-flex fleg-gap-5">
 | 
				
			||||||
 | 
					                            {#if pair.old.name !== pair.new.name}
 | 
				
			||||||
 | 
					                                <strong class="txt-strikethrough txt-hint">{pair.old.name}</strong>
 | 
				
			||||||
 | 
					                                <i class="ri-arrow-right-line txt-sm" />
 | 
				
			||||||
 | 
					                            {/if}
 | 
				
			||||||
 | 
					                            <strong class="txt">{pair.new.name}</strong>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
        <div class="col-6">
 | 
					                    {/if}
 | 
				
			||||||
            <div class="section-title">New schema</div>
 | 
					 | 
				
			||||||
            <code class="code-block">{@html diff([window.DIFF_INSERT, window.DIFF_EQUAL])}</code>
 | 
					 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="col-6 p-b-10">
 | 
				
			||||||
 | 
					                <code class="code-block">
 | 
				
			||||||
 | 
					                    {@html diff(pair.old, pair.new, [window.DIFF_DELETE, window.DIFF_EQUAL])}
 | 
				
			||||||
 | 
					                </code>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="col-6 p-b-10">
 | 
				
			||||||
 | 
					                <code class="code-block">
 | 
				
			||||||
 | 
					                    {@html diff(pair.old, pair.new, [window.DIFF_INSERT, window.DIFF_EQUAL])}
 | 
				
			||||||
 | 
					                </code>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        {/each}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <svelte:fragment slot="footer">
 | 
					    <svelte:fragment slot="footer">
 | 
				
			||||||
        <button type="button" class="btn btn-secondary" on:click={hide}>Close</button>
 | 
					        <button type="button" class="btn btn-secondary" on:click={hide}>Close</button>
 | 
				
			||||||
        <button
 | 
					        <button
 | 
				
			||||||
            type="button"
 | 
					            type="button"
 | 
				
			||||||
            class="btn btn-expanded m-l-auto"
 | 
					            class="btn btn-expanded"
 | 
				
			||||||
            class:btn-loading={isImporting}
 | 
					            class:btn-loading={isImporting}
 | 
				
			||||||
            on:click={() => submitImport()}
 | 
					            on:click={() => submitImport()}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,19 +14,19 @@
 | 
				
			||||||
    let fileInput;
 | 
					    let fileInput;
 | 
				
			||||||
    let importPopup;
 | 
					    let importPopup;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let schema = "";
 | 
					    let schemas = "";
 | 
				
			||||||
    let isLoadingFile = false;
 | 
					    let isLoadingFile = false;
 | 
				
			||||||
    let newCollections = [];
 | 
					    let newCollections = [];
 | 
				
			||||||
    let oldCollections = [];
 | 
					    let oldCollections = [];
 | 
				
			||||||
    let collectionsToModify = [];
 | 
					    let collectionsToModify = [];
 | 
				
			||||||
    let isLoadingOldCollections = false;
 | 
					    let isLoadingOldCollections = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $: if (typeof schema !== "undefined") {
 | 
					    $: if (typeof schemas !== "undefined") {
 | 
				
			||||||
        loadNewCollections(schema);
 | 
					        loadNewCollections(schemas);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $: isValid =
 | 
					    $: isValid =
 | 
				
			||||||
        !!schema &&
 | 
					        !!schemas &&
 | 
				
			||||||
        newCollections.length &&
 | 
					        newCollections.length &&
 | 
				
			||||||
        newCollections.length === newCollections.filter((item) => !!item.id && !!item.name).length;
 | 
					        newCollections.length === newCollections.filter((item) => !!item.id && !!item.name).length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,7 +43,7 @@
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $: hasChanges =
 | 
					    $: hasChanges =
 | 
				
			||||||
        !!schema && (collectionsToDelete.length || collectionsToAdd.length || collectionsToModify.length);
 | 
					        !!schemas && (collectionsToDelete.length || collectionsToAdd.length || collectionsToModify.length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $: canImport = !isLoadingOldCollections && isValid && hasChanges;
 | 
					    $: canImport = !isLoadingOldCollections && isValid && hasChanges;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -95,7 +95,7 @@
 | 
				
			||||||
        newCollections = [];
 | 
					        newCollections = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            newCollections = JSON.parse(schema);
 | 
					            newCollections = JSON.parse(schemas);
 | 
				
			||||||
        } catch (_) {}
 | 
					        } catch (_) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!Array.isArray(newCollections)) {
 | 
					        if (!Array.isArray(newCollections)) {
 | 
				
			||||||
| 
						 | 
					@ -120,12 +120,12 @@
 | 
				
			||||||
            isLoadingFile = false;
 | 
					            isLoadingFile = false;
 | 
				
			||||||
            fileInput.value = ""; // reset
 | 
					            fileInput.value = ""; // reset
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            schema = event.target.result;
 | 
					            schemas = event.target.result;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await tick();
 | 
					            await tick();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!newCollections.length) {
 | 
					            if (!newCollections.length) {
 | 
				
			||||||
                addErrorToast("Invalid collections schema.");
 | 
					                addErrorToast("Invalid collections list.");
 | 
				
			||||||
                clear();
 | 
					                clear();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
| 
						 | 
					@ -142,7 +142,7 @@
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function clear() {
 | 
					    function clear() {
 | 
				
			||||||
        schema = "";
 | 
					        schemas = "";
 | 
				
			||||||
        fileInput.value = "";
 | 
					        fileInput.value = "";
 | 
				
			||||||
        setErrors({});
 | 
					        setErrors({});
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -177,7 +177,7 @@
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <p>
 | 
					                    <p>
 | 
				
			||||||
                        Paste below the collections schema you want to import or
 | 
					                        Paste below the collections you want to import or
 | 
				
			||||||
                        <button
 | 
					                        <button
 | 
				
			||||||
                            class="btn btn-outline btn-sm m-l-5"
 | 
					                            class="btn btn-outline btn-sm m-l-5"
 | 
				
			||||||
                            class:btn-loading={isLoadingFile}
 | 
					                            class:btn-loading={isLoadingFile}
 | 
				
			||||||
| 
						 | 
					@ -191,18 +191,18 @@
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <Field class="form-field {!isValid ? 'field-error' : ''}" name="collections" let:uniqueId>
 | 
					                <Field class="form-field {!isValid ? 'field-error' : ''}" name="collections" let:uniqueId>
 | 
				
			||||||
                    <label for={uniqueId} class="p-b-10">Collections schema</label>
 | 
					                    <label for={uniqueId} class="p-b-10">Collections</label>
 | 
				
			||||||
                    <textarea
 | 
					                    <textarea
 | 
				
			||||||
                        id={uniqueId}
 | 
					                        id={uniqueId}
 | 
				
			||||||
                        class="code"
 | 
					                        class="code"
 | 
				
			||||||
                        spellcheck="false"
 | 
					                        spellcheck="false"
 | 
				
			||||||
                        rows="15"
 | 
					                        rows="15"
 | 
				
			||||||
                        required
 | 
					                        required
 | 
				
			||||||
                        bind:value={schema}
 | 
					                        bind:value={schemas}
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    {#if !!schema && !isValid}
 | 
					                    {#if !!schemas && !isValid}
 | 
				
			||||||
                        <div class="help-block help-block-error">Invalid collections schema.</div>
 | 
					                        <div class="help-block help-block-error">Invalid collections schemas.</div>
 | 
				
			||||||
                    {/if}
 | 
					                    {/if}
 | 
				
			||||||
                </Field>
 | 
					                </Field>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -212,7 +212,7 @@
 | 
				
			||||||
                            <i class="ri-information-line" />
 | 
					                            <i class="ri-information-line" />
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                        <div class="content">
 | 
					                        <div class="content">
 | 
				
			||||||
                            <string>Your collections schema is already up-to-date!</string>
 | 
					                            <string>Your collections structure is already up-to-date!</string>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                {/if}
 | 
					                {/if}
 | 
				
			||||||
| 
						 | 
					@ -234,17 +234,17 @@
 | 
				
			||||||
                        {/if}
 | 
					                        {/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        {#if collectionsToModify.length}
 | 
					                        {#if collectionsToModify.length}
 | 
				
			||||||
                            {#each collectionsToModify as entry (entry.old.id + entry.new.id)}
 | 
					                            {#each collectionsToModify as pair (pair.old.id + pair.new.id)}
 | 
				
			||||||
                                <div class="list-item">
 | 
					                                <div class="list-item">
 | 
				
			||||||
                                    <span class="label label-warning list-label">Modified</span>
 | 
					                                    <span class="label label-warning list-label">Modified</span>
 | 
				
			||||||
                                    <strong>
 | 
					                                    <strong>
 | 
				
			||||||
                                        {#if entry.old.name !== entry.new.name}
 | 
					                                        {#if pair.old.name !== pair.new.name}
 | 
				
			||||||
                                            <span class="txt-strikethrough txt-hint">{entry.old.name}</span> -
 | 
					                                            <span class="txt-strikethrough txt-hint">{pair.old.name}</span> -
 | 
				
			||||||
                                        {/if}
 | 
					                                        {/if}
 | 
				
			||||||
                                        {entry.new.name}
 | 
					                                        {pair.new.name}
 | 
				
			||||||
                                    </strong>
 | 
					                                    </strong>
 | 
				
			||||||
                                    {#if entry.new.id}
 | 
					                                    {#if pair.new.id}
 | 
				
			||||||
                                        <small class="txt-hint">({entry.new.id})</small>
 | 
					                                        <small class="txt-hint">({pair.new.id})</small>
 | 
				
			||||||
                                    {/if}
 | 
					                                    {/if}
 | 
				
			||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
                            {/each}
 | 
					                            {/each}
 | 
				
			||||||
| 
						 | 
					@ -265,7 +265,7 @@
 | 
				
			||||||
                {/if}
 | 
					                {/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <div class="flex m-t-base">
 | 
					                <div class="flex m-t-base">
 | 
				
			||||||
                    {#if !!schema}
 | 
					                    {#if !!schemas}
 | 
				
			||||||
                        <button type="button" class="btn btn-secondary link-hint" on:click={() => clear()}>
 | 
					                        <button type="button" class="btn btn-secondary link-hint" on:click={() => clear()}>
 | 
				
			||||||
                            <span class="txt">Clear</span>
 | 
					                            <span class="txt">Clear</span>
 | 
				
			||||||
                        </button>
 | 
					                        </button>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -98,7 +98,8 @@
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        confirm(`Do you really want to delete the selected user?`, () => {
 | 
					        confirm(`Do you really want to delete the selected user?`, () => {
 | 
				
			||||||
            return ApiClient.users.delete(user.id)
 | 
					            return ApiClient.users
 | 
				
			||||||
 | 
					                .delete(user.id)
 | 
				
			||||||
                .then(() => {
 | 
					                .then(() => {
 | 
				
			||||||
                    confirmClose = false;
 | 
					                    confirmClose = false;
 | 
				
			||||||
                    hide();
 | 
					                    hide();
 | 
				
			||||||
| 
						 | 
					@ -112,7 +113,8 @@
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function sendVerificationEmail(notify = true) {
 | 
					    function sendVerificationEmail(notify = true) {
 | 
				
			||||||
        return ApiClient.users.requestVerification(user.isNew ? email : user.email)
 | 
					        return ApiClient.users
 | 
				
			||||||
 | 
					            .requestVerification(user.isNew ? email : user.email)
 | 
				
			||||||
            .then(() => {
 | 
					            .then(() => {
 | 
				
			||||||
                confirmClose = false;
 | 
					                confirmClose = false;
 | 
				
			||||||
                hide();
 | 
					                hide();
 | 
				
			||||||
| 
						 | 
					@ -182,7 +184,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        {#if user.isNew || changePasswordToggle}
 | 
					        {#if user.isNew || changePasswordToggle}
 | 
				
			||||||
            <div class="col-12">
 | 
					            <div class="col-12">
 | 
				
			||||||
                <div class="grid" transition:slide={{ duration: 150 }}>
 | 
					                <div class="grid" transition:slide|local={{ duration: 150 }}>
 | 
				
			||||||
                    <div class="col-sm-6">
 | 
					                    <div class="col-sm-6">
 | 
				
			||||||
                        <Field class="form-field required" name="password" let:uniqueId>
 | 
					                        <Field class="form-field required" name="password" let:uniqueId>
 | 
				
			||||||
                            <label for={uniqueId}>
 | 
					                            <label for={uniqueId}>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue