mirror of
https://github.com/Baboo7/strapi-plugin-import-export-entries.git
synced 2025-09-04 00:02:40 -04:00
First commit
This commit is contained in:
commit
56f867827c
132
.gitignore
vendored
Normal file
132
.gitignore
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
.DS_Store
|
57
README.md
Normal file
57
README.md
Normal file
@ -0,0 +1,57 @@
|
||||
# Strapi Plugin Import Export
|
||||
|
||||
This plugin helps you import and export data from and to your database in just few clicks.
|
||||
|
||||
<img src="./doc/scr-ui.png" alt="UI" width="500"/>
|
||||
|
||||
## Requirements
|
||||
|
||||
Strapi v4 is required.
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
yarn add strapi-plugin-import-export-entries
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
npm i strapi-plugin-import-export-entries
|
||||
```
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
### Import
|
||||
|
||||
- Import data directly from the Content Manager
|
||||
- Read data from CSV and JSON file or from typing raw text
|
||||
- Import contents to collection type (NOT single type yet)
|
||||
|
||||
### Export
|
||||
|
||||
- Export data directly from the Content Manager
|
||||
- Export CSV and JSON contents
|
||||
- Download files or copy exported data to clipboard
|
||||
|
||||
## 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).
|
13
admin/src/api/exportProxy/ExportProxy.js
Normal file
13
admin/src/api/exportProxy/ExportProxy.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { request } from "@strapi/helper-plugin";
|
||||
|
||||
const getByContentType = async ({ slug, search, applySearch }) => {
|
||||
const data = await request(`/import-export/export/contentTypes`, {
|
||||
method: "POST",
|
||||
body: { slug, search, applySearch },
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
export default {
|
||||
getByContentType,
|
||||
};
|
1
admin/src/api/exportProxy/index.js
Normal file
1
admin/src/api/exportProxy/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from "./ExportProxy";
|
13
admin/src/api/importProxy/ImportProxy.js
Normal file
13
admin/src/api/importProxy/ImportProxy.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { request } from "@strapi/helper-plugin";
|
||||
|
||||
const importData = async ({ slug, data, format }) => {
|
||||
const resData = await request(`/import-export/import`, {
|
||||
method: "POST",
|
||||
body: { slug, data, format },
|
||||
});
|
||||
return resData;
|
||||
};
|
||||
|
||||
export default {
|
||||
importData,
|
||||
};
|
1
admin/src/api/importProxy/index.js
Normal file
1
admin/src/api/importProxy/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from "./ImportProxy";
|
31
admin/src/components/Editor/Editor.js
Normal file
31
admin/src/components/Editor/Editor.js
Normal file
@ -0,0 +1,31 @@
|
||||
import EditorLib from "@monaco-editor/react";
|
||||
import React from "react";
|
||||
|
||||
import "./style.css";
|
||||
|
||||
export const Editor = ({
|
||||
content = "",
|
||||
language = "csv",
|
||||
readOnly = false,
|
||||
onChange,
|
||||
style,
|
||||
}) => {
|
||||
return (
|
||||
<EditorLib
|
||||
className="plugin-ie-editor"
|
||||
style={style}
|
||||
height="30vh"
|
||||
theme="vs-dark"
|
||||
language={language}
|
||||
value={content}
|
||||
onChange={onChange}
|
||||
options={{
|
||||
readOnly,
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
scrollBeyondLastLine: false,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
1
admin/src/components/Editor/index.js
Normal file
1
admin/src/components/Editor/index.js
Normal file
@ -0,0 +1 @@
|
||||
export * from "./Editor";
|
4
admin/src/components/Editor/style.css
Normal file
4
admin/src/components/Editor/style.css
Normal file
@ -0,0 +1,4 @@
|
||||
.plugin-ie-editor {
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
}
|
216
admin/src/components/ExportModal/ExportModal.js
Normal file
216
admin/src/components/ExportModal/ExportModal.js
Normal file
@ -0,0 +1,216 @@
|
||||
import { Button } from "@strapi/design-system/Button";
|
||||
import { Checkbox } from "@strapi/design-system/Checkbox";
|
||||
import { EmptyStateLayout } from "@strapi/design-system/EmptyStateLayout";
|
||||
import {
|
||||
ModalLayout,
|
||||
ModalBody,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
} from "@strapi/design-system/ModalLayout";
|
||||
import { Flex } from "@strapi/design-system/Flex";
|
||||
import { Grid, GridItem } from "@strapi/design-system/Grid";
|
||||
import { Loader } from "@strapi/design-system/Loader";
|
||||
import { Portal } from "@strapi/design-system/Portal";
|
||||
import { Select, Option } from "@strapi/design-system/Select";
|
||||
import { Typography } from "@strapi/design-system/Typography";
|
||||
import IconFile from "@strapi/icons/File";
|
||||
import { pick } from "lodash";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import qs from "qs";
|
||||
|
||||
import "./style.css";
|
||||
import ExportProxy from "../../api/exportProxy";
|
||||
import { useDownloadFile } from "../../hooks/useDownloadFile";
|
||||
import { useSlug } from "../../hooks/useSlug";
|
||||
import { dataConverterConfigs, dataFormats } from "../../utils/dataConverter";
|
||||
import getTrad from "../../utils/getTrad";
|
||||
import { Editor } from "../Editor/Editor";
|
||||
import { useAlerts } from "../../hooks/useAlerts";
|
||||
|
||||
export const ExportModal = ({ onClose }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { search } = useLocation();
|
||||
const { downloadFile, withTimestamp } = useDownloadFile();
|
||||
const { slug } = useSlug();
|
||||
const { notify } = useAlerts();
|
||||
|
||||
const [optionApplyFilters, setOptionApplyFilters] = useState(false);
|
||||
const [exportFormat, setExportFormat] = useState(dataFormats.CSV);
|
||||
const [data, setData] = useState(null);
|
||||
const [dataConverted, setDataConverted] = useState("");
|
||||
const [fetchingData, setFetchingData] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
convertData();
|
||||
}, [data, exportFormat]);
|
||||
|
||||
const getData = async () => {
|
||||
const searchQry = qs.stringify(pick(qs.parse(search), ["filters", "sort"]));
|
||||
|
||||
setFetchingData(true);
|
||||
const data = await getEntries(slug, searchQry);
|
||||
setData(data);
|
||||
setFetchingData(false);
|
||||
};
|
||||
|
||||
const convertData = async () => {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const converter = dataConverterConfigs[exportFormat];
|
||||
if (!converter) {
|
||||
throw new Error(
|
||||
`File extension ${exportFormat} not supported to export data.`
|
||||
);
|
||||
}
|
||||
|
||||
const { convertData } = converter;
|
||||
setDataConverted(convertData(data));
|
||||
};
|
||||
|
||||
const writeDataToFile = async () => {
|
||||
const converter = dataConverterConfigs[exportFormat];
|
||||
if (!converter) {
|
||||
throw new Error(
|
||||
`File extension ${exportFormat} not supported to export data.`
|
||||
);
|
||||
}
|
||||
|
||||
const { fileExt, fileContentType } = converter;
|
||||
const fileName = `export_${slug}.${fileExt}`
|
||||
.replaceAll(":", "-")
|
||||
.replaceAll("--", "-");
|
||||
downloadFile(
|
||||
dataConverted,
|
||||
withTimestamp(fileName),
|
||||
`${fileContentType};charset=utf-8;`
|
||||
);
|
||||
};
|
||||
|
||||
const copyToClipboard = () => {
|
||||
navigator.clipboard.writeText(dataConverted);
|
||||
notify(
|
||||
"Copied to clipboard",
|
||||
"Your data has been copied to your clipboard successfully.",
|
||||
"success"
|
||||
);
|
||||
};
|
||||
|
||||
const clearData = () => {
|
||||
setData(null);
|
||||
};
|
||||
|
||||
const getEntries = async (slug, search) => {
|
||||
const data = await ExportProxy.getByContentType({
|
||||
slug,
|
||||
search,
|
||||
applySearch: optionApplyFilters,
|
||||
});
|
||||
return data.data;
|
||||
};
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<ModalLayout onClose={onClose} labelledBy="title">
|
||||
<ModalHeader>
|
||||
<Typography
|
||||
fontWeight="bold"
|
||||
textColor="neutral800"
|
||||
as="h2"
|
||||
id="title"
|
||||
>
|
||||
Export
|
||||
</Typography>
|
||||
</ModalHeader>
|
||||
<ModalBody className="plugin-ie-export_modal_body">
|
||||
{fetchingData && (
|
||||
<>
|
||||
<Flex justifyContent="center">
|
||||
<Loader>Fetching data...</Loader>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
{!data && !fetchingData && (
|
||||
<>
|
||||
<Flex direction="column" alignItems="start" gap="16px">
|
||||
<Typography fontWeight="bold" textColor="neutral800" as="h2">
|
||||
Options
|
||||
</Typography>
|
||||
<Checkbox
|
||||
value={optionApplyFilters}
|
||||
onValueChange={setOptionApplyFilters}
|
||||
>
|
||||
Apply filters and sort to exported data.
|
||||
</Checkbox>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
{data && !fetchingData && (
|
||||
<>
|
||||
<Grid gap={8}>
|
||||
<GridItem col={12}>
|
||||
<Select
|
||||
id="export-format"
|
||||
label="Export Format"
|
||||
required
|
||||
placeholder="Export Format"
|
||||
value={exportFormat}
|
||||
onChange={setExportFormat}
|
||||
>
|
||||
<Option value={dataFormats.CSV}>
|
||||
{dataFormats.CSV.toUpperCase()}
|
||||
</Option>
|
||||
<Option value={dataFormats.JSON}>
|
||||
{dataFormats.JSON.toUpperCase()}
|
||||
</Option>
|
||||
</Select>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
|
||||
{!!data && (
|
||||
<Editor content={dataConverted} language={exportFormat} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter
|
||||
startActions={
|
||||
<>
|
||||
{!!data && (
|
||||
<Button variant="tertiary" onClick={clearData}>
|
||||
{formatMessage({
|
||||
id: getTrad("plugin.cta.back-to-options"),
|
||||
})}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
endActions={
|
||||
<>
|
||||
{!data && (
|
||||
<Button onClick={getData}>
|
||||
{formatMessage({ id: getTrad("plugin.cta.get-data") })}
|
||||
</Button>
|
||||
)}
|
||||
{!!data && (
|
||||
<>
|
||||
<Button variant="secondary" onClick={copyToClipboard}>
|
||||
{formatMessage({
|
||||
id: getTrad("plugin.cta.copy-to-clipboard"),
|
||||
})}
|
||||
</Button>
|
||||
<Button onClick={writeDataToFile}>
|
||||
{formatMessage({ id: getTrad("plugin.cta.download-file") })}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ModalLayout>
|
||||
</Portal>
|
||||
);
|
||||
};
|
1
admin/src/components/ExportModal/index.js
Normal file
1
admin/src/components/ExportModal/index.js
Normal file
@ -0,0 +1 @@
|
||||
export * from "./ExportModal";
|
4
admin/src/components/ExportModal/style.css
Normal file
4
admin/src/components/ExportModal/style.css
Normal file
@ -0,0 +1,4 @@
|
||||
.plugin-ie-export_modal_body > *:not(:first-child) {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
188
admin/src/components/ImportModal/ImportModal.js
Normal file
188
admin/src/components/ImportModal/ImportModal.js
Normal file
@ -0,0 +1,188 @@
|
||||
import { Button } from "@strapi/design-system/Button";
|
||||
import { Flex } from "@strapi/design-system/Flex";
|
||||
import {
|
||||
ModalLayout,
|
||||
ModalBody,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
} from "@strapi/design-system/ModalLayout";
|
||||
import { Portal } from "@strapi/design-system/Portal";
|
||||
import { Typography } from "@strapi/design-system/Typography";
|
||||
import IconFile from "@strapi/icons/File";
|
||||
import React, { useState } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import "./style.css";
|
||||
import ImportProxy from "../../api/importProxy";
|
||||
import { useSlug } from "../../hooks/useSlug";
|
||||
import { dataFormats } from "../../utils/dataConverter";
|
||||
import getTrad from "../../utils/getTrad";
|
||||
import { Editor } from "../Editor/Editor";
|
||||
import { useAlerts } from "../../hooks/useAlerts";
|
||||
|
||||
export const ImportModal = ({ onClose }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { slug } = useSlug();
|
||||
const { notify } = useAlerts();
|
||||
|
||||
const [data, setData] = useState("");
|
||||
const [dataFormat, setDataFormat] = useState(dataFormats.CSV);
|
||||
const [labelClassNames, setLabelClassNames] = useState(
|
||||
"plugin-ie-import_modal_input-label"
|
||||
);
|
||||
|
||||
const onDataChanged = (data) => {
|
||||
setData(data);
|
||||
};
|
||||
|
||||
const onReadFile = (e) => {
|
||||
const file = e.target.files[0];
|
||||
readFile(file);
|
||||
};
|
||||
|
||||
const readFile = (file) => {
|
||||
if (file.type === "text/csv") {
|
||||
setDataFormat(dataFormats.CSV);
|
||||
} else if (file.type === "application/json") {
|
||||
setDataFormat(dataFormats.JSON);
|
||||
} else {
|
||||
throw new Error(`File type ${file.type} not supported.`);
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (e) => {
|
||||
const text = e.target.result;
|
||||
setData(text);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
};
|
||||
|
||||
const removeFile = () => {
|
||||
setData("");
|
||||
};
|
||||
|
||||
const uploadData = async () => {
|
||||
try {
|
||||
await ImportProxy.importData({ slug, data, format: dataFormat });
|
||||
notify(
|
||||
"Import successful",
|
||||
"Your data has been imported successfully. Refresh your page to see the latest updates.",
|
||||
"success"
|
||||
);
|
||||
} catch (err) {
|
||||
notify(
|
||||
"Import failed",
|
||||
"An error occured while importing your data",
|
||||
"danger"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleDragEnter = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setLabelClassNames(
|
||||
[
|
||||
labelClassNames,
|
||||
"plugin-ie-import_modal_input-label--dragged-over",
|
||||
].join(" ")
|
||||
);
|
||||
};
|
||||
|
||||
const handleDragLeave = () => {
|
||||
setLabelClassNames(
|
||||
labelClassNames.replaceAll(
|
||||
"plugin-ie-import_modal_input-label--dragged-over",
|
||||
""
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const handleDrop = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleDragLeave();
|
||||
const file = e.dataTransfer.files[0];
|
||||
readFile(file);
|
||||
};
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<ModalLayout onClose={onClose} labelledBy="title">
|
||||
<ModalHeader>
|
||||
<Typography
|
||||
fontWeight="bold"
|
||||
textColor="neutral800"
|
||||
as="h2"
|
||||
id="title"
|
||||
>
|
||||
Import
|
||||
</Typography>
|
||||
</ModalHeader>
|
||||
<ModalBody className="plugin-ie-import_modal_body">
|
||||
{!data && (
|
||||
<Flex>
|
||||
<label
|
||||
className={labelClassNames}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDragOver={handleDragOver}
|
||||
onDrop={handleDrop}
|
||||
>
|
||||
<span style={{ fontSize: 80 }}>
|
||||
<IconFile />
|
||||
</span>
|
||||
<Typography
|
||||
style={{ fontSize: "1rem", fontWeight: 500 }}
|
||||
textColor="neutral600"
|
||||
as="p"
|
||||
>
|
||||
Drag & drop your file into this area or browse for a file
|
||||
to upload
|
||||
</Typography>
|
||||
<input
|
||||
type="file"
|
||||
accept=".csv,.json"
|
||||
hidden=""
|
||||
onChange={onReadFile}
|
||||
/>
|
||||
</label>
|
||||
</Flex>
|
||||
)}
|
||||
{data && (
|
||||
<Editor
|
||||
content={data}
|
||||
language={dataFormat}
|
||||
onChange={onDataChanged}
|
||||
/>
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter
|
||||
startActions={
|
||||
<>
|
||||
{data && (
|
||||
<Button onClick={removeFile} variant="tertiary">
|
||||
{formatMessage({ id: getTrad("plugin.cta.remove-file") })}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
endActions={
|
||||
<>
|
||||
{data && (
|
||||
<Button onClick={uploadData}>
|
||||
{formatMessage({ id: getTrad("plugin.cta.import") })}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ModalLayout>
|
||||
</Portal>
|
||||
);
|
||||
};
|
1
admin/src/components/ImportModal/index.js
Normal file
1
admin/src/components/ImportModal/index.js
Normal file
@ -0,0 +1 @@
|
||||
export * from "./ImportModal";
|
47
admin/src/components/ImportModal/style.css
Normal file
47
admin/src/components/ImportModal/style.css
Normal file
@ -0,0 +1,47 @@
|
||||
.plugin-ie-import_modal_body > *:not(:first-child) {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.plugin-ie-import_modal_input-label {
|
||||
--hover-color: hsl(210, 100%, 50%);
|
||||
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
padding: 48px;
|
||||
border-width: 3px;
|
||||
border-color: #ddd;
|
||||
border-style: dashed;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.plugin-ie-import_modal_input-label:hover {
|
||||
border-color: var(--hover-color);
|
||||
}
|
||||
|
||||
.plugin-ie-import_modal_input-label--dragged-over {
|
||||
border-color: var(--hover-color);
|
||||
}
|
||||
|
||||
.plugin-ie-import_modal_input-label--dragged-over::after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.plugin-ie-import_modal_input-label > *:not(:first-child) {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.plugin-ie-import_modal_input-label input {
|
||||
display: none;
|
||||
}
|
26
admin/src/components/Initializer/index.js
Normal file
26
admin/src/components/Initializer/index.js
Normal file
@ -0,0 +1,26 @@
|
||||
/**
|
||||
*
|
||||
* Initializer
|
||||
*
|
||||
*/
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import pluginId from '../../pluginId';
|
||||
|
||||
const Initializer = ({ setPlugin }) => {
|
||||
const ref = useRef();
|
||||
ref.current = setPlugin;
|
||||
|
||||
useEffect(() => {
|
||||
ref.current(pluginId);
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
Initializer.propTypes = {
|
||||
setPlugin: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Initializer;
|
33
admin/src/components/Injected/Alerts/Alerts.js
Normal file
33
admin/src/components/Injected/Alerts/Alerts.js
Normal file
@ -0,0 +1,33 @@
|
||||
import { Alert } from "@strapi/design-system/Alert";
|
||||
import { Button } from "@strapi/design-system/Button";
|
||||
import { Portal } from "@strapi/design-system/Portal";
|
||||
import Download from "@strapi/icons/Download";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import "./style.css";
|
||||
import getTrad from "../../../utils/getTrad";
|
||||
import { ExportModal } from "../../ExportModal";
|
||||
import { useAlerts } from "../../../hooks/useAlerts";
|
||||
|
||||
export const Alerts = () => {
|
||||
const { alerts, removeAlert } = useAlerts();
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<div className="plugin-ie-alerts">
|
||||
{alerts?.map(({ id, title, message, variant }) => (
|
||||
<Alert
|
||||
key={id}
|
||||
closeLabel="Close"
|
||||
title={title}
|
||||
variant={variant}
|
||||
onClose={() => removeAlert(id)}
|
||||
>
|
||||
{message}
|
||||
</Alert>
|
||||
))}
|
||||
</div>
|
||||
</Portal>
|
||||
);
|
||||
};
|
1
admin/src/components/Injected/Alerts/index.js
Normal file
1
admin/src/components/Injected/Alerts/index.js
Normal file
@ -0,0 +1 @@
|
||||
export * from "./Alerts";
|
12
admin/src/components/Injected/Alerts/style.css
Normal file
12
admin/src/components/Injected/Alerts/style.css
Normal file
@ -0,0 +1,12 @@
|
||||
.plugin-ie-alerts {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
z-index: 10000;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.plugin-ie-alerts > *:not(:first-child) {
|
||||
margin-top: 16px;
|
||||
}
|
31
admin/src/components/Injected/export.js
Normal file
31
admin/src/components/Injected/export.js
Normal file
@ -0,0 +1,31 @@
|
||||
import { Button } from "@strapi/design-system/Button";
|
||||
import Download from "@strapi/icons/Download";
|
||||
import React, { useState } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import getTrad from "../../utils/getTrad";
|
||||
import { ExportModal } from "../ExportModal";
|
||||
|
||||
export const Export = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const [exportVisible, setExportVisible] = useState(false);
|
||||
|
||||
const openExportModal = () => {
|
||||
setExportVisible(true);
|
||||
};
|
||||
|
||||
const closeExportModal = () => {
|
||||
setExportVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button startIcon={<Download />} onClick={openExportModal}>
|
||||
{formatMessage({ id: getTrad("plugin.cta.export") })}
|
||||
</Button>
|
||||
|
||||
{exportVisible && <ExportModal onClose={closeExportModal} />}
|
||||
</>
|
||||
);
|
||||
};
|
31
admin/src/components/Injected/import.js
Normal file
31
admin/src/components/Injected/import.js
Normal file
@ -0,0 +1,31 @@
|
||||
import { Button } from "@strapi/design-system/Button";
|
||||
import Upload from "@strapi/icons/Upload";
|
||||
import React, { useState } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import getTrad from "../../utils/getTrad";
|
||||
import { ImportModal } from "../ImportModal";
|
||||
|
||||
export const Import = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const [importVisible, setImportVisible] = useState(false);
|
||||
|
||||
const openImportModal = () => {
|
||||
setImportVisible(true);
|
||||
};
|
||||
|
||||
const closeImportModal = () => {
|
||||
setImportVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button startIcon={<Upload />} onClick={openImportModal}>
|
||||
{formatMessage({ id: getTrad("plugin.cta.import") })}
|
||||
</Button>
|
||||
|
||||
{importVisible && <ImportModal onClose={closeImportModal} />}
|
||||
</>
|
||||
);
|
||||
};
|
40
admin/src/hooks/useAlerts.js
Normal file
40
admin/src/hooks/useAlerts.js
Normal file
@ -0,0 +1,40 @@
|
||||
import { useState, useRef } from "react";
|
||||
import { singletonHook } from "react-singleton-hook";
|
||||
|
||||
const init = { loading: true };
|
||||
|
||||
const useAlertsImpl = () => {
|
||||
const [alerts, setAlerts] = useState([]);
|
||||
const [idCount, setIdCount] = useState(0);
|
||||
const alertsRef = useRef(alerts);
|
||||
alertsRef.current = alerts;
|
||||
|
||||
const notify = (title, message, variant = "default") => {
|
||||
const alert = {
|
||||
id: idCount,
|
||||
timeout: setTimeout(() => removeAlert(idCount), 8000),
|
||||
variant,
|
||||
title,
|
||||
message,
|
||||
};
|
||||
setAlerts(alerts.concat(alert));
|
||||
setIdCount(idCount + 1);
|
||||
};
|
||||
|
||||
const removeAlert = (id) => {
|
||||
const alerts = alertsRef.current;
|
||||
const alert = alerts.find((a) => a.id === id);
|
||||
clearTimeout(alert.timeout);
|
||||
|
||||
const alertsFiltered = alerts.filter((a) => a.id !== id);
|
||||
setAlerts(alertsFiltered);
|
||||
};
|
||||
|
||||
return {
|
||||
alerts,
|
||||
notify,
|
||||
removeAlert,
|
||||
};
|
||||
};
|
||||
|
||||
export const useAlerts = singletonHook(init, useAlertsImpl);
|
24
admin/src/hooks/useDownloadFile.js
Normal file
24
admin/src/hooks/useDownloadFile.js
Normal file
@ -0,0 +1,24 @@
|
||||
export const useDownloadFile = () => {
|
||||
const downloadFile = (content, filename, contentType) => {
|
||||
var blob = new Blob([content], { type: contentType });
|
||||
var url = URL.createObjectURL(blob);
|
||||
|
||||
var link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.setAttribute("download", filename);
|
||||
link.click();
|
||||
};
|
||||
|
||||
const withTimestamp = (fileName) => {
|
||||
const ts = new Date().toISOString().replace(/\D/g, "").substring(2);
|
||||
|
||||
const name = fileName.split(".").slice(0, -1).join(".").concat(`_${ts}`);
|
||||
const extension = fileName.split(".").slice(-1);
|
||||
return [name, extension].join(".");
|
||||
};
|
||||
|
||||
return {
|
||||
downloadFile,
|
||||
withTimestamp,
|
||||
};
|
||||
};
|
17
admin/src/hooks/useSlug.js
Normal file
17
admin/src/hooks/useSlug.js
Normal file
@ -0,0 +1,17 @@
|
||||
import { last } from "lodash";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
export const useSlug = () => {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const [slug, setSlug] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
setSlug(last(pathname.split("/")));
|
||||
}, [pathname]);
|
||||
|
||||
return {
|
||||
slug,
|
||||
};
|
||||
};
|
57
admin/src/index.js
Normal file
57
admin/src/index.js
Normal file
@ -0,0 +1,57 @@
|
||||
import { prefixPluginTranslations } from "@strapi/helper-plugin";
|
||||
import pluginPkg from "../../package.json";
|
||||
import pluginId from "./pluginId";
|
||||
import Initializer from "./components/Initializer";
|
||||
import { Export } from "./components/Injected/export";
|
||||
import { Import } from "./components/Injected/import";
|
||||
import { Alerts } from "./components/Injected/Alerts";
|
||||
|
||||
const name = pluginPkg.strapi.name;
|
||||
|
||||
export default {
|
||||
register(app) {
|
||||
app.registerPlugin({
|
||||
id: pluginId,
|
||||
initializer: Initializer,
|
||||
isReady: false,
|
||||
name,
|
||||
});
|
||||
},
|
||||
|
||||
bootstrap(app) {
|
||||
app.injectContentManagerComponent("listView", "actions", {
|
||||
name: `${pluginId}-alerts`,
|
||||
Component: Alerts,
|
||||
});
|
||||
app.injectContentManagerComponent("listView", "actions", {
|
||||
name: `${pluginId}-import`,
|
||||
Component: Import,
|
||||
});
|
||||
app.injectContentManagerComponent("listView", "actions", {
|
||||
name: `${pluginId}-export`,
|
||||
Component: Export,
|
||||
});
|
||||
},
|
||||
|
||||
async registerTrads({ locales }) {
|
||||
const importedTrads = await Promise.all(
|
||||
locales.map((locale) => {
|
||||
return import(`./translations/${locale}.json`)
|
||||
.then(({ default: data }) => {
|
||||
return {
|
||||
data: prefixPluginTranslations(data, pluginId),
|
||||
locale,
|
||||
};
|
||||
})
|
||||
.catch(() => {
|
||||
return {
|
||||
data: {},
|
||||
locale,
|
||||
};
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
return Promise.resolve(importedTrads);
|
||||
},
|
||||
};
|
25
admin/src/pages/App/index.js
Normal file
25
admin/src/pages/App/index.js
Normal file
@ -0,0 +1,25 @@
|
||||
/**
|
||||
*
|
||||
* This component is the skeleton around the actual pages, and should only
|
||||
* contain code that should be seen on all pages. (e.g. navigation bar)
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import { NotFound } from '@strapi/helper-plugin';
|
||||
import pluginId from '../../pluginId';
|
||||
import HomePage from '../HomePage';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<div>
|
||||
<Switch>
|
||||
<Route path={`/plugins/${pluginId}`} component={HomePage} exact />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
20
admin/src/pages/HomePage/index.js
Normal file
20
admin/src/pages/HomePage/index.js
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
*
|
||||
* HomePage
|
||||
*
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
// import PropTypes from 'prop-types';
|
||||
import pluginId from '../../pluginId';
|
||||
|
||||
const HomePage = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>{pluginId}'s HomePage</h1>
|
||||
<p>Happy coding</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(HomePage);
|
5
admin/src/pluginId.js
Normal file
5
admin/src/pluginId.js
Normal file
@ -0,0 +1,5 @@
|
||||
const pluginPkg = require('../../package.json');
|
||||
|
||||
const pluginId = pluginPkg.name.replace(/^(@[^-,.][\w,-]+\/|strapi-)plugin-/i, '');
|
||||
|
||||
module.exports = pluginId;
|
12
admin/src/translations/en.json
Normal file
12
admin/src/translations/en.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"plugin.name": "Import Export",
|
||||
|
||||
"plugin.cta.back-to-options": "Back To Options",
|
||||
"plugin.cta.cancel": "Cancel",
|
||||
"plugin.cta.copy-to-clipboard": "Copy To Clipboard",
|
||||
"plugin.cta.download-file": "Download File",
|
||||
"plugin.cta.get-data": "Fetch Data",
|
||||
"plugin.cta.export": "Export",
|
||||
"plugin.cta.import": "Import",
|
||||
"plugin.cta.remove-file": "Remove File"
|
||||
}
|
1
admin/src/translations/fr.json
Normal file
1
admin/src/translations/fr.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
40
admin/src/utils/axiosInstance.js
Normal file
40
admin/src/utils/axiosInstance.js
Normal file
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* axios with a custom config.
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
import { auth } from '@strapi/helper-plugin';
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL: process.env.STRAPI_ADMIN_BACKEND_URL,
|
||||
});
|
||||
|
||||
instance.interceptors.request.use(
|
||||
async config => {
|
||||
config.headers = {
|
||||
Authorization: `Bearer ${auth.getToken()}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
return config;
|
||||
},
|
||||
error => {
|
||||
Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
instance.interceptors.response.use(
|
||||
response => response,
|
||||
error => {
|
||||
// whatever you want to do with the error
|
||||
if (error.response?.status === 401) {
|
||||
auth.clearAppStorage();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
|
||||
export default instance;
|
41
admin/src/utils/dataConverter.js
Normal file
41
admin/src/utils/dataConverter.js
Normal file
@ -0,0 +1,41 @@
|
||||
export const dataFormats = {
|
||||
CSV: "csv",
|
||||
JSON: "json",
|
||||
};
|
||||
|
||||
export const convertRowToArray = (row, keys) => {
|
||||
return keys.map((key) => row[key]);
|
||||
};
|
||||
|
||||
export const convertArrayToCsv = (row) => {
|
||||
return row
|
||||
.map(String)
|
||||
.map((v) => v.replaceAll('"', '""'))
|
||||
.map((v) => `"${v}"`)
|
||||
.join(",");
|
||||
};
|
||||
|
||||
export const convertToCsv = (rows) => {
|
||||
const columnTitles = Object.keys(rows[0]);
|
||||
const content = [convertArrayToCsv(columnTitles)]
|
||||
.concat(
|
||||
rows
|
||||
.map((row) => convertRowToArray(row, columnTitles))
|
||||
.map(convertArrayToCsv)
|
||||
)
|
||||
.join("\r\n");
|
||||
return content;
|
||||
};
|
||||
|
||||
export const dataConverterConfigs = {
|
||||
[dataFormats.CSV]: {
|
||||
convertData: convertToCsv,
|
||||
fileExt: "csv",
|
||||
fileContentType: "text/csv",
|
||||
},
|
||||
[dataFormats.JSON]: {
|
||||
convertData: (data) => JSON.stringify(data, null, "\t"),
|
||||
fileExt: "json",
|
||||
fileContentType: "application/json",
|
||||
},
|
||||
};
|
5
admin/src/utils/getTrad.js
Normal file
5
admin/src/utils/getTrad.js
Normal file
@ -0,0 +1,5 @@
|
||||
import pluginId from '../pluginId';
|
||||
|
||||
const getTrad = id => `${pluginId}.${id}`;
|
||||
|
||||
export default getTrad;
|
BIN
doc/scr-ui.png
Normal file
BIN
doc/scr-ui.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 279 KiB |
52
package.json
Normal file
52
package.json
Normal file
@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "strapi-plugin-import-export-entries",
|
||||
"version": "1.0.0",
|
||||
"description": "This plugin helps you import and export data from and to your database in just few clicks.",
|
||||
"strapi": {
|
||||
"name": "Import / Export",
|
||||
"description": "Import and export data from and to your database",
|
||||
"kind": "plugin",
|
||||
"displayName": "Import Export"
|
||||
},
|
||||
"dependencies": {
|
||||
"@monaco-editor/react": "4.4.5",
|
||||
"csvtojson": "2.0.10",
|
||||
"react-singleton-hook": "3.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@strapi/strapi": "^4.0.0"
|
||||
},
|
||||
"author": {
|
||||
"name": "Baboo",
|
||||
"url" : "https://github.com/Baboo7"
|
||||
},
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Baboo",
|
||||
"url" : "https://github.com/Baboo7"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"strapi",
|
||||
"plugin",
|
||||
"strapi",
|
||||
"import",
|
||||
"data",
|
||||
"export",
|
||||
"data",
|
||||
"content"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Baboo7/strapi-plugin-import-export-entries.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/Baboo7/strapi-plugin-import-export-entries/issues"
|
||||
},
|
||||
"homepage": "https://github.com/Baboo7/strapi-plugin-import-export-entries#readme",
|
||||
"license": "MIT"
|
||||
}
|
5
server/bootstrap.js
vendored
Normal file
5
server/bootstrap.js
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = ({ strapi }) => {
|
||||
// bootstrap phase
|
||||
};
|
6
server/config/index.js
Normal file
6
server/config/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
default: {},
|
||||
validator() {},
|
||||
};
|
3
server/content-types/index.js
Normal file
3
server/content-types/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {};
|
39
server/controllers/export.js
Normal file
39
server/controllers/export.js
Normal file
@ -0,0 +1,39 @@
|
||||
"use strict";
|
||||
|
||||
const qs = require("qs");
|
||||
|
||||
const exportData = async (ctx) => {
|
||||
let { slug, search, applySearch } = ctx.request.body;
|
||||
|
||||
let query = {};
|
||||
if (applySearch) {
|
||||
query = buildFilterQuery(search);
|
||||
}
|
||||
|
||||
const entries = await strapi.db.query(slug).findMany(query);
|
||||
|
||||
ctx.body = {
|
||||
data: entries,
|
||||
};
|
||||
};
|
||||
|
||||
const buildFilterQuery = (search) => {
|
||||
let { filters, sort } = qs.parse(search);
|
||||
|
||||
let where = filters;
|
||||
|
||||
const [attr, value] = sort?.split(":").map((v) => v.toLowerCase());
|
||||
let orderBy = {};
|
||||
if (attr && value) {
|
||||
orderBy[attr] = value;
|
||||
}
|
||||
|
||||
return {
|
||||
where,
|
||||
orderBy,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = ({ strapi }) => ({
|
||||
exportData,
|
||||
});
|
62
server/controllers/import.js
Normal file
62
server/controllers/import.js
Normal file
@ -0,0 +1,62 @@
|
||||
"use strict";
|
||||
|
||||
const csvtojson = require("csvtojson");
|
||||
const isEmpty = require("lodash/isEmpty");
|
||||
|
||||
const importData = async (ctx) => {
|
||||
const { slug, data: dataRaw, format } = ctx.request.body;
|
||||
|
||||
let data;
|
||||
if (format === "csv") {
|
||||
data = await csvtojson().fromString(dataRaw);
|
||||
} else if (format === "json") {
|
||||
data = JSON.parse(dataRaw);
|
||||
}
|
||||
|
||||
const processed = await Promise.all(data.map(updateOrCreateFlow(slug)));
|
||||
|
||||
const failures = processed
|
||||
.filter((p) => !p.success)
|
||||
.map((f) => ({ error: f.error, data: f.args[0] }));
|
||||
|
||||
ctx.body = {
|
||||
failures,
|
||||
};
|
||||
};
|
||||
|
||||
const updateOrCreateFlow = (slug) => async (d) => {
|
||||
const res = await catchError((d) => updateOrCreate(slug, d), d);
|
||||
return res;
|
||||
};
|
||||
|
||||
const updateOrCreate = async (slug, data) => {
|
||||
const where = {};
|
||||
if (data.id) {
|
||||
where.id = data.id;
|
||||
}
|
||||
|
||||
let entry;
|
||||
if (isEmpty(where)) {
|
||||
entry = await strapi.db.query(slug).create({
|
||||
data,
|
||||
});
|
||||
} else {
|
||||
entry = await strapi.db.query(slug).update({
|
||||
where,
|
||||
data,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const catchError = async (fn, ...args) => {
|
||||
try {
|
||||
await fn(...args);
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
return { success: false, error: err.message, args };
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ({ strapi }) => ({
|
||||
importData,
|
||||
});
|
9
server/controllers/index.js
Normal file
9
server/controllers/index.js
Normal file
@ -0,0 +1,9 @@
|
||||
"use strict";
|
||||
|
||||
const exportController = require("./export");
|
||||
const importController = require("./import");
|
||||
|
||||
module.exports = {
|
||||
export: exportController,
|
||||
import: importController,
|
||||
};
|
3
server/destroy.js
Normal file
3
server/destroy.js
Normal file
@ -0,0 +1,3 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = ({ strapi }) => {};
|
25
server/index.js
Normal file
25
server/index.js
Normal file
@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
const register = require('./register');
|
||||
const bootstrap = require('./bootstrap');
|
||||
const destroy = require('./destroy');
|
||||
const config = require('./config');
|
||||
const contentTypes = require('./content-types');
|
||||
const controllers = require('./controllers');
|
||||
const routes = require('./routes');
|
||||
const middlewares = require('./middlewares');
|
||||
const policies = require('./policies');
|
||||
const services = require('./services');
|
||||
|
||||
module.exports = {
|
||||
register,
|
||||
bootstrap,
|
||||
destroy,
|
||||
config,
|
||||
controllers,
|
||||
routes,
|
||||
services,
|
||||
contentTypes,
|
||||
policies,
|
||||
middlewares,
|
||||
};
|
3
server/middlewares/index.js
Normal file
3
server/middlewares/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {};
|
3
server/policies/index.js
Normal file
3
server/policies/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {};
|
5
server/register.js
Normal file
5
server/register.js
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = ({ strapi }) => {
|
||||
// registeration phase
|
||||
};
|
13
server/routes/export.js
Normal file
13
server/routes/export.js
Normal file
@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
type: "admin",
|
||||
routes: [
|
||||
{
|
||||
method: "POST",
|
||||
path: "/export/contentTypes",
|
||||
handler: "export.exportData",
|
||||
config: {
|
||||
policies: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
13
server/routes/import.js
Normal file
13
server/routes/import.js
Normal file
@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
type: "admin",
|
||||
routes: [
|
||||
{
|
||||
method: "POST",
|
||||
path: "/import",
|
||||
handler: "import.importData",
|
||||
config: {
|
||||
policies: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
7
server/routes/index.js
Normal file
7
server/routes/index.js
Normal file
@ -0,0 +1,7 @@
|
||||
const exportRoutes = require("./export");
|
||||
const importRoutes = require("./import");
|
||||
|
||||
module.exports = {
|
||||
export: exportRoutes,
|
||||
import: importRoutes,
|
||||
};
|
3
server/services/index.js
Normal file
3
server/services/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {};
|
3
strapi-admin.js
Normal file
3
strapi-admin.js
Normal file
@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = require('./admin/src').default;
|
3
strapi-server.js
Normal file
3
strapi-server.js
Normal file
@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = require('./server');
|
Loading…
x
Reference in New Issue
Block a user