export.js 7.2 KB

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