diff --git a/apis/collection.go b/apis/collection.go index bcc792e4..d4e8af9a 100644 --- a/apis/collection.go +++ b/apis/collection.go @@ -169,6 +169,7 @@ func (api *collectionApi) delete(c echo.Context) error { return handlerErr } +// @todo add event func (api *collectionApi) bulkImport(c echo.Context) error { form := forms.NewCollectionsImport(api.app) @@ -182,5 +183,5 @@ func (api *collectionApi) bulkImport(c echo.Context) error { return rest.NewBadRequestError("Failed to import the submitted collections.", submitErr) } - return nil + return c.NoContent(http.StatusNoContent) } diff --git a/forms/collections_import.go b/forms/collections_import.go index dd3e2441..10cca676 100644 --- a/forms/collections_import.go +++ b/forms/collections_import.go @@ -1,6 +1,7 @@ package forms import ( + "encoding/json" "fmt" "log" @@ -59,24 +60,8 @@ func (form *CollectionsImport) Submit() error { mappedOldCollections[old.GetId()] = old } - // raw insert/replace (aka. without any validations) - // (required to make sure that all linked collections exists before running the validations) mappedFormCollections := make(map[string]*models.Collection, len(form.Collections)) for _, collection := range form.Collections { - if mappedOldCollections[collection.GetId()] == nil { - collection.MarkAsNew() - } - - if err := txDao.Save(collection); err != nil { - if form.app.IsDebug() { - log.Println("[CollectionsImport] Save failure", collection.Name, err) - } - return validation.Errors{"collections": validation.NewError( - "collections_import_save_failure", - fmt.Sprintf("Failed to save the imported collection %q (id: %s).", collection.Name, collection.Id), - )} - } - mappedFormCollections[collection.GetId()] = collection } @@ -89,15 +74,33 @@ func (form *CollectionsImport) Submit() error { if form.app.IsDebug() { log.Println("[CollectionsImport] DeleteOthers failure", old.Name, err) } - return validation.Errors{"deleteOthers": validation.NewError( + return validation.Errors{"collections": validation.NewError( "collections_import_collection_delete_failure", - fmt.Sprintf("Failed to delete collection %q. Make sure that the collection is not system or referenced by other collections.", old.Name), + 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), )} } } } } + // raw insert/replace (aka. without any validations) + // (required to make sure that all linked collections exists before running the validations) + for _, collection := range form.Collections { + if mappedOldCollections[collection.GetId()] == nil { + collection.MarkAsNew() + } + + if err := txDao.Save(collection); err != nil { + if form.app.IsDebug() { + log.Println("[CollectionsImport] Save failure", collection.Name, err) + } + return validation.Errors{"collections": validation.NewError( + "collections_import_save_failure", + fmt.Sprintf("Integrity constraints failed - the collection %q (%s) cannot be imported.", collection.Name, collection.Id), + )} + } + } + // refresh the actual persisted collections list refreshedCollections := []*models.Collection{} if err := txDao.CollectionQuery().All(&refreshedCollections); err != nil { @@ -125,9 +128,13 @@ func (form *CollectionsImport) Submit() error { if form.app.IsDebug() { log.Println("[CollectionsImport] Validate failure", collection.Name, err) } + + // serialize the validation error(s) + serializedErr, _ := json.Marshal(err) + return validation.Errors{"collections": validation.NewError( "collections_import_validate_failure", - fmt.Sprintf("Integrity check failed - collection %q has invalid data.", collection.Name), + fmt.Sprintf("Data validations failed for collection %q (%s): %s", collection.Name, collection.Id, serializedErr), )} } } diff --git a/ui/package-lock.json b/ui/package-lock.json index 6f331326..d5cdf1e6 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -9,7 +9,6 @@ "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", "@codemirror/lang-javascript": "^6.0.2", - "@codemirror/lang-json": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/legacy-modes": "^6.0.0", "@codemirror/search": "^6.0.0", @@ -29,8 +28,7 @@ } }, "../../js-sdk": { - "name": "pocketbase", - "version": "0.3.0", + "version": "0.3.1", "dev": true, "license": "MIT", "devDependencies": { @@ -101,16 +99,6 @@ "@lezer/javascript": "^1.0.0" } }, - "node_modules/@codemirror/lang-json": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.0.tgz", - "integrity": "sha512-DvTcYTKLmg2viADXlTdufrT334M9jowe1qO02W28nvm+nejcvhM5vot5mE8/kPrxYw/HJHhwu1z2PyBpnMLCNQ==", - "dev": true, - "dependencies": { - "@codemirror/language": "^6.0.0", - "@lezer/json": "^1.0.0" - } - }, "node_modules/@codemirror/language": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.2.1.tgz", @@ -163,9 +151,9 @@ "dev": true }, "node_modules/@codemirror/view": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.1.4.tgz", - "integrity": "sha512-pekgUX+0hL4ri2JV7/bu7jhhwOgOhU1eRc1/ZyAQYCWcCI4TPB1qLrPE3cD/qW9yjBcYiN9MN0XI1tjK7Yw05Q==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.2.0.tgz", + "integrity": "sha512-3emW1symh+GoteFMBPsltjmF790U/trouLILATh3JodbF/z98HvcQh2g3+H6dfNIHx16uNonsAF4mNzVr1TJNA==", "dev": true, "dependencies": { "@codemirror/state": "^6.0.0", @@ -214,16 +202,6 @@ "@lezer/lr": "^1.0.0" } }, - "node_modules/@lezer/json": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.0.tgz", - "integrity": "sha512-zbAuUY09RBzCoCA3lJ1+ypKw5WSNvLqGMtasdW6HvVOqZoCpPr8eWrsGnOVWGKGn8Rh21FnrKRVlJXrGAVUqRw==", - "dev": true, - "dependencies": { - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, "node_modules/@lezer/lr": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.2.1.tgz", @@ -1038,9 +1016,9 @@ } }, "node_modules/sass": { - "version": "1.54.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.2.tgz", - "integrity": "sha512-wbVV26sejsCIbBScZZtNkvnrB/bVCQ8hSlZ01D9nzsVh9zLqCkWrlpvTb3YEb6xsuNi9cx75hncqwikHFSz7tw==", + "version": "1.54.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.3.tgz", + "integrity": "sha512-fLodey5Qd41Pxp/Tk7Al97sViYwF/TazRc5t6E65O7JOk4XF8pzwIW7CvCxYVOfJFFI/1x5+elDyBIixrp+zrw==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -1232,16 +1210,6 @@ "@lezer/javascript": "^1.0.0" } }, - "@codemirror/lang-json": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.0.tgz", - "integrity": "sha512-DvTcYTKLmg2viADXlTdufrT334M9jowe1qO02W28nvm+nejcvhM5vot5mE8/kPrxYw/HJHhwu1z2PyBpnMLCNQ==", - "dev": true, - "requires": { - "@codemirror/language": "^6.0.0", - "@lezer/json": "^1.0.0" - } - }, "@codemirror/language": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.2.1.tgz", @@ -1294,9 +1262,9 @@ "dev": true }, "@codemirror/view": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.1.4.tgz", - "integrity": "sha512-pekgUX+0hL4ri2JV7/bu7jhhwOgOhU1eRc1/ZyAQYCWcCI4TPB1qLrPE3cD/qW9yjBcYiN9MN0XI1tjK7Yw05Q==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.2.0.tgz", + "integrity": "sha512-3emW1symh+GoteFMBPsltjmF790U/trouLILATh3JodbF/z98HvcQh2g3+H6dfNIHx16uNonsAF4mNzVr1TJNA==", "dev": true, "requires": { "@codemirror/state": "^6.0.0", @@ -1336,16 +1304,6 @@ "@lezer/lr": "^1.0.0" } }, - "@lezer/json": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.0.tgz", - "integrity": "sha512-zbAuUY09RBzCoCA3lJ1+ypKw5WSNvLqGMtasdW6HvVOqZoCpPr8eWrsGnOVWGKGn8Rh21FnrKRVlJXrGAVUqRw==", - "dev": true, - "requires": { - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, "@lezer/lr": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.2.1.tgz", @@ -1855,9 +1813,9 @@ } }, "sass": { - "version": "1.54.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.2.tgz", - "integrity": "sha512-wbVV26sejsCIbBScZZtNkvnrB/bVCQ8hSlZ01D9nzsVh9zLqCkWrlpvTb3YEb6xsuNi9cx75hncqwikHFSz7tw==", + "version": "1.54.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.3.tgz", + "integrity": "sha512-fLodey5Qd41Pxp/Tk7Al97sViYwF/TazRc5t6E65O7JOk4XF8pzwIW7CvCxYVOfJFFI/1x5+elDyBIixrp+zrw==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", diff --git a/ui/src/components/base/CodeEditor.svelte b/ui/src/components/base/CodeEditor.svelte deleted file mode 100644 index 9ef2d5ed..00000000 --- a/ui/src/components/base/CodeEditor.svelte +++ /dev/null @@ -1,134 +0,0 @@ - - -
diff --git a/ui/src/components/collections/CollectionsExportForm.svelte b/ui/src/components/collections/CollectionsExportForm.svelte deleted file mode 100644 index a20da437..00000000 --- a/ui/src/components/collections/CollectionsExportForm.svelte +++ /dev/null @@ -1,352 +0,0 @@ - - -
-
-
- - {@html oldSchema} - -
-
- - {@html newSchema} - -
-
- - diff --git a/ui/src/components/collections/CollectionsExportPanel.svelte b/ui/src/components/collections/CollectionsExportPanel.svelte deleted file mode 100644 index f25705d4..00000000 --- a/ui/src/components/collections/CollectionsExportPanel.svelte +++ /dev/null @@ -1,29 +0,0 @@ - - - - -

