hackmd.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. const logger = require('@alias/logger')('growi:routes:hackmd');
  2. const path = require('path');
  3. const fs = require('graceful-fs');
  4. const swig = require('swig-templates');
  5. const axios = require('axios');
  6. const ApiResponse = require('../util/apiResponse');
  7. module.exports = function(crowi, app) {
  8. const Page = crowi.models.Page;
  9. // load GROWI agent script for HackMD
  10. const manifest = require(path.join(crowi.publicDir, 'manifest.json'));
  11. const agentScriptPath = path.join(crowi.publicDir, manifest['js/agent-for-hackmd.js']);
  12. // generate swig template
  13. let agentScriptContentTpl = undefined;
  14. /**
  15. * GET /_hackmd/load-agent
  16. *
  17. * loadAgent action
  18. * This should be access from HackMD and send agent script
  19. *
  20. * @param {object} req
  21. * @param {object} res
  22. */
  23. const loadAgent = function(req, res) {
  24. // generate swig template
  25. if (agentScriptContentTpl == null) {
  26. agentScriptContentTpl = swig.compileFile(agentScriptPath);
  27. }
  28. const origin = `${req.protocol}://${req.get('host')}`;
  29. // generate definitions to replace
  30. const definitions = {
  31. origin,
  32. };
  33. // inject
  34. const script = agentScriptContentTpl(definitions);
  35. res.set('Content-Type', 'application/javascript');
  36. res.send(script);
  37. };
  38. /**
  39. * GET /_hackmd/load-styles
  40. *
  41. * loadStyles action
  42. * This should be access from HackMD and send script to insert styles
  43. *
  44. * @param {object} req
  45. * @param {object} res
  46. */
  47. const loadStyles = function(req, res) {
  48. const styleFilePath = path.join(crowi.publicDir, manifest['styles/style-hackmd.css']);
  49. const styles = fs.readFileSync(styleFilePath);
  50. res.set('Content-Type', 'text/css');
  51. res.send(styles);
  52. };
  53. const validateForApi = async function(req, res, next) {
  54. // validate process.env.HACKMD_URI
  55. const hackmdUri = process.env.HACKMD_URI;
  56. if (hackmdUri == null) {
  57. return res.json(ApiResponse.error('HackMD for GROWI has not been setup'));
  58. }
  59. // validate pageId
  60. const pageId = req.body.pageId;
  61. if (pageId == null) {
  62. return res.json(ApiResponse.error('pageId required'));
  63. }
  64. // validate page
  65. const page = await Page.findOne({ _id: pageId });
  66. if (page == null) {
  67. return res.json(ApiResponse.error(`Page('${pageId}') does not exist`));
  68. }
  69. req.page = page;
  70. next();
  71. };
  72. /**
  73. * POST /_api/hackmd.integrate
  74. *
  75. * Create page on HackMD and start to integrate
  76. * @param {object} req
  77. * @param {object} res
  78. */
  79. const integrate = async function(req, res) {
  80. const hackmdUri = process.env.HACKMD_URI_FOR_SERVER || process.env.HACKMD_URI;
  81. let page = req.page;
  82. if (page.pageIdOnHackmd != null) {
  83. try {
  84. // check if page exists in HackMD
  85. await axios.get(`${hackmdUri}/${page.pageIdOnHackmd}`);
  86. }
  87. catch (err) {
  88. // reset if pages doesn't exist
  89. page.pageIdOnHackmd = undefined;
  90. }
  91. }
  92. try {
  93. if (page.pageIdOnHackmd == null) {
  94. page = await createNewPageOnHackmdAndRegister(hackmdUri, page);
  95. }
  96. else {
  97. page = await Page.syncRevisionToHackmd(page);
  98. }
  99. const data = {
  100. pageIdOnHackmd: page.pageIdOnHackmd,
  101. revisionIdHackmdSynced: page.revisionHackmdSynced,
  102. hasDraftOnHackmd: page.hasDraftOnHackmd,
  103. };
  104. return res.json(ApiResponse.success(data));
  105. }
  106. catch (err) {
  107. logger.error(err);
  108. return res.json(ApiResponse.error('Integration with HackMD process failed'));
  109. }
  110. };
  111. async function createNewPageOnHackmdAndRegister(hackmdUri, page) {
  112. // access to HackMD and create page
  113. const response = await axios.get(`${hackmdUri}/new`);
  114. logger.debug('HackMD responds', response);
  115. // extract page id on HackMD
  116. const pagePathOnHackmd = response.request.path; // e.g. '/NC7bSRraT1CQO1TO7wjCPw'
  117. const pageIdOnHackmd = pagePathOnHackmd.substr(1); // strip the head '/'
  118. return Page.registerHackmdPage(page, pageIdOnHackmd);
  119. }
  120. /**
  121. * POST /_api/hackmd.saveOnHackmd
  122. *
  123. * receive when save operation triggered on HackMD
  124. * !! This will be invoked many time from many people !!
  125. *
  126. * @param {object} req
  127. * @param {object} res
  128. */
  129. const saveOnHackmd = async function(req, res) {
  130. const page = req.page;
  131. try {
  132. await Page.updateHasDraftOnHackmd(page, true);
  133. return res.json(ApiResponse.success());
  134. }
  135. catch (err) {
  136. logger.error(err);
  137. return res.json(ApiResponse.error('saveOnHackmd process failed'));
  138. }
  139. };
  140. return {
  141. loadAgent,
  142. loadStyles,
  143. validateForApi,
  144. integrate,
  145. saveOnHackmd,
  146. };
  147. };