export.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. const loggerFactory = require('@alias/logger');
  2. const logger = loggerFactory('growi:routes:apiv3:export');
  3. const fs = require('fs');
  4. const express = require('express');
  5. const { param } = require('express-validator');
  6. const router = express.Router();
  7. /**
  8. * @swagger
  9. * tags:
  10. * name: Export
  11. */
  12. /**
  13. * @swagger
  14. *
  15. * components:
  16. * schemas:
  17. * ExportStatus:
  18. * description: ExportStatus
  19. * type: object
  20. * properties:
  21. * zipFileStats:
  22. * type: array
  23. * items:
  24. * type: object
  25. * description: the property of each file
  26. * progressList:
  27. * type: array
  28. * items:
  29. * type: object
  30. * description: progress data for each exporting collections
  31. * isExporting:
  32. * type: boolean
  33. * description: whether the current exporting job exists or not
  34. */
  35. module.exports = (crowi) => {
  36. const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
  37. const loginRequired = require('../../middlewares/login-required')(crowi);
  38. const adminRequired = require('../../middlewares/admin-required')(crowi);
  39. const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);
  40. const csrf = require('../../middlewares/csrf')(crowi);
  41. const { exportService, socketIoService } = crowi;
  42. this.adminEvent = crowi.event('admin');
  43. // setup event
  44. this.adminEvent.on('onProgressForExport', (data) => {
  45. socketIoService.getAdminSocket().emit('admin:onProgressForExport', data);
  46. });
  47. this.adminEvent.on('onStartZippingForExport', (data) => {
  48. socketIoService.getAdminSocket().emit('admin:onStartZippingForExport', data);
  49. });
  50. this.adminEvent.on('onTerminateForExport', (data) => {
  51. socketIoService.getAdminSocket().emit('admin:onTerminateForExport', data);
  52. });
  53. const validator = {
  54. deleteFile: [
  55. // https://regex101.com/r/mD4eZs/6
  56. // prevent from unexpecting attack doing delete file (path traversal attack)
  57. param('fileName').not().matches(/(\.\.\/|\.\.\\)/),
  58. ],
  59. };
  60. /**
  61. * @swagger
  62. *
  63. * /export/status:
  64. * get:
  65. * tags: [Export]
  66. * operationId: getExportStatus
  67. * summary: /export/status
  68. * description: get properties of stored zip files for export
  69. * responses:
  70. * 200:
  71. * description: the zip file statuses
  72. * content:
  73. * application/json:
  74. * schema:
  75. * properties:
  76. * status:
  77. * $ref: '#/components/schemas/ExportStatus'
  78. */
  79. router.get('/status', accessTokenParser, loginRequired, adminRequired, async(req, res) => {
  80. const status = await exportService.getStatus();
  81. // TODO: use res.apiv3
  82. return res.json({
  83. ok: true,
  84. status,
  85. });
  86. });
  87. /**
  88. * @swagger
  89. *
  90. * /export:
  91. * post:
  92. * tags: [Export]
  93. * operationId: createExport
  94. * summary: /export
  95. * description: generate zipped jsons for collections
  96. * responses:
  97. * 200:
  98. * description: a zip file is generated
  99. * content:
  100. * application/json:
  101. * schema:
  102. * properties:
  103. * status:
  104. * $ref: '#/components/schemas/ExportStatus'
  105. */
  106. router.post('/', accessTokenParser, loginRequired, adminRequired, csrf, async(req, res) => {
  107. // TODO: add express validator
  108. try {
  109. const { collections } = req.body;
  110. exportService.export(collections);
  111. // TODO: use res.apiv3
  112. return res.status(200).json({
  113. ok: true,
  114. });
  115. }
  116. catch (err) {
  117. // TODO: use ApiV3Error
  118. logger.error(err);
  119. return res.status(500).send({ status: 'ERROR' });
  120. }
  121. });
  122. /**
  123. * @swagger
  124. *
  125. * /export/{fileName}:
  126. * delete:
  127. * tags: [Export]
  128. * operationId: deleteExport
  129. * summary: /export/{fileName}
  130. * description: delete the file
  131. * parameters:
  132. * - name: fileName
  133. * in: path
  134. * description: the file name of zip file
  135. * required: true
  136. * schema:
  137. * type: string
  138. * responses:
  139. * 200:
  140. * description: the file is deleted
  141. * content:
  142. * application/json:
  143. * schema:
  144. * type: object
  145. */
  146. router.delete('/:fileName', accessTokenParser, loginRequired, adminRequired, validator.deleteFile, apiV3FormValidator, csrf, async(req, res) => {
  147. // TODO: add express validator
  148. const { fileName } = req.params;
  149. try {
  150. const zipFile = exportService.getFile(fileName);
  151. fs.unlinkSync(zipFile);
  152. // TODO: use res.apiv3
  153. return res.status(200).send({ ok: true });
  154. }
  155. catch (err) {
  156. // TODO: use ApiV3Error
  157. logger.error(err);
  158. return res.status(500).send({ ok: false });
  159. }
  160. });
  161. return router;
  162. };