hackmd.js 4.0 KB

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