mizozobu 6 лет назад
Родитель
Сommit
2addec05ca

+ 26 - 11
src/client/js/components/Admin/Export/ExportAsZip.jsx

@@ -1,6 +1,7 @@
 import React, { Fragment } from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
+import { format } from 'date-fns';
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 import AppContainer from '../../../services/AppContainer';
@@ -15,7 +16,8 @@ class ExportPage extends React.Component {
       files: {},
     };
 
-    this.createZipFile = this.createZipFile.bind(this);
+    this.toggleCheckbox = this.toggleCheckbox.bind(this);
+    this.exportSingle = this.exportSingle.bind(this);
     this.deleteZipFile = this.deleteZipFile.bind(this);
   }
 
@@ -25,9 +27,18 @@ class ExportPage extends React.Component {
     this.setState({ files: res.files });
   }
 
-  async createZipFile() {
+  toggleCheckbox(e) {
+    const { target } = e;
+    const { name, checked } = target;
+
+    this.setState({
+      [name]: checked,
+    });
+  }
+
+  async exportSingle(collection) {
     // TODO use appContainer.apiv3.post
-    const res = await this.props.appContainer.apiPost('/v3/export/pages', {});
+    const res = await this.props.appContainer.apiPost(`/v3/export/${collection}`, {});
     // TODO toastSuccess, toastError
     this.setState((prevState) => {
       return {
@@ -52,27 +63,31 @@ class ExportPage extends React.Component {
         <h2>Export Data as Zip</h2>
         <form className="my-5">
           {Object.keys(this.state.files).map((file) => {
-            const disabled = file !== 'pages';
+            const disabled = !(file === 'pages' || file === 'revisions');
+            const stat = this.state.files[file] || {};
             return (
-              <div className="form-check" key={file}>
+              <div className="checkbox checkbox-info" key={file}>
                 <input
-                  type="radio"
+                  type="checkbox"
                   id={file}
-                  name="collection"
+                  name={file}
                   className="form-check-input"
                   value={file}
                   disabled={disabled}
-                  checked={!disabled}
-                  onChange={() => {}}
+                  checked={this.state[file]}
+                  onChange={this.toggleCheckbox}
                 />
                 <label className={`form-check-label ml-3 ${disabled ? 'text-muted' : ''}`} htmlFor={file}>
-                  {file} ({this.state.files[file] || 'not found'})
+                  {file} ({stat.name || 'not found'}) ({stat.mtime ? format(new Date(stat.mtime), 'yyyy/MM/dd HH:mm:ss') : ''})
                 </label>
+                <button type="button" className="btn btn-sm btn-primary" onClick={() => this.exportSingle(file)} disabled={disabled}>
+                  Create zip file
+                </button>
               </div>
             );
           })}
         </form>
-        <button type="button" className="btn btn-sm btn-default" onClick={this.createZipFile}>Generate</button>
+        <button type="button" className="btn btn-sm btn-default" onClick={this.exportSingle}>Generate</button>
         <a href="/_api/v3/export/pages">
           <button type="button" className="btn btn-sm btn-primary ml-2">Download</button>
         </a>

+ 7 - 4
src/server/routes/apiv3/export.js

@@ -87,14 +87,17 @@ module.exports = (crowi) => {
    *          content:
    *            application/json:
    */
-  router.post('/pages', async(req, res) => {
-    // TODO: rename path to "/:collection" and add express validator
+  router.post('/:collection', async(req, res) => {
+    // TODO: add express validator
     try {
-      const file = await exportService.exportCollection(Page);
+      const { collection } = req.params;
+      const Model = exportService.getModelFromCollectionName(collection);
+
+      const file = await exportService.exportCollection(Model);
       // TODO: use res.apiv3
       return res.status(200).json({
         ok: true,
-        collection: [Page.collection.collectionName],
+        collection: [Model.collection.collectionName],
         file: path.basename(file),
       });
     }

+ 41 - 6
src/server/service/export.js

@@ -8,27 +8,39 @@ class ExportService {
 
   constructor(crowi) {
     this.baseDir = path.join(crowi.tmpDir, 'downloads');
-    this.extension = 'json';
     this.encoding = 'utf-8';
     this.per = 100;
     this.zlibLevel = 9; // 0(min) - 9(max)
 
-    this.files = {};
-    // populate this.files
+    // { pages: Page, users: User, ... }
+    this.collectionMap = {};
+    this.initCollectionMap(crowi.models);
+
     // this.files = {
     //   configs: path.join(this.baseDir, 'configs.json'),
     //   pages: path.join(this.baseDir, 'pages.json'),
     //   pagetagrelations: path.join(this.baseDir, 'pagetagrelations.json'),
     //   ...
     // };
-    // TODO: handle 3 globalnotificationsettings collection properly
-    // see Object.values(crowi.models).forEach((m) => { return console.log(m.collection.collectionName) });
+    this.files = {};
     Object.values(crowi.models).forEach((m) => {
       const name = m.collection.collectionName;
-      this.files[name] = path.join(this.baseDir, `${name}.${this.extension}`);
+      this.files[name] = path.join(this.baseDir, `${name}.json`);
     });
   }
 
+  /**
+   * initialize collection map
+   *
+   * @memberOf ExportService
+   * @param {object} models from models/index.js
+   */
+  initCollectionMap(models) {
+    for (const model of Object.values(models)) {
+      this.collectionMap[model.collection.collectionName] = model;
+    }
+  }
+
   /**
    * dump a collection into json
    *
@@ -49,6 +61,12 @@ class ExportService {
       status[path.basename(file, '.zip')] = file;
     });
 
+    files.forEach((file) => {
+      const stats = fs.statSync(path.join(this.baseDir, file));
+      stats.name = file;
+      status[path.basename(file, '.zip')] = stats;
+    });
+
     return status;
   }
 
@@ -200,6 +218,23 @@ class ExportService {
     return zip;
   }
 
+  /**
+   * get a model from collection name
+   *
+   * @memberOf ExportService
+   * @param {object} collectionName collection name
+   * @return {object} instance of mongoose model
+   */
+  getModelFromCollectionName(collectionName) {
+    const Model = this.collectionMap[collectionName];
+
+    if (Model == null) {
+      throw new Error(`cannot find a model for collection name "${collectionName}"`);
+    }
+
+    return Model;
+  }
+
 }
 
 module.exports = ExportService;