migrations.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  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.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 swimlaneId = board.getDefaultSwimline()._id;
  146. Cards.find({ boardId: board._id }).forEach((card) => {
  147. if (!card.hasOwnProperty('swimlaneId')) {
  148. Cards.direct.update(
  149. { _id: card._id },
  150. { $set: { swimlaneId } },
  151. noValidate
  152. );
  153. }
  154. });
  155. });
  156. });
  157. Migrations.add('add-views', () => {
  158. Boards.find().forEach((board) => {
  159. if (!board.hasOwnProperty('view')) {
  160. Boards.direct.update(
  161. { _id: board._id },
  162. { $set: { view: 'board-view-swimlanes' } },
  163. noValidate
  164. );
  165. }
  166. });
  167. });
  168. Migrations.add('add-checklist-items', () => {
  169. Checklists.find().forEach((checklist) => {
  170. // Create new items
  171. _.sortBy(checklist.items, 'sort').forEach((item, index) => {
  172. ChecklistItems.direct.insert({
  173. title: (item.title ? item.title : 'Checklist'),
  174. sort: index,
  175. isFinished: item.isFinished,
  176. checklistId: checklist._id,
  177. cardId: checklist.cardId,
  178. });
  179. });
  180. // Delete old ones
  181. Checklists.direct.update({ _id: checklist._id },
  182. { $unset: { items : 1 } },
  183. noValidate
  184. );
  185. });
  186. });
  187. Migrations.add('add-profile-view', () => {
  188. Users.find().forEach((user) => {
  189. if (!user.hasOwnProperty('profile.boardView')) {
  190. // Set default view
  191. Users.direct.update(
  192. { _id: user._id },
  193. { $set: { 'profile.boardView': 'board-view-lists' } },
  194. noValidate
  195. );
  196. }
  197. });
  198. });
  199. Migrations.add('add-card-types', () => {
  200. Cards.find().forEach((card) => {
  201. Cards.direct.update(
  202. { _id: card._id },
  203. { $set: {
  204. type: 'cardType-card',
  205. linkedId: null } },
  206. noValidate
  207. );
  208. });
  209. });
  210. Migrations.add('add-custom-fields-to-cards', () => {
  211. Cards.update({
  212. customFields: {
  213. $exists: false,
  214. },
  215. }, {
  216. $set: {
  217. customFields:[],
  218. },
  219. }, noValidateMulti);
  220. });
  221. Migrations.add('add-requester-field', () => {
  222. Cards.update({
  223. requestedBy: {
  224. $exists: false,
  225. },
  226. }, {
  227. $set: {
  228. requestedBy:'',
  229. },
  230. }, noValidateMulti);
  231. });
  232. Migrations.add('add-assigner-field', () => {
  233. Cards.update({
  234. assignedBy: {
  235. $exists: false,
  236. },
  237. }, {
  238. $set: {
  239. assignedBy:'',
  240. },
  241. }, noValidateMulti);
  242. });
  243. Migrations.add('add-parent-field-to-cards', () => {
  244. Cards.update({
  245. parentId: {
  246. $exists: false,
  247. },
  248. }, {
  249. $set: {
  250. parentId:'',
  251. },
  252. }, noValidateMulti);
  253. });
  254. Migrations.add('add-subtasks-boards', () => {
  255. Boards.update({
  256. subtasksDefaultBoardId: {
  257. $exists: false,
  258. },
  259. }, {
  260. $set: {
  261. subtasksDefaultBoardId: null,
  262. subtasksDefaultListId: null,
  263. },
  264. }, noValidateMulti);
  265. });
  266. Migrations.add('add-subtasks-sort', () => {
  267. Boards.update({
  268. subtaskSort: {
  269. $exists: false,
  270. },
  271. }, {
  272. $set: {
  273. subtaskSort: -1,
  274. },
  275. }, noValidateMulti);
  276. });
  277. Migrations.add('add-subtasks-allowed', () => {
  278. Boards.update({
  279. allowsSubtasks: {
  280. $exists: false,
  281. },
  282. }, {
  283. $set: {
  284. allowsSubtasks: true,
  285. },
  286. }, noValidateMulti);
  287. });
  288. Migrations.add('add-subtasks-allowed', () => {
  289. Boards.update({
  290. presentParentTask: {
  291. $exists: false,
  292. },
  293. }, {
  294. $set: {
  295. presentParentTask: 'no-parent',
  296. },
  297. }, noValidateMulti);
  298. });
  299. Migrations.add('add-authenticationMethod', () => {
  300. Users.update({
  301. 'authenticationMethod': {
  302. $exists: false,
  303. },
  304. }, {
  305. $set: {
  306. 'authenticationMethod': 'password',
  307. },
  308. }, noValidateMulti);
  309. });
  310. Migrations.add('remove-tag', () => {
  311. Users.update({
  312. }, {
  313. $unset: {
  314. 'profile.tags':1,
  315. },
  316. }, noValidateMulti);
  317. });
  318. Migrations.add('remove-customFields-references-broken', () => {
  319. Cards.update({'customFields.$value': null},
  320. { $pull: {
  321. customFields: {value: null},
  322. },
  323. }, noValidateMulti);
  324. });
  325. Migrations.add('add-product-name', () => {
  326. Settings.update({
  327. productName: {
  328. $exists: false,
  329. },
  330. }, {
  331. $set: {
  332. productName:'',
  333. },
  334. }, noValidateMulti);
  335. });
  336. Migrations.add('add-hide-logo', () => {
  337. Settings.update({
  338. hideLogo: {
  339. $exists: false,
  340. },
  341. }, {
  342. $set: {
  343. hideLogo: false,
  344. },
  345. }, noValidateMulti);
  346. });
  347. Migrations.add('add-custom-html-after-body-start', () => {
  348. Settings.update({
  349. customHTMLafterBodyStart: {
  350. $exists: false,
  351. },
  352. }, {
  353. $set: {
  354. customHTMLafterBodyStart:'',
  355. },
  356. }, noValidateMulti);
  357. });
  358. Migrations.add('add-custom-html-before-body-end', () => {
  359. Settings.update({
  360. customHTMLbeforeBodyEnd: {
  361. $exists: false,
  362. },
  363. }, {
  364. $set: {
  365. customHTMLbeforeBodyEnd:'',
  366. },
  367. }, noValidateMulti);
  368. });
  369. Migrations.add('add-displayAuthenticationMethod', () => {
  370. Settings.update({
  371. displayAuthenticationMethod: {
  372. $exists: false,
  373. },
  374. }, {
  375. $set: {
  376. displayAuthenticationMethod: true,
  377. },
  378. }, noValidateMulti);
  379. });
  380. Migrations.add('add-defaultAuthenticationMethod', () => {
  381. Settings.update({
  382. defaultAuthenticationMethod: {
  383. $exists: false,
  384. },
  385. }, {
  386. $set: {
  387. defaultAuthenticationMethod: 'password',
  388. },
  389. }, noValidateMulti);
  390. });
  391. Migrations.add('add-templates', () => {
  392. Boards.update({
  393. type: {
  394. $exists: false,
  395. },
  396. }, {
  397. $set: {
  398. type: 'board',
  399. },
  400. }, noValidateMulti);
  401. Swimlanes.update({
  402. type: {
  403. $exists: false,
  404. },
  405. }, {
  406. $set: {
  407. type: 'swimlane',
  408. },
  409. }, noValidateMulti);
  410. Lists.update({
  411. type: {
  412. $exists: false,
  413. },
  414. swimlaneId: {
  415. $exists: false,
  416. },
  417. }, {
  418. $set: {
  419. type: 'list',
  420. swimlaneId: '',
  421. },
  422. }, noValidateMulti);
  423. Users.find({
  424. 'profile.templatesBoardId': {
  425. $exists: false,
  426. },
  427. }).forEach((user) => {
  428. // Create board and swimlanes
  429. Boards.insert({
  430. title: TAPi18n.__('templates'),
  431. permission: 'private',
  432. type: 'template-container',
  433. members: [
  434. {
  435. userId: user._id,
  436. isAdmin: true,
  437. isActive: true,
  438. isNoComments: false,
  439. isCommentOnly: false,
  440. },
  441. ],
  442. }, (err, boardId) => {
  443. // Insert the reference to our templates board
  444. Users.update(user._id, {$set: {'profile.templatesBoardId': boardId}});
  445. // Insert the card templates swimlane
  446. Swimlanes.insert({
  447. title: TAPi18n.__('card-templates-swimlane'),
  448. boardId,
  449. sort: 1,
  450. type: 'template-container',
  451. }, (err, swimlaneId) => {
  452. // Insert the reference to out card templates swimlane
  453. Users.update(user._id, {$set: {'profile.cardTemplatesSwimlaneId': swimlaneId}});
  454. });
  455. // Insert the list templates swimlane
  456. Swimlanes.insert({
  457. title: TAPi18n.__('list-templates-swimlane'),
  458. boardId,
  459. sort: 2,
  460. type: 'template-container',
  461. }, (err, swimlaneId) => {
  462. // Insert the reference to out list templates swimlane
  463. Users.update(user._id, {$set: {'profile.listTemplatesSwimlaneId': swimlaneId}});
  464. });
  465. // Insert the board templates swimlane
  466. Swimlanes.insert({
  467. title: TAPi18n.__('board-templates-swimlane'),
  468. boardId,
  469. sort: 3,
  470. type: 'template-container',
  471. }, (err, swimlaneId) => {
  472. // Insert the reference to out board templates swimlane
  473. Users.update(user._id, {$set: {'profile.boardTemplatesSwimlaneId': swimlaneId}});
  474. });
  475. });
  476. });
  477. });
  478. Migrations.add('fix-circular-reference', () => {
  479. Cards.find().forEach((card) => {
  480. if (card.parentId === card._id) {
  481. Cards.update(card._id, {$set: {parentId: ''}}, noValidateMulti);
  482. }
  483. });
  484. });