Export collections schema

-
- - -
diff --git a/ui/src/components/records/PageRecords.svelte b/ui/src/components/records/PageRecords.svelte index 23d31435..9a2772a3 100644 --- a/ui/src/components/records/PageRecords.svelte +++ b/ui/src/components/records/PageRecords.svelte @@ -13,7 +13,6 @@ import CollectionsSidebar from "@/components/collections/CollectionsSidebar.svelte"; import CollectionUpsertPanel from "@/components/collections/CollectionUpsertPanel.svelte"; import CollectionDocsPanel from "@/components/collections/docs/CollectionDocsPanel.svelte"; - import CollectionsExportPanel from "@/components/collections/CollectionsExportPanel.svelte"; import RecordUpsertPanel from "@/components/records/RecordUpsertPanel.svelte"; import RecordsList from "@/components/records/RecordsList.svelte"; @@ -21,7 +20,6 @@ const queryParams = new URLSearchParams($querystring); - let collectionsExportPanel; let collectionUpsertPanel; let collectionDocsPanel; let recordPanel; @@ -132,8 +130,6 @@ {/if} - - diff --git a/ui/src/components/base/DiffPopup.svelte b/ui/src/components/settings/ImportPopup.svelte similarity index 51% rename from ui/src/components/base/DiffPopup.svelte rename to ui/src/components/settings/ImportPopup.svelte index e4e6775c..0f1c77d4 100644 --- a/ui/src/components/base/DiffPopup.svelte +++ b/ui/src/components/settings/ImportPopup.svelte @@ -1,17 +1,19 @@ - + -

