| 
									
										
										
										
											2022-08-05 11:00:38 +08:00
										 |  |  | package forms | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2022-08-06 13:03:34 +08:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2022-08-05 11:00:38 +08:00
										 |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"log" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	validation "github.com/go-ozzo/ozzo-validation/v4" | 
					
						
							|  |  |  | 	"github.com/pocketbase/pocketbase/core" | 
					
						
							|  |  |  | 	"github.com/pocketbase/pocketbase/daos" | 
					
						
							|  |  |  | 	"github.com/pocketbase/pocketbase/models" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-06 23:15:18 +08:00
										 |  |  | // CollectionsImport specifies a form model to bulk import
 | 
					
						
							|  |  |  | // (create, replace and delete) collections from a user provided list.
 | 
					
						
							| 
									
										
										
										
											2022-08-05 11:00:38 +08:00
										 |  |  | type CollectionsImport struct { | 
					
						
							| 
									
										
										
										
											2022-08-06 23:15:18 +08:00
										 |  |  | 	config CollectionsImportConfig | 
					
						
							| 
									
										
										
										
											2022-08-05 11:00:38 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-09 00:16:33 +08:00
										 |  |  | 	Collections   []*models.Collection `form:"collections" json:"collections"` | 
					
						
							|  |  |  | 	DeleteMissing bool                 `form:"deleteMissing" json:"deleteMissing"` | 
					
						
							| 
									
										
										
										
											2022-08-05 11:00:38 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-06 23:15:18 +08:00
										 |  |  | // CollectionsImportConfig is the [CollectionsImport] factory initializer config.
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2022-08-07 20:38:21 +08:00
										 |  |  | // NB! App is a required struct member.
 | 
					
						
							| 
									
										
										
										
											2022-08-06 23:15:18 +08:00
										 |  |  | type CollectionsImportConfig struct { | 
					
						
							| 
									
										
										
										
											2022-08-31 18:38:31 +08:00
										 |  |  | 	App core.App | 
					
						
							|  |  |  | 	Dao *daos.Dao | 
					
						
							| 
									
										
										
										
											2022-08-07 20:38:21 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewCollectionsImport creates a new [CollectionsImport] form with
 | 
					
						
							|  |  |  | // initializer config created from the provided [core.App] instance.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // If you want to submit the form as part of another transaction, use
 | 
					
						
							| 
									
										
										
										
											2022-08-31 18:38:31 +08:00
										 |  |  | // [NewCollectionsImportWithConfig] with explicitly set Dao.
 | 
					
						
							| 
									
										
										
										
											2022-08-07 20:38:21 +08:00
										 |  |  | func NewCollectionsImport(app core.App) *CollectionsImport { | 
					
						
							|  |  |  | 	return NewCollectionsImportWithConfig(CollectionsImportConfig{ | 
					
						
							|  |  |  | 		App: app, | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2022-08-06 23:15:18 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewCollectionsImportWithConfig creates a new [CollectionsImport]
 | 
					
						
							|  |  |  | // form with the provided config or panics on invalid configuration.
 | 
					
						
							|  |  |  | func NewCollectionsImportWithConfig(config CollectionsImportConfig) *CollectionsImport { | 
					
						
							|  |  |  | 	form := &CollectionsImport{config: config} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-07 20:38:21 +08:00
										 |  |  | 	if form.config.App == nil { | 
					
						
							|  |  |  | 		panic("Missing required config.App instance.") | 
					
						
							| 
									
										
										
										
											2022-08-05 11:00:38 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-31 18:38:31 +08:00
										 |  |  | 	if form.config.Dao == nil { | 
					
						
							|  |  |  | 		form.config.Dao = form.config.App.Dao() | 
					
						
							| 
									
										
										
										
											2022-08-07 20:38:21 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-08-05 11:00:38 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-07 20:38:21 +08:00
										 |  |  | 	return form | 
					
						
							| 
									
										
										
										
											2022-08-06 23:15:18 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-05 11:00:38 +08:00
										 |  |  | // Validate makes the form validatable by implementing [validation.Validatable] interface.
 | 
					
						
							|  |  |  | func (form *CollectionsImport) Validate() error { | 
					
						
							|  |  |  | 	return validation.ValidateStruct(form, | 
					
						
							|  |  |  | 		validation.Field(&form.Collections, validation.Required), | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Submit applies the import, aka.:
 | 
					
						
							|  |  |  | // - imports the form collections (create or replace)
 | 
					
						
							|  |  |  | // - sync the collection changes with their related records table
 | 
					
						
							|  |  |  | // - ensures the integrity of the imported structure (aka. run validations for each collection)
 | 
					
						
							| 
									
										
										
										
											2022-08-09 00:16:33 +08:00
										 |  |  | // - if [form.DeleteMissing] is set, deletes all local collections that are not found in the imports list
 | 
					
						
							| 
									
										
										
										
											2022-08-05 11:00:38 +08:00
										 |  |  | //
 | 
					
						
							|  |  |  | // All operations are wrapped in a single transaction that are
 | 
					
						
							|  |  |  | // rollbacked on the first encountered error.
 | 
					
						
							| 
									
										
										
										
											2022-08-08 01:58:21 +08:00
										 |  |  | //
 | 
					
						
							|  |  |  | // You can optionally provide a list of InterceptorFunc to further
 | 
					
						
							|  |  |  | // modify the form behavior before persisting it.
 | 
					
						
							|  |  |  | func (form *CollectionsImport) Submit(interceptors ...InterceptorFunc) error { | 
					
						
							| 
									
										
										
										
											2022-08-05 11:00:38 +08:00
										 |  |  | 	if err := form.Validate(); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-09 00:16:33 +08:00
										 |  |  | 	return runInterceptors(func() error { | 
					
						
							| 
									
										
										
										
											2022-08-31 18:38:31 +08:00
										 |  |  | 		return form.config.Dao.RunInTransaction(func(txDao *daos.Dao) error { | 
					
						
							| 
									
										
										
										
											2022-08-09 00:16:33 +08:00
										 |  |  | 			importErr := txDao.ImportCollections( | 
					
						
							|  |  |  | 				form.Collections, | 
					
						
							|  |  |  | 				form.DeleteMissing, | 
					
						
							|  |  |  | 				form.beforeRecordsSync, | 
					
						
							|  |  |  | 			) | 
					
						
							|  |  |  | 			if importErr == nil { | 
					
						
							|  |  |  | 				return nil | 
					
						
							| 
									
										
										
										
											2022-08-05 11:00:38 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-09 00:16:33 +08:00
										 |  |  | 			// validation failure
 | 
					
						
							|  |  |  | 			if err, ok := importErr.(validation.Errors); ok { | 
					
						
							|  |  |  | 				return err | 
					
						
							| 
									
										
										
										
											2022-08-06 13:03:34 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-09 00:16:33 +08:00
										 |  |  | 			// generic/db failure
 | 
					
						
							|  |  |  | 			if form.config.App.IsDebug() { | 
					
						
							|  |  |  | 				log.Println("Internal import failure:", importErr) | 
					
						
							| 
									
										
										
										
											2022-08-06 13:03:34 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-08-09 00:16:33 +08:00
										 |  |  | 			return validation.Errors{"collections": validation.NewError( | 
					
						
							|  |  |  | 				"collections_import_failure", | 
					
						
							|  |  |  | 				"Failed to import the collections configuration.", | 
					
						
							|  |  |  | 			)} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	}, interceptors...) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-08-06 13:03:34 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-09 00:16:33 +08:00
										 |  |  | func (form *CollectionsImport) beforeRecordsSync(txDao *daos.Dao, mappedNew, mappedOld map[string]*models.Collection) error { | 
					
						
							|  |  |  | 	// refresh the actual persisted collections list
 | 
					
						
							|  |  |  | 	refreshedCollections := []*models.Collection{} | 
					
						
							|  |  |  | 	if err := txDao.CollectionQuery().OrderBy("created ASC").All(&refreshedCollections); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-08-05 11:00:38 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-09 00:16:33 +08:00
										 |  |  | 	// trigger the validator for each existing collection to
 | 
					
						
							|  |  |  | 	// ensure that the app is not left in a broken state
 | 
					
						
							|  |  |  | 	for _, collection := range refreshedCollections { | 
					
						
							|  |  |  | 		upsertModel := mappedOld[collection.GetId()] | 
					
						
							|  |  |  | 		if upsertModel == nil { | 
					
						
							|  |  |  | 			upsertModel = collection | 
					
						
							| 
									
										
										
										
											2022-08-05 11:00:38 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-09 00:16:33 +08:00
										 |  |  | 		upsertForm := NewCollectionUpsertWithConfig(CollectionUpsertConfig{ | 
					
						
							| 
									
										
										
										
											2022-08-31 18:38:31 +08:00
										 |  |  | 			App: form.config.App, | 
					
						
							|  |  |  | 			Dao: txDao, | 
					
						
							| 
									
										
										
										
											2022-08-09 00:16:33 +08:00
										 |  |  | 		}, upsertModel) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// load form fields with the refreshed collection state
 | 
					
						
							|  |  |  | 		upsertForm.Id = collection.Id | 
					
						
							|  |  |  | 		upsertForm.Name = collection.Name | 
					
						
							|  |  |  | 		upsertForm.System = collection.System | 
					
						
							|  |  |  | 		upsertForm.ListRule = collection.ListRule | 
					
						
							|  |  |  | 		upsertForm.ViewRule = collection.ViewRule | 
					
						
							|  |  |  | 		upsertForm.CreateRule = collection.CreateRule | 
					
						
							|  |  |  | 		upsertForm.UpdateRule = collection.UpdateRule | 
					
						
							|  |  |  | 		upsertForm.DeleteRule = collection.DeleteRule | 
					
						
							|  |  |  | 		upsertForm.Schema = collection.Schema | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if err := upsertForm.Validate(); err != nil { | 
					
						
							|  |  |  | 			// serialize the validation error(s)
 | 
					
						
							| 
									
										
										
										
											2022-08-10 18:22:27 +08:00
										 |  |  | 			serializedErr, _ := json.MarshalIndent(err, "", "  ") | 
					
						
							| 
									
										
										
										
											2022-08-09 00:16:33 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			return validation.Errors{"collections": validation.NewError( | 
					
						
							|  |  |  | 				"collections_import_validate_failure", | 
					
						
							| 
									
										
										
										
											2022-08-10 18:22:27 +08:00
										 |  |  | 				fmt.Sprintf("Data validations failed for collection %q (%s):\n%s", collection.Name, collection.Id, serializedErr), | 
					
						
							| 
									
										
										
										
											2022-08-09 00:16:33 +08:00
										 |  |  | 			)} | 
					
						
							| 
									
										
										
										
											2022-08-05 11:00:38 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-08-09 00:16:33 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-08-05 11:00:38 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-09 00:16:33 +08:00
										 |  |  | 	return nil | 
					
						
							| 
									
										
										
										
											2022-08-05 11:00:38 +08:00
										 |  |  | } |