export.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. import { ReactiveCache } from '/imports/reactiveCache';
  2. import { Exporter } from './exporter';
  3. import { Meteor } from 'meteor/meteor';
  4. /* global JsonRoutes */
  5. if (Meteor.isServer) {
  6. import { Picker } from 'meteor/communitypackages:picker';
  7. // todo XXX once we have a real API in place, move that route there
  8. // todo XXX also share the route definition between the client and the server
  9. // so that we could use something like
  10. // `ApiRoutes.path('boards/export', boardId)``
  11. // on the client instead of copy/pasting the route path manually between the
  12. // client and the server.
  13. /**
  14. * @operation exportJson
  15. * @tag Boards
  16. *
  17. * @summary This route is used to export the board to a json file format.
  18. *
  19. * @description If user is already logged-in, pass loginToken as param
  20. * "authToken": '/api/boards/:boardId/export?authToken=:token'
  21. *
  22. * See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/
  23. * for detailed explanations
  24. *
  25. * @param {string} boardId the ID of the board we are exporting
  26. * @param {string} authToken the loginToken
  27. */
  28. JsonRoutes.add('get', '/api/boards/:boardId/export', function (req, res) {
  29. const boardId = req.params.boardId;
  30. let user = null;
  31. let impersonateDone = false;
  32. let adminId = null;
  33. const loginToken = req.query.authToken;
  34. if (loginToken) {
  35. const hashToken = Accounts._hashLoginToken(loginToken);
  36. user = ReactiveCache.getUser({
  37. 'services.resume.loginTokens.hashedToken': hashToken,
  38. });
  39. adminId = user._id.toString();
  40. impersonateDone = ReactiveCache.getImpersonatedUser({ adminId: adminId });
  41. } else if (!Meteor.settings.public.sandstorm) {
  42. Authentication.checkUserId(req.userId);
  43. user = ReactiveCache.getUser({ _id: req.userId, isAdmin: true });
  44. }
  45. const exporter = new Exporter(boardId);
  46. if (exporter.canExport(user) || impersonateDone) {
  47. if (impersonateDone) {
  48. ImpersonatedUsers.insert({
  49. adminId: adminId,
  50. boardId: boardId,
  51. reason: 'exportJSON',
  52. });
  53. }
  54. JsonRoutes.sendResult(res, {
  55. code: 200,
  56. data: exporter.build(),
  57. });
  58. } else {
  59. // we could send an explicit error message, but on the other hand the only
  60. // way to get there is by hacking the UI so let's keep it raw.
  61. JsonRoutes.sendResult(res, 403);
  62. }
  63. });
  64. // todo XXX once we have a real API in place, move that route there
  65. // todo XXX also share the route definition between the client and the server
  66. // so that we could use something like
  67. // `ApiRoutes.path('boards/export', boardId)``
  68. // on the client instead of copy/pasting the route path manually between the
  69. // client and the server.
  70. /**
  71. * @operation exportJson
  72. * @tag Boards
  73. *
  74. * @summary This route is used to export a attachement to a json file format.
  75. *
  76. * @description If user is already logged-in, pass loginToken as param
  77. * "authToken": '/api/boards/:boardId/attachments/:attachmentId/export?authToken=:token'
  78. *
  79. *
  80. * @param {string} boardId the ID of the board we are exporting
  81. * @param {string} attachmentId the ID of the attachment we are exporting
  82. * @param {string} authToken the loginToken
  83. */
  84. JsonRoutes.add(
  85. 'get',
  86. '/api/boards/:boardId/attachments/:attachmentId/export',
  87. function (req, res) {
  88. const boardId = req.params.boardId;
  89. const attachmentId = req.params.attachmentId;
  90. let user = null;
  91. let impersonateDone = false;
  92. let adminId = null;
  93. const loginToken = req.query.authToken;
  94. if (loginToken) {
  95. const hashToken = Accounts._hashLoginToken(loginToken);
  96. user = ReactiveCache.getUser({
  97. 'services.resume.loginTokens.hashedToken': hashToken,
  98. });
  99. adminId = user._id.toString();
  100. impersonateDone = ReactiveCache.getImpersonatedUser({ adminId: adminId });
  101. } else if (!Meteor.settings.public.sandstorm) {
  102. Authentication.checkUserId(req.userId);
  103. user = ReactiveCache.getUser({ _id: req.userId, isAdmin: true });
  104. }
  105. const exporter = new Exporter(boardId, attachmentId);
  106. if (exporter.canExport(user) || impersonateDone) {
  107. if (impersonateDone) {
  108. ImpersonatedUsers.insert({
  109. adminId: adminId,
  110. boardId: boardId,
  111. attachmentId: attachmentId,
  112. reason: 'exportJSONattachment',
  113. });
  114. }
  115. JsonRoutes.sendResult(res, {
  116. code: 200,
  117. data: exporter.build(),
  118. });
  119. } else {
  120. // we could send an explicit error message, but on the other hand the only
  121. // way to get there is by hacking the UI so let's keep it raw.
  122. JsonRoutes.sendResult(res, 403);
  123. }
  124. },
  125. );
  126. /**
  127. * @operation exportCSV/TSV
  128. * @tag Boards
  129. *
  130. * @summary This route is used to export the board to a CSV or TSV file format.
  131. *
  132. * @description If user is already logged-in, pass loginToken as param
  133. *
  134. * See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/
  135. * for detailed explanations
  136. *
  137. * @param {string} boardId the ID of the board we are exporting
  138. * @param {string} authToken the loginToken
  139. * @param {string} delimiter delimiter to use while building export. Default is comma ','
  140. */
  141. Picker.route('/api/boards/:boardId/export/csv', function (params, req, res) {
  142. const boardId = params.boardId;
  143. let user = null;
  144. let impersonateDone = false;
  145. let adminId = null;
  146. const loginToken = params.query.authToken;
  147. if (loginToken) {
  148. const hashToken = Accounts._hashLoginToken(loginToken);
  149. user = ReactiveCache.getUser({
  150. 'services.resume.loginTokens.hashedToken': hashToken,
  151. });
  152. adminId = user._id.toString();
  153. impersonateDone = ReactiveCache.getImpersonatedUser({ adminId: adminId });
  154. } else if (!Meteor.settings.public.sandstorm) {
  155. Authentication.checkUserId(req.userId);
  156. user = ReactiveCache.getUser({
  157. _id: req.userId,
  158. isAdmin: true,
  159. });
  160. }
  161. const exporter = new Exporter(boardId);
  162. if (exporter.canExport(user) || impersonateDone) {
  163. if (impersonateDone) {
  164. let exportType = 'exportCSV';
  165. if( params.query.delimiter == "\t" ) {
  166. exportType = 'exportTSV';
  167. }
  168. ImpersonatedUsers.insert({
  169. adminId: adminId,
  170. boardId: boardId,
  171. reason: exportType,
  172. });
  173. }
  174. let userLanguage = 'en';
  175. if (user && user.profile) {
  176. userLanguage = user.profile.language
  177. }
  178. if( params.query.delimiter == "\t" ) {
  179. // TSV file
  180. res.writeHead(200, {
  181. 'Content-Type': 'text/tsv',
  182. });
  183. }
  184. else {
  185. // CSV file (comma or semicolon)
  186. res.writeHead(200, {
  187. 'Content-Type': 'text/csv; charset=utf-8',
  188. });
  189. // Adding UTF8 BOM to quick fix MS Excel issue
  190. // use Uint8Array to prevent from converting bytes to string
  191. res.write(new Uint8Array([0xEF, 0xBB, 0xBF]));
  192. }
  193. res.write(exporter.buildCsv(params.query.delimiter, userLanguage));
  194. res.end();
  195. } else {
  196. res.writeHead(403);
  197. res.end('Permission Error');
  198. }
  199. });
  200. }