import.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. const loggerFactory = require('@alias/logger');
  2. const logger = loggerFactory('growi:routes:apiv3:import'); // eslint-disable-line no-unused-vars
  3. const path = require('path');
  4. const multer = require('multer');
  5. const { ObjectId } = require('mongoose').Types;
  6. const express = require('express');
  7. const router = express.Router();
  8. /**
  9. * @swagger
  10. * tags:
  11. * name: Import
  12. */
  13. module.exports = (crowi) => {
  14. const { importService } = crowi;
  15. const uploads = multer({
  16. storage: multer.diskStorage({
  17. destination: (req, file, cb) => {
  18. cb(null, importService.baseDir);
  19. },
  20. filename(req, file, cb) {
  21. // to prevent hashing the file name. files with same name will be overwritten.
  22. cb(null, file.originalname);
  23. },
  24. }),
  25. fileFilter: (req, file, cb) => {
  26. if (path.extname(file.originalname) === '.zip') {
  27. return cb(null, true);
  28. }
  29. cb(new Error('Only ".zip" is allowed'));
  30. },
  31. });
  32. /**
  33. * defined overwrite params for each collection
  34. * all imported documents are overwriten by this value
  35. * each value can be any value or a function (_value, { _document, key, schema }) { return newValue }
  36. *
  37. * @param {object} Model instance of mongoose model
  38. * @param {object} req request object
  39. * @return {object} document to be persisted
  40. */
  41. const overwriteParamsFn = async(Model, schema, req) => {
  42. const { collectionName } = Model.collection;
  43. /* eslint-disable no-case-declarations */
  44. switch (Model.collection.collectionName) {
  45. case 'pages':
  46. // TODO: use schema and req to generate overwriteParams
  47. // e.g. { creator: schema.creator === 'me' ? ObjectId(req.user._id) : importService.keepOriginal }
  48. return {
  49. status: 'published', // FIXME when importing users and user groups
  50. grant: 1, // FIXME when importing users and user groups
  51. grantedUsers: [], // FIXME when importing users and user groups
  52. grantedGroup: null, // FIXME when importing users and user groups
  53. creator: ObjectId(req.user._id), // FIXME when importing users
  54. lastUpdateUser: ObjectId(req.user._id), // FIXME when importing users
  55. liker: [], // FIXME when importing users
  56. seenUsers: [], // FIXME when importing users
  57. commentCount: 0, // FIXME when importing comments
  58. extended: {}, // FIXME when ?
  59. pageIdOnHackmd: undefined, // FIXME when importing hackmd?
  60. revisionHackmdSynced: undefined, // FIXME when importing hackmd?
  61. hasDraftOnHackmd: undefined, // FIXME when importing hackmd?
  62. };
  63. // case 'revisoins':
  64. // return {};
  65. // case 'users':
  66. // return {};
  67. // ... add more cases
  68. default:
  69. throw new Error(`cannot find a model for collection name "${collectionName}"`);
  70. }
  71. /* eslint-enable no-case-declarations */
  72. };
  73. /**
  74. * @swagger
  75. *
  76. * /import:
  77. * post:
  78. * tags: [Import]
  79. * description: import a collection from a zipped json
  80. * produces:
  81. * - application/json
  82. * responses:
  83. * 200:
  84. * description: data is successfully imported
  85. * content:
  86. * application/json:
  87. */
  88. router.post('/', async(req, res) => {
  89. // TODO: add express validator
  90. const { fileName, collections, schema } = req.body;
  91. const zipFile = importService.getFile(fileName);
  92. // unzip
  93. await importService.unzip(zipFile);
  94. // eslint-disable-next-line no-unused-vars
  95. const { meta, fileStats } = await importService.parseZipFile(zipFile);
  96. // filter fileStats
  97. const filteredFileStats = fileStats.filter(({ fileName, collectionName, size }) => { return collections.includes(collectionName) });
  98. // TODO: validate using meta data
  99. try {
  100. await Promise.all(filteredFileStats.map(async({ fileName, collectionName, size }) => {
  101. const Model = importService.getModelFromCollectionName(collectionName);
  102. const jsonFile = importService.getFile(fileName);
  103. let overwriteParams;
  104. if (overwriteParamsFn[collectionName] != null) {
  105. // await in case overwriteParamsFn[collection] is a Promise
  106. overwriteParams = await overwriteParamsFn(Model, schema[collectionName], req);
  107. }
  108. await importService.import(Model, jsonFile, overwriteParams);
  109. }));
  110. // TODO: use res.apiv3
  111. return res.send({ ok: true });
  112. }
  113. catch (err) {
  114. // TODO: use ApiV3Error
  115. logger.error(err);
  116. return res.status(500).send({ status: 'ERROR' });
  117. }
  118. });
  119. router.post('/upload', uploads.single('file'), async(req, res) => {
  120. const { file } = req;
  121. const zipFile = importService.getFile(file.filename);
  122. try {
  123. const data = await importService.parseZipFile(zipFile);
  124. // TODO: use res.apiv3
  125. return res.send({
  126. ok: true,
  127. file: file.filename,
  128. data,
  129. });
  130. }
  131. catch (err) {
  132. // TODO: use ApiV3Error
  133. logger.error(err);
  134. return res.status(500).send({ status: 'ERROR' });
  135. }
  136. });
  137. return router;
  138. };