import.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. class TrelloCreator {
  2. constructor() {
  3. // the object creation dates, indexed by Trello id (so we only parse actions once!)
  4. this.createdAt = {
  5. board: null,
  6. cards: {},
  7. lists: {},
  8. };
  9. // the labels we created, indexed by Trello id (to map when importing cards)
  10. this.labels = {};
  11. // the lists we created, indexed by Trello id (to map when importing cards)
  12. this.lists = {};
  13. }
  14. /**
  15. * must call parseActions before calling this one
  16. */
  17. createBoardAndLabels(trelloBoard, dateOfImport) {
  18. const createdAt = this.createdAt.board;
  19. const boardToCreate = {
  20. archived: trelloBoard.closed,
  21. // XXX map from Trello colors
  22. color: Boards.simpleSchema()._schema.color.allowedValues[0],
  23. createdAt,
  24. labels: [],
  25. members: [{
  26. userId: Meteor.userId(),
  27. isAdmin: true,
  28. isActive: true,
  29. }],
  30. // XXX make a more robust mapping algorithm?
  31. permission: trelloBoard.prefs.permissionLevel,
  32. slug: getSlug(trelloBoard.name) || 'board',
  33. stars: 0,
  34. title: trelloBoard.name,
  35. };
  36. trelloBoard.labels.forEach((label) => {
  37. labelToCreate = {
  38. _id: Random.id(6),
  39. color: label.color,
  40. name: label.name,
  41. };
  42. // we need to remember them by Trello ID, as this is the only ref we have when importing cards
  43. this.labels[label.id] = labelToCreate;
  44. boardToCreate.labels.push(labelToCreate);
  45. });
  46. const boardId = Boards.direct.insert(boardToCreate);
  47. // XXX add activities
  48. return boardId;
  49. }
  50. createLists(trelloLists, boardId, dateOfImport) {
  51. trelloLists.forEach((list) => {
  52. const listToCreate = {
  53. archived: list.closed,
  54. boardId,
  55. createdAt: this.createdAt.lists[list.id],
  56. title: list.name,
  57. userId: Meteor.userId(),
  58. };
  59. listToCreate._id = Lists.direct.insert(listToCreate);
  60. this.lists[list.id] = listToCreate;
  61. // XXX add activities
  62. });
  63. }
  64. createCards(trelloCards, boardId, dateOfImport) {
  65. trelloCards.forEach((card) => {
  66. const cardToCreate = {
  67. archived: card.closed,
  68. boardId,
  69. createdAt: this.createdAt.cards[card.id],
  70. dateLastActivity: dateOfImport,
  71. description: card.desc,
  72. listId: this.lists[card.idList]._id,
  73. sort: card.pos,
  74. title: card.name,
  75. // XXX use the original user?
  76. userId: Meteor.userId(),
  77. };
  78. // add labels
  79. if(card.idLabels) {
  80. cardToCreate.labelIds = card.idLabels.map((trelloId) => {
  81. return this.labels[trelloId]._id;
  82. });
  83. }
  84. Cards.direct.insert(cardToCreate);
  85. // XXX add comments
  86. // XXX add attachments
  87. // XXX add activities
  88. });
  89. }
  90. parseActions(trelloActions) {
  91. trelloActions.forEach((action) =>{
  92. switch (action.type) {
  93. case 'createBoard':
  94. this.createdAt.board = action.date;
  95. break;
  96. case 'createCard':
  97. const cardId = action.data.card.id;
  98. this.createdAt.cards[cardId] = action.date;
  99. break;
  100. case 'createList':
  101. const listId = action.data.list.id;
  102. this.createdAt.lists[listId] = action.date;
  103. break;
  104. // XXX extract comments as well
  105. default:
  106. // do nothing
  107. break;
  108. }
  109. });
  110. }
  111. }
  112. Meteor.methods({
  113. importTrelloCard(trelloCard, data) {
  114. // 1. check parameters are ok from a syntax point of view
  115. const DateString = Match.Where(function (dateAsString) {
  116. check(dateAsString, String);
  117. return moment(dateAsString, moment.ISO_8601).isValid();
  118. });
  119. try {
  120. check(trelloCard, Match.ObjectIncluding({
  121. name: String,
  122. desc: String,
  123. closed: Boolean,
  124. dateLastActivity: DateString,
  125. labels: [Match.ObjectIncluding({
  126. name: String,
  127. color: String,
  128. })],
  129. actions: [Match.ObjectIncluding({
  130. type: String,
  131. date: DateString,
  132. data: Object,
  133. })],
  134. members: [Object],
  135. }));
  136. check(data, {
  137. listId: String,
  138. sortIndex: Number,
  139. });
  140. } catch(e) {
  141. throw new Meteor.Error('error-json-schema');
  142. }
  143. // 2. check parameters are ok from a business point of view (exist & authorized)
  144. const list = Lists.findOne(data.listId);
  145. if(!list) {
  146. throw new Meteor.Error('error-list-doesNotExist');
  147. }
  148. if(Meteor.isServer) {
  149. if (!allowIsBoardMember(Meteor.userId(), Boards.findOne(list.boardId))) {
  150. throw new Meteor.Error('error-board-notAMember');
  151. }
  152. }
  153. // 3. map all fields for the card to create
  154. const dateOfImport = new Date();
  155. const cardToCreate = {
  156. archived: trelloCard.closed,
  157. boardId: list.boardId,
  158. // this is a default date, we'll fetch the actual one from the actions array
  159. createdAt: dateOfImport,
  160. dateLastActivity: dateOfImport,
  161. description: trelloCard.desc,
  162. listId: list._id,
  163. sort: data.sortIndex,
  164. title: trelloCard.name,
  165. // XXX use the original user?
  166. userId: Meteor.userId(),
  167. };
  168. // 4. find actual creation date
  169. const creationAction = trelloCard.actions.find((action) => {
  170. return action.type === 'createCard';
  171. });
  172. if(creationAction) {
  173. cardToCreate.createdAt = creationAction.date;
  174. }
  175. // 5. map labels - create missing ones
  176. trelloCard.labels.forEach((currentLabel) => {
  177. const color = currentLabel.color;
  178. const name = currentLabel.name;
  179. const existingLabel = list.board().getLabel(name, color);
  180. let labelId = undefined;
  181. if (existingLabel) {
  182. labelId = existingLabel._id;
  183. } else {
  184. let labelCreated = list.board().addLabel(name, color);
  185. // XXX currently mutations return no value so we have to fetch the label we just created
  186. // waiting on https://github.com/mquandalle/meteor-collection-mutations/issues/1 to remove...
  187. labelCreated = list.board().getLabel(name, color);
  188. labelId = labelCreated._id;
  189. }
  190. if(labelId) {
  191. if (!cardToCreate.labelIds) {
  192. cardToCreate.labelIds = [];
  193. }
  194. cardToCreate.labelIds.push(labelId);
  195. }
  196. });
  197. // 6. insert new card into list
  198. const cardId = Cards.direct.insert(cardToCreate);
  199. Activities.direct.insert({
  200. activityType: 'importCard',
  201. boardId: cardToCreate.boardId,
  202. cardId,
  203. createdAt: dateOfImport,
  204. listId: cardToCreate.listId,
  205. source: {
  206. id: trelloCard.id,
  207. system: 'Trello',
  208. url: trelloCard.url,
  209. },
  210. // we attribute the import to current user, not the one from the original card
  211. userId: Meteor.userId(),
  212. });
  213. // 7. parse actions and add comments
  214. trelloCard.actions.forEach((currentAction) => {
  215. if(currentAction.type === 'commentCard') {
  216. const commentToCreate = {
  217. boardId: list.boardId,
  218. cardId,
  219. createdAt: currentAction.date,
  220. text: currentAction.data.text,
  221. // XXX use the original comment user instead
  222. userId: Meteor.userId(),
  223. };
  224. const commentId = CardComments.direct.insert(commentToCreate);
  225. Activities.direct.insert({
  226. activityType: 'addComment',
  227. boardId: commentToCreate.boardId,
  228. cardId: commentToCreate.cardId,
  229. commentId,
  230. createdAt: commentToCreate.createdAt,
  231. userId: commentToCreate.userId,
  232. });
  233. }
  234. });
  235. return cardId;
  236. },
  237. importTrelloBoard(trelloBoard, data) {
  238. const trelloCreator = new TrelloCreator();
  239. // 1. check parameters are ok from a syntax point of view
  240. try {
  241. // XXX do proper checking
  242. check(trelloBoard, Object);
  243. check(data, Object);
  244. } catch(e) {
  245. throw new Meteor.Error('error-json-schema');
  246. }
  247. // 2. check parameters are ok from a business point of view (exist & authorized)
  248. // XXX check we are allowed
  249. // 3. create all elements
  250. const dateOfImport = new Date();
  251. trelloCreator.parseActions(trelloBoard.actions);
  252. const boardId = trelloCreator.createBoardAndLabels(trelloBoard, dateOfImport);
  253. trelloCreator.createLists(trelloBoard.lists, boardId, dateOfImport);
  254. trelloCreator.createCards(trelloBoard.cards, boardId, dateOfImport);
  255. // XXX add activities
  256. // XXX set modifiedAt or lastActivity
  257. // XXX add members
  258. return boardId;
  259. },
  260. });