{title}

+

Side-by-side diff

-
{contentATitle}
- {@html diff([DIFF_DELETE, DIFF_EQUAL])} +
Old schema
+ {@html diff([window.DIFF_DELETE, window.DIFF_EQUAL])}
-
{contentBTitle}
- {@html diff([DIFF_INSERT, DIFF_EQUAL])} +
New schema
+ {@html diff([window.DIFF_INSERT, window.DIFF_EQUAL])}
+
diff --git a/ui/src/components/settings/PageImportCollections.svelte b/ui/src/components/settings/PageImportCollections.svelte index a20cfd2d..34b7cdcd 100644 --- a/ui/src/components/settings/PageImportCollections.svelte +++ b/ui/src/components/settings/PageImportCollections.svelte @@ -3,18 +3,18 @@ import ApiClient from "@/utils/ApiClient"; import CommonHelper from "@/utils/CommonHelper"; import { pageTitle } from "@/stores/app"; - import { addInfoToast, addErrorToast } from "@/stores/toasts"; + import { addErrorToast } from "@/stores/toasts"; + import { setErrors } from "@/stores/errors"; import Field from "@/components/base/Field.svelte"; - import DiffPopup from "@/components/base/DiffPopup.svelte"; import SettingsSidebar from "@/components/settings/SettingsSidebar.svelte"; + import ImportPopup from "@/components/settings/ImportPopup.svelte"; $pageTitle = "Import collections"; let fileInput; - let diffPopup; + let importPopup; let schema = ""; - let isImporting = false; let isLoadingFile = false; let newCollections = []; let oldCollections = []; @@ -141,22 +141,10 @@ reader.readAsText(file); } - function submitImport() { - isImporting = true; - - try { - const newCollections = JSON.parse(schema); - ApiClient.collections.import(newCollections); - } catch (err) { - ApiClient.errorResponseHandler(err); - } - - isImporting = false; - } - function clear() { schema = ""; fileInput.value = ""; + setErrors({}); } @@ -224,36 +212,23 @@
- Everything is up-to-date! + Your collections schema is already up-to-date!
{/if} {#if isValid && newCollections.length && hasChanges} -
-
-
Detected changes
-
- -
+
Detected changes
-
+
{#if collectionsToDelete.length} {#each collectionsToDelete as collection (collection.id)}
Deleted {collection.name} + {#if collection.id} + ({collection.id}) + {/if}
{/each} {/if} @@ -268,6 +243,9 @@ {/if} {entry.new.name} + {#if entry.new.id} + ({entry.new.id}) + {/if}
{/each} {/if} @@ -277,6 +255,9 @@
New {collection.name} + {#if collection.id} + ({collection.id}) + {/if}
{/each} {/if} @@ -284,23 +265,19 @@ {/if}
- + {#if !!schema} + + {/if}
{/if} @@ -308,7 +285,7 @@
- + clear()} />