migrations.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. // Anytime you change the schema of one of the collection in a non-backward
  2. // compatible way you have to write a migration in this file using the following
  3. // API:
  4. //
  5. // Migrations.add(name, migrationCallback, optionalOrder);
  6. // Note that we have extra migrations defined in `sandstorm.js` that are
  7. // exclusive to Sandstorm and shouldn’t be executed in the general case.
  8. // XXX I guess if we had ES6 modules we could
  9. // `import { isSandstorm } from sandstorm.js` and define the migration here as
  10. // well, but for now I want to avoid definied too many globals.
  11. // In the context of migration functions we don't want to validate database
  12. // mutation queries against the current (ie, latest) collection schema. Doing
  13. // that would work at the time we write the migration but would break in the
  14. // future when we'll update again the concerned collection schema.
  15. //
  16. // To prevent this bug we always have to disable the schema validation and
  17. // argument transformations. We generally use the shorthandlers defined below.
  18. const noValidate = {
  19. validate: false,
  20. filter: false,
  21. autoConvert: false,
  22. removeEmptyStrings: false,
  23. getAutoValues: false,
  24. };
  25. const noValidateMulti = { ...noValidate, multi: true };
  26. Migrations.add('board-background-color', () => {
  27. const defaultColor = '#16A085';
  28. Boards.update({
  29. background: {
  30. $exists: false,
  31. },
  32. }, {
  33. $set: {
  34. background: {
  35. type: 'color',
  36. color: defaultColor,
  37. },
  38. },
  39. }, noValidateMulti);
  40. });
  41. Migrations.add('lowercase-board-permission', () => {
  42. ['Public', 'Private'].forEach((permission) => {
  43. Boards.update(
  44. { permission },
  45. { $set: { permission: permission.toLowerCase() } },
  46. noValidateMulti
  47. );
  48. });
  49. });
  50. // Security migration: see https://github.com/wekan/wekan/issues/99
  51. Migrations.add('change-attachments-type-for-non-images', () => {
  52. const newTypeForNonImage = 'application/octet-stream';
  53. Attachments.find().forEach((file) => {
  54. if (!file.isImage()) {
  55. Attachments.update(file._id, {
  56. $set: {
  57. 'original.type': newTypeForNonImage,
  58. 'copies.attachments.type': newTypeForNonImage,
  59. },
  60. }, noValidate);
  61. }
  62. });
  63. });
  64. Migrations.add('card-covers', () => {
  65. Cards.find().forEach((card) => {
  66. const cover = Attachments.findOne({ cardId: card._id, cover: true });
  67. if (cover) {
  68. Cards.update(card._id, {$set: {coverId: cover._id}}, noValidate);
  69. }
  70. });
  71. Attachments.update({}, {$unset: {cover: ''}}, noValidateMulti);
  72. });
  73. Migrations.add('use-css-class-for-boards-colors', () => {
  74. const associationTable = {
  75. '#27AE60': 'nephritis',
  76. '#C0392B': 'pomegranate',
  77. '#2980B9': 'belize',
  78. '#8E44AD': 'wisteria',
  79. '#2C3E50': 'midnight',
  80. '#E67E22': 'pumpkin',
  81. };
  82. Boards.find().forEach((board) => {
  83. const oldBoardColor = board.background.color;
  84. const newBoardColor = associationTable[oldBoardColor];
  85. Boards.update(board._id, {
  86. $set: { color: newBoardColor },
  87. $unset: { background: '' },
  88. }, noValidate);
  89. });
  90. });
  91. Migrations.add('denormalize-star-number-per-board', () => {
  92. Boards.find().forEach((board) => {
  93. const nStars = Users.find({'profile.starredBoards': board._id}).count();
  94. Boards.update(board._id, {$set: {stars: nStars}}, noValidate);
  95. });
  96. });
  97. // We want to keep a trace of former members so we can efficiently publish their
  98. // infos in the general board publication.
  99. Migrations.add('add-member-isactive-field', () => {
  100. Boards.find({}, {fields: {members: 1}}).forEach((board) => {
  101. const allUsersWithSomeActivity = _.chain(
  102. Activities.find({ boardId: board._id }, { fields:{ userId:1 }}).fetch())
  103. .pluck('userId')
  104. .uniq()
  105. .value();
  106. const currentUsers = _.pluck(board.members, 'userId');
  107. const formerUsers = _.difference(allUsersWithSomeActivity, currentUsers);
  108. const newMemberSet = [];
  109. board.members.forEach((member) => {
  110. member.isActive = true;
  111. newMemberSet.push(member);
  112. });
  113. formerUsers.forEach((userId) => {
  114. newMemberSet.push({
  115. userId,
  116. isAdmin: false,
  117. isActive: false,
  118. });
  119. });
  120. Boards.update(board._id, {$set: {members: newMemberSet}}, noValidate);
  121. });
  122. });
  123. Migrations.add('add-sort-checklists', () => {
  124. Checklists.find().forEach((checklist, index) => {
  125. if (!checklist.hasOwnProperty('sort')) {
  126. Checklists.direct.update(
  127. checklist._id,
  128. { $set: { sort: index } },
  129. noValidate
  130. );
  131. }
  132. checklist.items.find().forEach((item, index) => {
  133. if (!item.hasOwnProperty('sort')) {
  134. Checklists.direct.update(
  135. { _id: checklist._id, 'items._id': item._id },
  136. { $set: { 'items.$.sort': index } },
  137. noValidate
  138. );
  139. }
  140. });
  141. });
  142. });
  143. Migrations.add('add-swimlanes', () => {
  144. Boards.find().forEach((board) => {
  145. const swimlane = Swimlanes.findOne({ boardId: board._id });
  146. let swimlaneId = '';
  147. if (swimlane)
  148. swimlaneId = swimlane._id;
  149. else
  150. swimlaneId = Swimlanes.direct.insert({
  151. boardId: board._id,
  152. title: 'Default',
  153. });
  154. Cards.find({ boardId: board._id }).forEach((card) => {
  155. if (!card.hasOwnProperty('swimlaneId')) {
  156. Cards.direct.update(
  157. { _id: card._id },
  158. { $set: { swimlaneId } },
  159. noValidate
  160. );
  161. }
  162. });
  163. });
  164. });
  165. Migrations.add('add-views', () => {
  166. Boards.find().forEach((board) => {
  167. if (!board.hasOwnProperty('view')) {
  168. Boards.direct.update(
  169. { _id: board._id },
  170. { $set: { view: 'board-view-swimlanes' } },
  171. noValidate
  172. );
  173. }
  174. });
  175. });
  176. Migrations.add('add-checklist-items', () => {
  177. Checklists.find().forEach((checklist) => {
  178. // Create new items
  179. _.sortBy(checklist.items, 'sort').forEach((item, index) => {
  180. ChecklistItems.direct.insert({
  181. title: checklist.title,
  182. sort: index,
  183. isFinished: item.isFinished,
  184. checklistId: checklist._id,
  185. cardId: checklist.cardId,
  186. });
  187. });
  188. // Delete old ones
  189. Checklists.direct.update({ _id: checklist._id },
  190. { $unset: { items : 1 } },
  191. noValidate
  192. );
  193. });
  194. });
  195. Migrations.add('add-profile-view', () => {
  196. Users.find().forEach((user) => {
  197. // Set default view
  198. Users.direct.update(
  199. { _id: user._id },
  200. { $set: { 'profile.boardView': 'board-view-lists' } },
  201. noValidate
  202. );
  203. });
  204. });