UNPKG

strapi-plugin-import-export-entries

Version:

Import/Export data from and to your database in just few clicks.

662 lines (523 loc) 17.6 kB
# Strapi Plugin Import Export Entries Import/Export data from and to your database in just few clicks. <p align="center"> <img src="./doc/logo.png" alt="UI" width="300"/> </p> ## Features ### Import - Import data directly from the Content Manager - Import data from JSON file or from typing raw text according to user permissions - Import contents to collection type/single type (components, dynamic zones and media are supported) - Control which roles can import data from the admin UI. ### Export - Export data directly from the Content Manager - Export JSON contents according to user permissions - Download files or copy exported data to clipboard - Filter & sort data using Content Manager filters & sorting - Export plugins content types - Control which roles can export data from the admin UI. ### Known Limitations At the moment, dynamic zones and media are not unit tested. Tests will be implemented in a near future to improve quality and development speed. ## Screenshots <p align="center"> <img src="./doc/scr-ui.png" alt="UI" width="500"/> </p> <p align="center"> <img src="./doc/scr-ui-import.png" alt="UI" width="500"/> </p> <p align="center"> <img src="./doc/scr-ui-export.png" alt="UI" width="500"/> </p> ## Table Of Content - [Requirements](#requirements) - [Feedback](#feedback) - [Contribute](#contribute) - [Installation](#installation) - [Rebuild The Admin Panel](#rebuild-the-admin-panel) - [Usage](#usage) - [Access Control](#access-control) - [Preferences](#preferences) - [Config](#config) - [Available Options](#available-options) - [`idField` Per Collection](#idfield-per-collection) - [Importing Large Files](#importing-large-files) - [Filtering & Sorting](#filtering-and-sorting) - [Services](#services) - [Content API](#content-api) - [Webhook](#webhook) - [Importing Data](#importing-data) - [JSON v2](#json-v2) - [JSON v1 (deprecated)](#json-v1-deprecated) - [Related Plugins](#related-plugins) - [Author](#author) - [Acknowledgments](#acknowledgments) ## Requirements Strapi v4 is required. ## Feedback <p align="center"> <img src="./doc/map.png" alt="Product roadmap" width="100"/> </p> Access the [publicly available product roadmap](https://strapi-import-export-entries.canny.io) and suggest features, report bugs or upvote other people suggestions. <p align="center"> <img src="./doc/discord-logo.png" alt="Discord community" width="100"/> </p> Join the [Discord Community](https://discord.gg/dcqCAFFdP8) to give your feedback 📣 and get some help from the community ⛑️ ## Contribute See the repo [Strapi Contribute](https://github.com/Baboo7/strapi-contribute#readme). ## Installation 1. Download ``` yarn add strapi-plugin-import-export-entries ``` or ``` npm i strapi-plugin-import-export-entries ``` 2. Enable the plugin Add in the file `config/plugins.js`: ```js module.exports = ({ env }) => ({ //... 'import-export-entries': { enabled: true, config: { // See `Config` section. }, }, //... }); ``` ## Rebuild The Admin Panel New releases can introduce changes to the administration panel that require a rebuild. Rebuild the admin panel with one of the following commands: ``` yarn build --clean ``` or ``` npm run build --clean ``` # Usage Once the plugin is installed and setup, the functionnalities for a collection are accessible on its content management page. <p align="center"> <img src="./doc/scr-usage.png" alt="UI" width="500"/> </p> You can also export the whole database from the home page of the plugin. <p align="center"> <img src="./doc/scr-homepage.png" alt="UI" width="500"/> </p> ## Access Control You can define which roles can import and/or export data from the admin UI. Go to `Settings > Roles (under Administration Panel) > Plugins > Import-export-entries`. <p align="center"> <img src="./doc/access-control-admin.png" alt="UI" width="500"/> </p> <p align="center"> <em>Admin view.</em> </p> <br> <p align="center"> <img src="./doc/access-control-user-homepage.png" alt="UI" width="500"/> </p> <p align="center"> <em>User view of the plugin home page that can only export data.</em> </p> <br> <p align="center"> <img src="./doc/access-control-user-content-manager.png" alt="UI" width="500"/> </p> <p align="center"> <em>User view of the content management page that can only export data.</em> </p> ## Preferences For a quick and convenient use, you can set your preferences from the home page of the plugin. <p align="center"> <img src="./doc/scr-homepage-preferences.png" alt="UI" width="500"/> </p> Once set, they will be used each time you import/export data. ## Config ### Available Options In `config/plugins.js`: ```ts module.exports = ({ env }) => ({ //... 'import-export-entries': { enabled: true, config: { /** * Public hostname of the server. * * If you use the local provider to persist medias, * `serverPublicHostname` should be set to properly export media urls. */ serverPublicHostname: 'https://yoga.com', // default: "". }, }, //... }); ``` In any collection schema `schema.json`: ```ts { "collectionName": "my-awesome-collection", "info": { "displayName": "My Awesome Collection", }, "pluginOptions": { "import-export-entries": { /** * Define the `idField` used to find an entry of the collection * when importing data. * * `idField` must match the name of an attribute. * See section _Specifying `idField` Per Collection_ for more details */ "idField": "name" } }, "attributes": { /** * In this example, `name` will be used to find an entry * of this collection when importing data. */ "name": { "type": "string", "unique": true }, } } ``` <a id='idfield-per-collection'></a> ### Specifying `idField` Per Collection Importing data will either create entries if they don't exist, or update them otherwise. When transfering data from a database to another, relying on the `id` of an entry is not reliable. For example, if you are transfering data on hospitals with this schema: ```ts interface Hospital { id: number; name: string; employees: Employee[]; patients: Patient[]; } ``` You will have something similar in your source and target databases: ```ts // data in source database { id: 1, name: "Pitié Salpêtrière", employees: [2, 3], patients: [4, 5], } // data in target database { id: 11, name: "Pitié Salpêtrière", employees: [12, 13], patients: [14, 15], } ``` _Different databases, different `id`s._ 🫠 That's why we need a way to define the field used to find an entry in a collection. This field is called an `idField`. To define the `idField` of a collection, add it in the `pluginOptions` of the collection, under the property `import-export-entries`. Using the example above, this is how we would define the `idField` of the collection `hospital`: ```ts { "collectionName": "hospitals", "info": { "displayName": "Hospital", }, "options": {}, /** * In the property `pluginOptions`, define the `idField` under the property `import-export-entries`. * * `idField` must match the name of an attribute. */ "pluginOptions": { "import-export-entries": { "idField": "name" } }, "attributes": { "name": { // 👈 `name` will be used to find a hospital when importing data. "type": "string", "unique": true }, "employees": { "type": "relation", "relation": "oneToMany", "target": "api::employees.employees" }, "patients": { "type": "relation", "relation": "oneToMany", "target": "api::patients.patients" }, } } ``` For each collection of your application, you can define a different `idField`. For example, you can set the `name` attribute as the `idField` of the collection `hospital`, and for the collection `patients` use the attribute `ssn` (I really hope you're not storing uncyphered SSNs in your database 😬). > How does the search behave if I don't define explicitly the `idField` of a collection? By default, the `idField` of a collection is the `id` attribute. We can imagine in a near future to automatically detect unique scalar fields of a collection and use them by default, but it's not the case at the moment. > How does the search behave when I specify the `idField` from the strapi admin UI? The `idField` specified from the import modal of the admin UI takes precedence over the one defined in the `pluginOptions` of the collection. This default behavior could change in the future if user feedback shows it's cumbersome to set it manually on each import. You tell me. ### Importing Large Files When importing data, imported file size may exceed the file size limit of the server. To lift up the limit, configure the [Strapi middleware `body`](https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/configurations/required/middlewares.html#body): ```js // ./config/middlewares.js module.exports = { // ... { name: 'strapi::body', config: { jsonLimit: '10mb', }, }, // ... } ``` ## Filtering and Sorting The filtering and sorting mechanism relies on Strapi filtering and sorting feature: 1. Connect to the content manager page of the model you want to export, and filter and sort the data as you want it to be exported. <p align="center"> <img src="./doc/scr-add-filter-and-sort.png" alt="UI" width="500"/> </p> 2. Open the export modal and check the option _Apply filters and sort to exported data_. <p align="center"> <img src="./doc/scr-check-apply-filters-sort-option.png" alt="UI" width="500"/> </p> 3. Click on _Fetch Data_. The exported data is filtered and sorted as expected. ## Services ```ts /***************************** * Service "import". ****************************/ /** * Get the service. */ const service = strapi.plugin("import-export-entries").service("import"); /** * Method importData. */ await service.importData( /** * Data to import. * Expected type depends on the specified format: * - csv: string * - jso: object | object[] * - json: string */ dataRaw: object | object[] | string, options: { /** * Slug of the imported model. * - "media" is a custom slug to specifically import media. See section Importing Data > Media below. */ slug: "media" | string; /** * Format of the imported data. * - csv * - jso: javascript object * - json: javascript object notation */ format: "csv" | "jso" | "json"; /** User importing data. */ user: object; } ) : Promise<{ failures: { /** Error raised. */ error: Error; /** Data for which import failed. */ data: object; }[] }>; ``` ```ts /***************************** * Service "export". ****************************/ /** * Get the service. */ const service = strapi.plugin("import-export-entries").service("export"); /** * Method exportData. */ await service.exportData( options: { /** * Slug of the model to export. * - "media" is a custom slug to specifically export media. */ slug: "media" | string; /** * Export format. * - csv * - json: javascript object notation */ exportFormat: "csv" | "json"; /** Search query used to select the entries to export. The package `qs` is used to parse the query. */ search?: string; /** Whether to apply the search query. */ applySearch?: boolean; /** Whether to export relations as id instead of plain objects. */ relationsAsId?: boolean; /** Deepness of the exported data. */ deepness?: number; } ) : Promise<string>; ``` ## Content API Data can be imported/exported through the content api. Endpoints have to be enabled in _Settings > Users & Permissions plugin > Roles_. ```ts /***************************** * Import data * * POST /api/import-export-entries/content/import ****************************/ type RouteParams = { /** Slug of the model to export. */ slug: string; /** * Data to import. * if `format` is "csv", data must be a string. * if `format` is "json", data must be an object or an array of objects. * */ data: string | Object | Object[]; /** Format of the passed data to import. */ format: 'csv' | 'json'; /** Name of the field to use as a unique identifier for entries. Default: "id" */ idField?: string; }; type RouteReturn = { /** Array of failed imports. */ failures: { /** Error raised during import. */ error: string; /** Data for which the import failed. */ data: Object; }[]; }; ``` ```ts /***************************** * Export data * * POST /api/import-export-entries/content/export/contentTypes ****************************/ type RouteParams = { /** Slug of the model to export. */ slug: string; /** Format to use to export the data. */ exportFormat: 'csv' | 'json'; /** Search query used to select the entries to export. The package `qs` is used to parse the query. Default: "" */ search?: string; /** Whether to apply the search query. Default: false */ applySearch?: boolean; /** Whether to export relations as id instead of plain objects. Default: false */ relationsAsId?: boolean; /** Deepness of the exported data. Default: 5 */ deepness?: number; }; type RouteReturn = { /** Exported data. */ data: string; }; ``` ## Webhook At the moment, the webhook is triggered only for media creation, update and deletion. It is not triggered for other data. # Importing Data ## JSON v2 JSON v2 introduces a new supported file structure. Data is flattened and dependencies only relies on `id`s (`object`s are not supported in this new version). Collection types, single types, media and components are all treated the same for ease of use. Here is an example: ```js { "version": 2, // required for the import to work properly. "data": { // Each collection has a dedicated key in the `data` property. "api::collection-name.collection-name": { // Sub keys are `id`s of imported entries and values hold the data of the entries to import. "1": { "id": 1 //... }, "2": { "id": 2 //... } }, "api::other-collection-name.other-collection-name": { "1": { "id": 1, // Relations are specified by `id`s. "collectionAbove": [1] //... }, "2": { "id": 2, "collectionAbove": [1, 2] //... } }, // Import medias. "plugin::upload.file": { "1": { "id": 1 //... }, "2": { "id": 2 //... } }, // Import components. "my.component": { "1": { "id": 1 //... }, "2": { "id": 2 //... } } } } ``` ## JSON v1 (deprecated) The expected import data structure: ## Relation: ### `object` the relation is searched in db by `id`. If an entry is found, it is updated with the provided data. Otherwise, it is created. ### `number` The relation is treated as an id. ## Media: ### `object` the media must have an `id`, `hash`, `name` or `url` property. First the media is searched by `id`, then by `hash`, then by `name` and finally imported from `url` if not found previously. When imported by `url`, the `hash` and `name` of the file are deduced from the `url` (the `hash` is also deduced because Strapi exports files with their `hash` in the `url` instead of the `name`). The `hash` and `name` are used to find the media in db. First the media is searched by `hash`, then by `name` and used if found. Otherwise, the media is uploaded to the db by downloading the file from the `url`. > ⚠️ Check the server has access to the `url`. When imported by `url`, extra data can be provided to enhance the created file: - `id` (_defaults to an auto generated `id`_) - `name` (_defaults to the `name` deduced from the url_) - `caption` (_defaults to `""`_) - `alternativeText` (_defaults to `""`_) ### Examples - `{ id: 1 }` - `{ hash: "alpaga.jpg" }` - `{ name: "alpaga.jpg" }` - `{ url: "https://www.thetimes.co.uk/imageserver/image/alpaga.jpg" }` (Deduced file `hash` is `alpaga` and deduced `name` is `imageserver-image-alpaga.jpg`) - `{ url: "http://localhost:1337/alpaga.jpg" }` (Deduced file `hash` is `alpaga` and deduced `name` is `alpaga.jpg`) - `{ id: 734, url: "http://localhost:1337/alpaga.jpg", name: "Alpacool", caption: "Alpacool In Da Hood", alternativeText: "Alpacool in da hood" }` ### `string` Same as above, except the media provided is treated as a `url`. - `"https://www.thetimes.co.uk/imageserver/image/alpaga.jpg"` (Deduced file `hash` is `alpaga` and deduced `name` is `imageserver-image-alpaga.jpg`) - `"http://localhost:1337/alpaga.jpg"` (Deduced file `hash` is `alpaga` and deduced `name` is `alpaga.jpg`) ### `number` The media is treated as an id. - `7` ## Related Plugins - [Strapi Plugin Request Id](https://github.com/Baboo7/strapi-plugin-request-id): Add a unique id to each request made to your server and track your users' activity in the logs ## Author Baboo - [@Baboo7](https://github.com/Baboo7) ## Acknowledgments This plugin (and especially this README) took strong inspiration from the [strapi-plugin-import-export-content](https://github.com/EdisonPeM/strapi-plugin-import-export-content#readme) from [EdisonPeM](https://github.com/EdisonPeM).