export.js 7.0 KB

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