diff --git a/.gitignore b/.gitignore
index e2ed488..cfe9a7a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -120,8 +120,6 @@ dist
.DS_store
# build files
-/build
-/lib
/types
.adminjs
example-app/.adminjs
\ No newline at end of file
diff --git a/lib/components/ExportComponent.jsx b/lib/components/ExportComponent.jsx
new file mode 100644
index 0000000..39d191a
--- /dev/null
+++ b/lib/components/ExportComponent.jsx
@@ -0,0 +1,66 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.getExportedFileName = exports.mimeTypes = void 0;
+const tslib_1 = require("tslib");
+const react_1 = tslib_1.__importStar(require("react"));
+const adminjs_1 = require("adminjs");
+const design_system_1 = require("@adminjs/design-system");
+const file_saver_1 = require("file-saver");
+const exporter_type_1 = require("../exporter.type");
+const format_1 = tslib_1.__importDefault(require("date-fns/format"));
+exports.mimeTypes = {
+ json: 'application/json',
+ csv: 'text/csv',
+ xml: 'text/xml',
+};
+const getExportedFileName = (extension) => `export-${(0, format_1.default)(Date.now(), 'yyyy-MM-dd_HH-mm')}.${extension}`;
+exports.getExportedFileName = getExportedFileName;
+const ExportComponent = ({ resource }) => {
+ const filter = {};
+ const query = new URLSearchParams(location.search);
+ for (const entry of query.entries()) {
+ const [key, value] = entry;
+ if (key.match('filters.')) {
+ filter[key.replace('filters.', '')] = value;
+ }
+ }
+ const [isFetching, setFetching] = (0, react_1.useState)();
+ const sendNotice = (0, adminjs_1.useNotice)();
+ const exportData = async (type) => {
+ setFetching(true);
+ try {
+ const { data: { exportedData }, } = await new adminjs_1.ApiClient().resourceAction({
+ method: 'post',
+ resourceId: resource.id,
+ actionName: 'export',
+ params: {
+ type,
+ filter
+ },
+ });
+ const blob = new Blob([exportedData], { type: exports.mimeTypes[type] });
+ (0, file_saver_1.saveAs)(blob, (0, exports.getExportedFileName)(type));
+ sendNotice({ message: 'Exported successfully', type: 'success' });
+ }
+ catch (e) {
+ sendNotice({ message: e.message, type: 'error' });
+ }
+ setFetching(false);
+ };
+ if (isFetching) {
+ return ;
+ }
+ return (
+
+ Choose export format:
+
+
+ {exporter_type_1.Exporters.map(parserType => (
+ exportData(parserType)} disabled={isFetching}>
+ {parserType.toUpperCase()}
+
+ ))}
+
+ );
+};
+exports.default = ExportComponent;
diff --git a/lib/components/ImportComponent.jsx b/lib/components/ImportComponent.jsx
new file mode 100644
index 0000000..29bce3f
--- /dev/null
+++ b/lib/components/ImportComponent.jsx
@@ -0,0 +1,49 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+const tslib_1 = require("tslib");
+const react_1 = tslib_1.__importStar(require("react"));
+const adminjs_1 = require("adminjs");
+const design_system_1 = require("@adminjs/design-system");
+const ImportComponent = ({ resource }) => {
+ const [file, setFile] = (0, react_1.useState)(null);
+ const sendNotice = (0, adminjs_1.useNotice)();
+ const [isFetching, setFetching] = (0, react_1.useState)();
+ const onUpload = (uploadedFile) => {
+ var _a;
+ setFile((_a = uploadedFile === null || uploadedFile === void 0 ? void 0 : uploadedFile[0]) !== null && _a !== void 0 ? _a : null);
+ };
+ const onSubmit = async () => {
+ if (!file) {
+ return;
+ }
+ setFetching(true);
+ try {
+ const importData = new FormData();
+ importData.append('file', file, file === null || file === void 0 ? void 0 : file.name);
+ await new adminjs_1.ApiClient().resourceAction({
+ method: 'post',
+ resourceId: resource.id,
+ actionName: 'import',
+ data: importData,
+ });
+ sendNotice({ message: 'Imported successfully', type: 'success' });
+ }
+ catch (e) {
+ sendNotice({ message: e.message, type: 'error' });
+ }
+ setFetching(false);
+ };
+ if (isFetching) {
+ return ;
+ }
+ return (
+
+ {file && ( setFile(null)}/>)}
+
+
+ Upload
+
+
+ );
+};
+exports.default = ImportComponent;
diff --git a/lib/components/bundleComponents.js b/lib/components/bundleComponents.js
new file mode 100644
index 0000000..f4b0e8b
--- /dev/null
+++ b/lib/components/bundleComponents.js
@@ -0,0 +1,11 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.bundleComponents = void 0;
+const tslib_1 = require("tslib");
+const adminjs_1 = tslib_1.__importDefault(require("adminjs"));
+const bundleComponents = () => {
+ const EXPORT_COMPONENT = adminjs_1.default.bundle('../../src/components/ExportComponent');
+ const IMPORT_COMPONENT = adminjs_1.default.bundle('../../src/components/ImportComponent');
+ return { EXPORT_COMPONENT, IMPORT_COMPONENT };
+};
+exports.bundleComponents = bundleComponents;
diff --git a/lib/export.handler.js b/lib/export.handler.js
new file mode 100644
index 0000000..09356a4
--- /dev/null
+++ b/lib/export.handler.js
@@ -0,0 +1,15 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.exportHandler = void 0;
+const parsers_1 = require("./parsers");
+const utils_1 = require("./utils");
+const exportHandler = async (request, response, context) => {
+ var _a, _b;
+ const parser = parsers_1.Parsers[(_b = (_a = request.query) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : 'json'].export;
+ const records = await (0, utils_1.getRecords)(context, request);
+ const parsedData = parser(records);
+ return {
+ exportedData: parsedData,
+ };
+};
+exports.exportHandler = exportHandler;
diff --git a/lib/exporter.type.js b/lib/exporter.type.js
new file mode 100644
index 0000000..fe80003
--- /dev/null
+++ b/lib/exporter.type.js
@@ -0,0 +1,4 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.Exporters = void 0;
+exports.Exporters = ['csv', 'json', 'xml'];
diff --git a/lib/import.handler.js b/lib/import.handler.js
new file mode 100644
index 0000000..e85e5a0
--- /dev/null
+++ b/lib/import.handler.js
@@ -0,0 +1,16 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.importHandler = void 0;
+const tslib_1 = require("tslib");
+const fs_1 = tslib_1.__importDefault(require("fs"));
+const util_1 = tslib_1.__importDefault(require("util"));
+const utils_1 = require("./utils");
+const readFile = util_1.default.promisify(fs_1.default.readFile);
+const importHandler = async (request, response, context) => {
+ const file = (0, utils_1.getFileFromRequest)(request);
+ const importer = (0, utils_1.getImporterByFileName)(file.name);
+ const fileContent = await readFile(file.path);
+ await importer(fileContent.toString(), context.resource);
+ return {};
+};
+exports.importHandler = importHandler;
diff --git a/lib/importExportFeature.js b/lib/importExportFeature.js
new file mode 100644
index 0000000..61496bd
--- /dev/null
+++ b/lib/importExportFeature.js
@@ -0,0 +1,27 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+const adminjs_1 = require("adminjs");
+const bundleComponents_1 = require("./components/bundleComponents");
+const utils_1 = require("./utils");
+const export_handler_1 = require("./export.handler");
+const import_handler_1 = require("./import.handler");
+const { EXPORT_COMPONENT, IMPORT_COMPONENT } = (0, bundleComponents_1.bundleComponents)();
+const importExportFeature = () => {
+ return (0, adminjs_1.buildFeature)({
+ actions: {
+ export: {
+ handler: (0, utils_1.postActionHandler)(export_handler_1.exportHandler),
+ component: EXPORT_COMPONENT,
+ actionType: 'resource',
+ showFilter: true
+ },
+ import: {
+ handler: (0, utils_1.postActionHandler)(import_handler_1.importHandler),
+ component: IMPORT_COMPONENT,
+ actionType: 'resource',
+ showFilter: true
+ },
+ },
+ });
+};
+exports.default = importExportFeature;
diff --git a/lib/index.js b/lib/index.js
new file mode 100644
index 0000000..38b6b62
--- /dev/null
+++ b/lib/index.js
@@ -0,0 +1,11 @@
+"use strict";
+/**
+ * @module @adminjs/import-export
+ * @subcategory Features
+ * @section modules
+ */
+Object.defineProperty(exports, "__esModule", { value: true });
+const tslib_1 = require("tslib");
+const importExportFeature_1 = tslib_1.__importDefault(require("./importExportFeature"));
+tslib_1.__exportStar(require("./components/bundleComponents"), exports);
+exports.default = importExportFeature_1.default;
diff --git a/lib/modules/csv/csv.exporter.js b/lib/modules/csv/csv.exporter.js
new file mode 100644
index 0000000..5a112c1
--- /dev/null
+++ b/lib/modules/csv/csv.exporter.js
@@ -0,0 +1,8 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.csvExporter = void 0;
+const json2csv_1 = require("json2csv");
+const csvExporter = (records) => {
+ return (0, json2csv_1.parse)(records.map(r => r.params));
+};
+exports.csvExporter = csvExporter;
diff --git a/lib/modules/csv/csv.importer.js b/lib/modules/csv/csv.importer.js
new file mode 100644
index 0000000..723a562
--- /dev/null
+++ b/lib/modules/csv/csv.importer.js
@@ -0,0 +1,11 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.csvImporter = void 0;
+const tslib_1 = require("tslib");
+const csvtojson_1 = tslib_1.__importDefault(require("csvtojson"));
+const utils_1 = require("../../utils");
+const csvImporter = async (csvString, resource) => {
+ const records = await (0, csvtojson_1.default)().fromString(csvString);
+ return (0, utils_1.saveRecords)(records, resource);
+};
+exports.csvImporter = csvImporter;
diff --git a/lib/modules/json/json.exporter.js b/lib/modules/json/json.exporter.js
new file mode 100644
index 0000000..925e4cc
--- /dev/null
+++ b/lib/modules/json/json.exporter.js
@@ -0,0 +1,7 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.jsonExporter = void 0;
+const jsonExporter = (records) => {
+ return JSON.stringify(records.map(r => r.params));
+};
+exports.jsonExporter = jsonExporter;
diff --git a/lib/modules/json/json.importer.js b/lib/modules/json/json.importer.js
new file mode 100644
index 0000000..f3353b9
--- /dev/null
+++ b/lib/modules/json/json.importer.js
@@ -0,0 +1,9 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.jsonImporter = void 0;
+const utils_1 = require("../../utils");
+const jsonImporter = async (jsonString, resource) => {
+ const records = JSON.parse(jsonString);
+ return (0, utils_1.saveRecords)(records, resource);
+};
+exports.jsonImporter = jsonImporter;
diff --git a/lib/modules/xml/xml.exporter.js b/lib/modules/xml/xml.exporter.js
new file mode 100644
index 0000000..cbca1d9
--- /dev/null
+++ b/lib/modules/xml/xml.exporter.js
@@ -0,0 +1,17 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.xmlExporter = void 0;
+const tslib_1 = require("tslib");
+const xml_1 = tslib_1.__importDefault(require("xml"));
+const xmlExporter = (records) => {
+ const data = records.map(record => ({
+ record: Object.entries(record.params).map(([key, value]) => ({
+ [key]: value,
+ })),
+ }));
+ return (0, xml_1.default)({ records: data }, {
+ indent: '\t',
+ declaration: true,
+ });
+};
+exports.xmlExporter = xmlExporter;
diff --git a/lib/modules/xml/xml.importer.js b/lib/modules/xml/xml.importer.js
new file mode 100644
index 0000000..f3b6a7e
--- /dev/null
+++ b/lib/modules/xml/xml.importer.js
@@ -0,0 +1,12 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.xmlImporter = void 0;
+const tslib_1 = require("tslib");
+const xml2js_1 = tslib_1.__importDefault(require("xml2js"));
+const utils_1 = require("../../utils");
+const xmlImporter = async (xmlString, resource) => {
+ const parser = new xml2js_1.default.Parser({ explicitArray: false });
+ const { records: { record }, } = await parser.parseStringPromise(xmlString);
+ return (0, utils_1.saveRecords)(record, resource);
+};
+exports.xmlImporter = xmlImporter;
diff --git a/lib/parsers.js b/lib/parsers.js
new file mode 100644
index 0000000..064092b
--- /dev/null
+++ b/lib/parsers.js
@@ -0,0 +1,14 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.Parsers = void 0;
+const json_exporter_1 = require("./modules/json/json.exporter");
+const json_importer_1 = require("./modules/json/json.importer");
+const csv_exporter_1 = require("./modules/csv/csv.exporter");
+const xml_exporter_1 = require("./modules/xml/xml.exporter");
+const csv_importer_1 = require("./modules/csv/csv.importer");
+const xml_importer_1 = require("./modules/xml/xml.importer");
+exports.Parsers = {
+ json: { export: json_exporter_1.jsonExporter, import: json_importer_1.jsonImporter },
+ csv: { export: csv_exporter_1.csvExporter, import: csv_importer_1.csvImporter },
+ xml: { export: xml_exporter_1.xmlExporter, import: xml_importer_1.xmlImporter },
+};
diff --git a/lib/utils.js b/lib/utils.js
new file mode 100644
index 0000000..e8c7972
--- /dev/null
+++ b/lib/utils.js
@@ -0,0 +1,65 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.getRecords = exports.getFileFromRequest = exports.postActionHandler = exports.getImporterByFileName = exports.saveRecords = void 0;
+const adminjs_1 = require("adminjs");
+const csv_importer_1 = require("./modules/csv/csv.importer");
+const json_importer_1 = require("./modules/json/json.importer");
+const xml_importer_1 = require("./modules/xml/xml.importer");
+const saveRecords = async (records, resource) => {
+ return Promise.all(records.map(async (record) => {
+ try {
+ return await resource.create(record);
+ }
+ catch (e) {
+ console.error(e);
+ return e;
+ }
+ }));
+};
+exports.saveRecords = saveRecords;
+const getImporterByFileName = (fileName) => {
+ if (fileName.includes('.json')) {
+ return json_importer_1.jsonImporter;
+ }
+ if (fileName.includes('.csv')) {
+ return csv_importer_1.csvImporter;
+ }
+ if (fileName.includes('.xml')) {
+ return xml_importer_1.xmlImporter;
+ }
+ throw new Error('No parser found');
+};
+exports.getImporterByFileName = getImporterByFileName;
+const postActionHandler = (handler) => async (request, response, context) => {
+ if (request.method !== 'post') {
+ return {};
+ }
+ return handler(request, response, context);
+};
+exports.postActionHandler = postActionHandler;
+const getFileFromRequest = (request) => {
+ var _a;
+ const file = (_a = request.payload) === null || _a === void 0 ? void 0 : _a.file;
+ if (!(file === null || file === void 0 ? void 0 : file.path)) {
+ throw new adminjs_1.ValidationError({
+ file: { message: 'No file uploaded' },
+ });
+ }
+ return file;
+};
+exports.getFileFromRequest = getFileFromRequest;
+const getRecords = async (context, request) => {
+ var _a, _b, _c, _d, _e, _f;
+ const idProperty = (_b = (_a = context.resource
+ .properties()
+ .find(p => p.isId())) === null || _a === void 0 ? void 0 : _a.name) === null || _b === void 0 ? void 0 : _b.call(_a);
+ const titleProperty = (_d = (_c = context.resource.decorate().titleProperty()) === null || _c === void 0 ? void 0 : _c.name) === null || _d === void 0 ? void 0 : _d.call(_c);
+ return context.resource.find(new adminjs_1.Filter(((_e = request === null || request === void 0 ? void 0 : request.query) === null || _e === void 0 ? void 0 : _e.filter) ? JSON.stringify((_f = request === null || request === void 0 ? void 0 : request.query) === null || _f === void 0 ? void 0 : _f.filter) : {}, context.resource), {
+ limit: Number.MAX_SAFE_INTEGER,
+ sort: {
+ sortBy: idProperty !== null && idProperty !== void 0 ? idProperty : titleProperty,
+ direction: 'asc',
+ },
+ });
+};
+exports.getRecords = getRecords;
diff --git a/package.json b/package.json
index f811c95..f2e0423 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,7 @@
}
},
"private": false,
- "repository": "git@github.com/MenahemOwlytics/adminjs-import-export.git",
+ "repository": "git@github.com:MenahemOwlytics/adminjs-import-export.git",
"license": "SEE LICENSE IN LICENSE",
"scripts": {
"release": "semantic-release",
diff --git a/src/components/ExportComponent.tsx b/src/components/ExportComponent.tsx
index 9cb506a..a41ca21 100644
--- a/src/components/ExportComponent.tsx
+++ b/src/components/ExportComponent.tsx
@@ -3,8 +3,6 @@ import { ActionProps, ApiClient, useNotice } from 'adminjs';
import { Box, Button, Loader, Text } from '@adminjs/design-system';
import { saveAs } from 'file-saver';
import format from 'date-fns/format';
-import { useSearchParams } from "react-router-dom";
-
import { Exporters, ExporterType } from '../exporter.type.js';
@@ -18,12 +16,12 @@ export const getExportedFileName = (extension: string) =>
`export-${format(Date.now(), 'yyyy-MM-dd_HH-mm')}.${extension}`;
const ExportComponent: FC = ({ resource }) => {
- const filter: Record = {}
- const query = new URLSearchParams(location.search)
+ const filter: Record = {};
+ const query = new URLSearchParams(location.search);
for (const entry of query.entries()) {
- const [key, value] = entry
+ const [key, value] = entry;
if (key.match('filters.')) {
- filter[key.replace('filters.', '')] = value
+ filter[key.replace('filters.', '')] = value;
}
}
@@ -41,7 +39,7 @@ const ExportComponent: FC = ({ resource }) => {
actionName: 'export',
params: {
type,
- filter
+ filter,
},
});
diff --git a/src/export.handler.ts b/src/export.handler.ts
index 2652201..322a9e8 100644
--- a/src/export.handler.ts
+++ b/src/export.handler.ts
@@ -10,7 +10,7 @@ export const exportHandler: ActionHandler = async (
) => {
const parser = Parsers[request.query?.type ?? 'json'].export;
- const records = await getRecords(context,request);
+ const records = await getRecords(context, request);
const parsedData = parser(records);
return {
diff --git a/src/importExportFeature.ts b/src/importExportFeature.ts
index 9861315..6c9cb33 100644
--- a/src/importExportFeature.ts
+++ b/src/importExportFeature.ts
@@ -25,13 +25,13 @@ const importExportFeature = (
handler: postActionHandler(exportHandler),
component: exportComponent,
actionType: 'resource',
- showFilter: true
+ showFilter: true,
},
import: {
handler: postActionHandler(importHandler),
component: importComponent,
actionType: 'resource',
- showFilter: true
+ showFilter: true,
},
},
});
diff --git a/src/utils.ts b/src/utils.ts
index c74e777..bcc6cf2 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -75,11 +75,17 @@ export const getRecords = async (
?.name?.();
const titleProperty = context.resource.decorate().titleProperty()?.name?.();
- return context.resource.find(new Filter(request?.query?.filter ? JSON.stringify(request?.query?.filter) : {}, context.resource), {
- limit: Number.MAX_SAFE_INTEGER,
- sort: {
- sortBy: idProperty ?? titleProperty,
- direction: 'asc',
- },
- });
+ return context.resource.find(
+ new Filter(
+ request?.query?.filter ? JSON.stringify(request?.query?.filter) : {},
+ context.resource
+ ),
+ {
+ limit: Number.MAX_SAFE_INTEGER,
+ sort: {
+ sortBy: idProperty ?? titleProperty,
+ direction: 'asc',
+ },
+ }
+ );
};