migrations.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581
  1. import fs from 'fs';
  2. import path from 'path';
  3. import { TAPi18n } from '/imports/i18n';
  4. import AccountSettings from '../models/accountSettings';
  5. import TableVisibilityModeSettings from '../models/tableVisibilityModeSettings';
  6. import Actions from '../models/actions';
  7. import Activities from '../models/activities';
  8. import Announcements from '../models/announcements';
  9. import Attachments from '../models/attachments';
  10. //import AttachmentsOld from '../models/attachments_old';
  11. import Avatars from '../models/avatars';
  12. //import AvatarsOld from '../models/avatars_old';
  13. import Boards from '../models/boards';
  14. import CardComments from '../models/cardComments';
  15. import Cards from '../models/cards';
  16. import ChecklistItems from '../models/checklistItems';
  17. import Checklists from '../models/checklists';
  18. import CustomFields from '../models/customFields';
  19. import Integrations from '../models/integrations';
  20. import InvitationCodes from '../models/invitationCodes';
  21. import Lists from '../models/lists';
  22. import Rules from '../models/rules';
  23. import Settings from '../models/settings';
  24. import Swimlanes from '../models/swimlanes';
  25. import Triggers from '../models/triggers';
  26. import UnsavedEdits from '../models/unsavedEdits';
  27. import Users from '../models/users';
  28. // MIGRATIONS DISABLED - BACKWARD COMPATIBILITY APPROACH
  29. // All migrations have been disabled to prevent downtime and performance issues
  30. // with large databases. Instead, the application now uses backward compatibility
  31. // code that detects the current database structure and works with both migrated
  32. // and non-migrated data without requiring any migrations to run.
  33. //
  34. // This approach ensures:
  35. // 1. No migration downtime
  36. // 2. Immediate functionality for all database states
  37. // 3. Gradual data structure updates as users interact with the system
  38. // 4. Full backward compatibility with existing data
  39. //
  40. // Original migration API (now disabled):
  41. // Migrations.add(name, migrationCallback, optionalOrder);
  42. // Note that we have extra migrations defined in `sandstorm.js` that are
  43. // exclusive to Sandstorm and shouldn't be executed in the general case.
  44. // XXX I guess if we had ES6 modules we could
  45. // `import { isSandstorm } from sandstorm.js` and define the migration here as
  46. // well, but for now I want to avoid definied too many globals.
  47. // In the context of migration functions we don't want to validate database
  48. // mutation queries against the current (ie, latest) collection schema. Doing
  49. // that would work at the time we write the migration but would break in the
  50. // future when we'll update again the concerned collection schema.
  51. //
  52. // To prevent this bug we always have to disable the schema validation and
  53. // argument transformations. We generally use the shorthandlers defined below.
  54. const noValidate = {
  55. validate: false,
  56. filter: false,
  57. autoConvert: false,
  58. removeEmptyStrings: false,
  59. getAutoValues: false,
  60. };
  61. const noValidateMulti = { ...noValidate, multi: true };
  62. // ============================================================================
  63. // ALL MIGRATIONS DISABLED - BACKWARD COMPATIBILITY APPROACH
  64. // ============================================================================
  65. // All migrations below have been disabled to prevent downtime and performance
  66. // issues with large databases. The application now uses backward compatibility
  67. // code in the models and client code to handle both migrated and non-migrated
  68. // database structures without requiring any migrations to run.
  69. //
  70. // This ensures immediate functionality regardless of database state.
  71. // ============================================================================
  72. /*
  73. Migrations.add('board-background-color', () => {
  74. const defaultColor = '#16A085';
  75. Boards.update(
  76. {
  77. background: {
  78. $exists: false,
  79. },
  80. },
  81. {
  82. $set: {
  83. background: {
  84. type: 'color',
  85. color: defaultColor,
  86. },
  87. },
  88. },
  89. noValidateMulti,
  90. );
  91. });
  92. Migrations.add('add-cardcounterlist-allowed', () => {
  93. Boards.update(
  94. {
  95. allowsCardCounterList: {
  96. $exists: false,
  97. },
  98. },
  99. {
  100. $set: {
  101. allowsCardCounterList: true,
  102. },
  103. },
  104. noValidateMulti,
  105. );
  106. });
  107. /*
  108. Migrations.add('add-boardmemberlist-allowed', () => {
  109. Boards.update(
  110. {
  111. allowsBoardMemberList: {
  112. $exists: false,
  113. },
  114. },
  115. {
  116. $set: {
  117. allowsBoardMemberList: true,
  118. },
  119. },
  120. noValidateMulti,
  121. );
  122. });
  123. Migrations.add('lowercase-board-permission', () => {
  124. ['Public', 'Private'].forEach(permission => {
  125. Boards.update(
  126. { permission },
  127. { $set: { permission: permission.toLowerCase() } },
  128. noValidateMulti,
  129. );
  130. });
  131. });
  132. /*
  133. // Security migration: see https://github.com/wekan/wekan/issues/99
  134. Migrations.add('change-attachments-type-for-non-images', () => {
  135. const newTypeForNonImage = 'application/octet-stream';
  136. Attachments.find().forEach(file => {
  137. if (!file.isImage()) {
  138. Attachments.update(
  139. file._id,
  140. {
  141. $set: {
  142. 'original.type': newTypeForNonImage,
  143. 'copies.attachments.type': newTypeForNonImage,
  144. },
  145. },
  146. noValidate,
  147. );
  148. }
  149. });
  150. });
  151. Migrations.add('card-covers', () => {
  152. Cards.find().forEach(card => {
  153. const cover = Attachments.findOne({ cardId: card._id, cover: true });
  154. if (cover) {
  155. Cards.update(card._id, { $set: { coverId: cover._id } }, noValidate);
  156. }
  157. });
  158. Attachments.update({}, { $unset: { cover: '' } }, noValidateMulti);
  159. });
  160. Migrations.add('use-css-class-for-boards-colors', () => {
  161. const associationTable = {
  162. '#27AE60': 'nephritis',
  163. '#C0392B': 'pomegranate',
  164. '#2980B9': 'belize',
  165. '#8E44AD': 'wisteria',
  166. '#2C3E50': 'midnight',
  167. '#E67E22': 'pumpkin',
  168. '#CD5A91': 'moderatepink',
  169. '#00AECC': 'strongcyan',
  170. '#4BBF6B': 'limegreen',
  171. '#2C3E51': 'dark',
  172. '#27AE61': 'relax',
  173. '#568BA2': 'corteza',
  174. '#499BEA': 'clearblue',
  175. '#596557': 'natural',
  176. '#2A80B8': 'modern',
  177. '#2a2a2a': 'moderndark',
  178. '#222222': 'exodark',
  179. '#0A0A14': 'cleandark',
  180. '#FFFFFF': 'cleanlight',
  181. };
  182. Boards.find().forEach(board => {
  183. const oldBoardColor = board.background.color;
  184. const newBoardColor = associationTable[oldBoardColor];
  185. Boards.update(
  186. board._id,
  187. {
  188. $set: { color: newBoardColor },
  189. $unset: { background: '' },
  190. },
  191. noValidate,
  192. );
  193. });
  194. });
  195. Migrations.add('denormalize-star-number-per-board', () => {
  196. Boards.find().forEach(board => {
  197. const nStars = Users.find({ 'profile.starredBoards': board._id }).countDocuments();
  198. Boards.update(board._id, { $set: { stars: nStars } }, noValidate);
  199. });
  200. });
  201. // We want to keep a trace of former members so we can efficiently publish their
  202. // infos in the general board publication.
  203. Migrations.add('add-member-isactive-field', () => {
  204. Boards.find({}, { fields: { members: 1 } }).forEach(board => {
  205. const allUsersWithSomeActivity = _.chain(
  206. Activities.find(
  207. { boardId: board._id },
  208. { fields: { userId: 1 } },
  209. ).fetch(),
  210. )
  211. .pluck('userId')
  212. .uniq()
  213. .value();
  214. const currentUsers = _.pluck(board.members, 'userId');
  215. const formerUsers = _.difference(allUsersWithSomeActivity, currentUsers);
  216. const newMemberSet = [];
  217. board.members.forEach(member => {
  218. member.isActive = true;
  219. newMemberSet.push(member);
  220. });
  221. formerUsers.forEach(userId => {
  222. newMemberSet.push({
  223. userId,
  224. isAdmin: false,
  225. isActive: false,
  226. });
  227. });
  228. Boards.update(board._id, { $set: { members: newMemberSet } }, noValidate);
  229. });
  230. });
  231. Migrations.add('add-sort-checklists', () => {
  232. Checklists.find().forEach((checklist, index) => {
  233. if (!checklist.hasOwnProperty('sort')) {
  234. Checklists.direct.update(
  235. checklist._id,
  236. { $set: { sort: index } },
  237. noValidate,
  238. );
  239. }
  240. checklist.items.forEach((item, index) => {
  241. if (!item.hasOwnProperty('sort')) {
  242. Checklists.direct.update(
  243. { _id: checklist._id, 'items._id': item._id },
  244. { $set: { 'items.$.sort': index } },
  245. noValidate,
  246. );
  247. }
  248. });
  249. });
  250. });
  251. Migrations.add('add-swimlanes', () => {
  252. Boards.find().forEach(board => {
  253. const swimlaneId = board.getDefaultSwimline()._id;
  254. Cards.find({ boardId: board._id }).forEach(card => {
  255. if (!card.hasOwnProperty('swimlaneId')) {
  256. Cards.direct.update(
  257. { _id: card._id },
  258. { $set: { swimlaneId } },
  259. noValidate,
  260. );
  261. }
  262. });
  263. });
  264. });
  265. Migrations.add('add-views', () => {
  266. Boards.find().forEach(board => {
  267. if (!board.hasOwnProperty('view')) {
  268. Boards.direct.update(
  269. { _id: board._id },
  270. { $set: { view: 'board-view-swimlanes' } },
  271. noValidate,
  272. );
  273. }
  274. });
  275. });
  276. Migrations.add('add-checklist-items', () => {
  277. Checklists.find().forEach(checklist => {
  278. // Create new items
  279. _.sortBy(checklist.items, 'sort').forEach((item, index) => {
  280. ChecklistItems.direct.insert({
  281. title: item.title ? item.title : 'Checklist',
  282. sort: index,
  283. isFinished: item.isFinished,
  284. checklistId: checklist._id,
  285. cardId: checklist.cardId,
  286. });
  287. });
  288. // Delete old ones
  289. Checklists.direct.update(
  290. { _id: checklist._id },
  291. { $unset: { items: 1 } },
  292. noValidate,
  293. );
  294. });
  295. });
  296. Migrations.add('add-card-types', () => {
  297. Cards.find().forEach(card => {
  298. Cards.direct.update(
  299. { _id: card._id },
  300. {
  301. $set: {
  302. type: 'cardType-card',
  303. linkedId: null,
  304. },
  305. },
  306. noValidate,
  307. );
  308. });
  309. });
  310. Migrations.add('add-custom-fields-to-cards', () => {
  311. Cards.update(
  312. {
  313. customFields: {
  314. $exists: false,
  315. },
  316. },
  317. {
  318. $set: {
  319. customFields: [],
  320. },
  321. },
  322. noValidateMulti,
  323. );
  324. });
  325. Migrations.add('add-requester-field', () => {
  326. Cards.update(
  327. {
  328. requestedBy: {
  329. $exists: false,
  330. },
  331. },
  332. {
  333. $set: {
  334. requestedBy: '',
  335. },
  336. },
  337. noValidateMulti,
  338. );
  339. });
  340. Migrations.add('add-assigner-field', () => {
  341. Cards.update(
  342. {
  343. assignedBy: {
  344. $exists: false,
  345. },
  346. },
  347. {
  348. $set: {
  349. assignedBy: '',
  350. },
  351. },
  352. noValidateMulti,
  353. );
  354. });
  355. Migrations.add('add-parent-field-to-cards', () => {
  356. Cards.update(
  357. {
  358. parentId: {
  359. $exists: false,
  360. },
  361. },
  362. {
  363. $set: {
  364. parentId: '',
  365. },
  366. },
  367. noValidateMulti,
  368. );
  369. });
  370. Migrations.add('add-subtasks-boards', () => {
  371. Boards.update(
  372. {
  373. subtasksDefaultBoardId: {
  374. $exists: false,
  375. },
  376. },
  377. {
  378. $set: {
  379. subtasksDefaultBoardId: null,
  380. subtasksDefaultListId: null,
  381. },
  382. },
  383. noValidateMulti,
  384. );
  385. });
  386. Migrations.add('add-subtasks-sort', () => {
  387. Boards.update(
  388. {
  389. subtaskSort: {
  390. $exists: false,
  391. },
  392. },
  393. {
  394. $set: {
  395. subtaskSort: -1,
  396. },
  397. },
  398. noValidateMulti,
  399. );
  400. });
  401. Migrations.add('add-subtasks-allowed', () => {
  402. Boards.update(
  403. {
  404. allowsSubtasks: {
  405. $exists: false,
  406. },
  407. },
  408. {
  409. $set: {
  410. allowsSubtasks: true,
  411. },
  412. },
  413. noValidateMulti,
  414. );
  415. });
  416. Migrations.add('add-subtasks-allowed', () => {
  417. Boards.update(
  418. {
  419. presentParentTask: {
  420. $exists: false,
  421. },
  422. },
  423. {
  424. $set: {
  425. presentParentTask: 'no-parent',
  426. },
  427. },
  428. noValidateMulti,
  429. );
  430. });
  431. Migrations.add('add-authenticationMethod', () => {
  432. Users.update(
  433. {
  434. authenticationMethod: {
  435. $exists: false,
  436. },
  437. },
  438. {
  439. $set: {
  440. authenticationMethod: 'password',
  441. },
  442. },
  443. noValidateMulti,
  444. );
  445. });
  446. Migrations.add('remove-tag', () => {
  447. Users.update(
  448. {},
  449. {
  450. $unset: {
  451. 'profile.tags': 1,
  452. },
  453. },
  454. noValidateMulti,
  455. );
  456. });
  457. Migrations.add('remove-customFields-references-broken', () => {
  458. Cards.update(
  459. { 'customFields.$value': null },
  460. {
  461. $pull: {
  462. customFields: { value: null },
  463. },
  464. },
  465. noValidateMulti,
  466. );
  467. });
  468. Migrations.add('add-product-name', () => {
  469. Settings.update(
  470. {
  471. productName: {
  472. $exists: false,
  473. },
  474. },
  475. {
  476. $set: {
  477. productName: '',
  478. },
  479. },
  480. noValidateMulti,
  481. );
  482. });
  483. Migrations.add('add-hide-logo', () => {
  484. Settings.update(
  485. {
  486. hideLogo: {
  487. $exists: false,
  488. },
  489. },
  490. {
  491. $set: {
  492. hideLogo: false,
  493. },
  494. },
  495. noValidateMulti,
  496. );
  497. });
  498. Migrations.add('add-hide-card-counter-list', () => {
  499. Settings.update(
  500. {
  501. hideCardCounterList: {
  502. $exists: false,
  503. },
  504. },
  505. {
  506. $set: {
  507. hideCardCounterList: false,
  508. },
  509. },
  510. noValidateMulti,
  511. );
  512. });
  513. Migrations.add('add-hide-board-member-list', () => {
  514. Settings.update(
  515. {
  516. hideBoardMemberList: {
  517. $exists: false,
  518. },
  519. },
  520. {
  521. $set: {
  522. hideBoardMemberList: false,
  523. },
  524. },
  525. noValidateMulti,
  526. );
  527. });
  528. Migrations.add('add-displayAuthenticationMethod', () => {
  529. Settings.update(
  530. {
  531. displayAuthenticationMethod: {
  532. $exists: false,
  533. },
  534. },
  535. {
  536. $set: {
  537. displayAuthenticationMethod: true,
  538. },
  539. },
  540. noValidateMulti,
  541. );
  542. });
  543. Migrations.add('add-defaultAuthenticationMethod', () => {
  544. Settings.update(
  545. {
  546. defaultAuthenticationMethod: {
  547. $exists: false,
  548. },
  549. },
  550. {
  551. $set: {
  552. defaultAuthenticationMethod: 'password',
  553. },
  554. },
  555. noValidateMulti,
  556. );
  557. });
  558. Migrations.add('add-templates', () => {
  559. Boards.update(
  560. {
  561. type: {
  562. $exists: false,
  563. },
  564. },
  565. {
  566. $set: {
  567. type: 'board',
  568. },
  569. },
  570. noValidateMulti,
  571. );
  572. Swimlanes.update(
  573. {
  574. type: {
  575. $exists: false,
  576. },
  577. },
  578. {
  579. $set: {
  580. type: 'swimlane',
  581. },
  582. },
  583. noValidateMulti,
  584. );
  585. Lists.update(
  586. {
  587. type: {
  588. $exists: false,
  589. },
  590. swimlaneId: {
  591. $exists: false,
  592. },
  593. },
  594. {
  595. $set: {
  596. type: 'list',
  597. swimlaneId: '',
  598. },
  599. },
  600. noValidateMulti,
  601. );
  602. Users.find({
  603. 'profile.templatesBoardId': {
  604. $exists: false,
  605. },
  606. }).forEach(user => {
  607. // Create board and swimlanes
  608. Boards.insert(
  609. {
  610. title: TAPi18n.__('templates'),
  611. permission: 'private',
  612. type: 'template-container',
  613. members: [
  614. {
  615. userId: user._id,
  616. isAdmin: true,
  617. isActive: true,
  618. isNoComments: false,
  619. isCommentOnly: false,
  620. },
  621. ],
  622. },
  623. (err, boardId) => {
  624. // Insert the reference to our templates board
  625. Users.update(user._id, {
  626. $set: { 'profile.templatesBoardId': boardId },
  627. });
  628. // Insert the card templates swimlane
  629. Swimlanes.insert(
  630. {
  631. title: TAPi18n.__('card-templates-swimlane'),
  632. boardId,
  633. sort: 1,
  634. type: 'template-container',
  635. },
  636. (err, swimlaneId) => {
  637. // Insert the reference to out card templates swimlane
  638. Users.update(user._id, {
  639. $set: { 'profile.cardTemplatesSwimlaneId': swimlaneId },
  640. });
  641. },
  642. );
  643. // Insert the list templates swimlane
  644. Swimlanes.insert(
  645. {
  646. title: TAPi18n.__('list-templates-swimlane'),
  647. boardId,
  648. sort: 2,
  649. type: 'template-container',
  650. },
  651. (err, swimlaneId) => {
  652. // Insert the reference to out list templates swimlane
  653. Users.update(user._id, {
  654. $set: { 'profile.listTemplatesSwimlaneId': swimlaneId },
  655. });
  656. },
  657. );
  658. // Insert the board templates swimlane
  659. Swimlanes.insert(
  660. {
  661. title: TAPi18n.__('board-templates-swimlane'),
  662. boardId,
  663. sort: 3,
  664. type: 'template-container',
  665. },
  666. (err, swimlaneId) => {
  667. // Insert the reference to out board templates swimlane
  668. Users.update(user._id, {
  669. $set: { 'profile.boardTemplatesSwimlaneId': swimlaneId },
  670. });
  671. },
  672. );
  673. },
  674. );
  675. });
  676. });
  677. Migrations.add('fix-circular-reference_', () => {
  678. Cards.find().forEach(card => {
  679. if (card.parentId === card._id) {
  680. Cards.update(card._id, { $set: { parentId: '' } }, noValidateMulti);
  681. }
  682. });
  683. });
  684. Migrations.add('mutate-boardIds-in-customfields', () => {
  685. CustomFields.find().forEach(cf => {
  686. CustomFields.update(
  687. cf,
  688. {
  689. $set: {
  690. boardIds: [cf.boardId],
  691. },
  692. $unset: {
  693. boardId: '',
  694. },
  695. },
  696. noValidateMulti,
  697. );
  698. });
  699. });
  700. const modifiedAtTables = [
  701. AccountSettings,
  702. TableVisibilityModeSettings,
  703. Actions,
  704. Activities,
  705. Announcements,
  706. Boards,
  707. CardComments,
  708. Cards,
  709. ChecklistItems,
  710. Checklists,
  711. CustomFields,
  712. Integrations,
  713. InvitationCodes,
  714. Lists,
  715. Rules,
  716. Settings,
  717. Swimlanes,
  718. Triggers,
  719. UnsavedEdits,
  720. Users,
  721. ];
  722. Migrations.add('add-missing-created-and-modified', () => {
  723. Promise.all(
  724. modifiedAtTables.map(db =>
  725. db
  726. .rawCollection()
  727. .updateMany(
  728. { modifiedAt: { $exists: false } },
  729. { $set: { modifiedAt: new Date() } },
  730. { multi: true },
  731. )
  732. .then(() =>
  733. db
  734. .rawCollection()
  735. .updateMany(
  736. { createdAt: { $exists: false } },
  737. { $set: { createdAt: new Date() } },
  738. { multi: true },
  739. ),
  740. ),
  741. ),
  742. )
  743. .then(() => {
  744. // eslint-disable-next-line no-console
  745. console.info('Successfully added createdAt and updatedAt to all tables');
  746. })
  747. .catch(e => {
  748. // eslint-disable-next-line no-console
  749. console.error(e);
  750. });
  751. });
  752. Migrations.add('fix-incorrect-dates', () => {
  753. const tables = [
  754. AccountSettings,
  755. TableVisibilityModeSettings,
  756. Actions,
  757. Activities,
  758. Announcements,
  759. Boards,
  760. CardComments,
  761. Cards,
  762. ChecklistItems,
  763. Checklists,
  764. CustomFields,
  765. Integrations,
  766. InvitationCodes,
  767. Lists,
  768. Rules,
  769. Settings,
  770. Swimlanes,
  771. Triggers,
  772. UnsavedEdits,
  773. ];
  774. // Dates were previously created with Date.now() which is a number, not a date
  775. tables.forEach(t =>
  776. t
  777. .rawCollection()
  778. .find({ $or: [{ createdAt: { $type: 1 } }, { updatedAt: { $type: 1 } }] })
  779. .forEach(({ _id, createdAt, updatedAt }) => {
  780. t.rawCollection().updateMany(
  781. { _id },
  782. {
  783. $set: {
  784. createdAt: new Date(createdAt),
  785. updatedAt: new Date(updatedAt),
  786. },
  787. },
  788. );
  789. }),
  790. );
  791. });
  792. Migrations.add('add-assignee', () => {
  793. Cards.update(
  794. {
  795. assignees: {
  796. $exists: false,
  797. },
  798. },
  799. {
  800. $set: {
  801. assignees: [],
  802. },
  803. },
  804. noValidateMulti,
  805. );
  806. });
  807. Migrations.add('add-profile-showDesktopDragHandles', () => {
  808. Users.update(
  809. {
  810. 'profile.showDesktopDragHandles': {
  811. $exists: false,
  812. },
  813. },
  814. {
  815. $set: {
  816. 'profile.showDesktopDragHandles': false,
  817. },
  818. },
  819. noValidateMulti,
  820. );
  821. });
  822. Migrations.add('add-profile-hiddenMinicardLabelText', () => {
  823. Users.update(
  824. {
  825. 'profile.hiddenMinicardLabelText': {
  826. $exists: false,
  827. },
  828. },
  829. {
  830. $set: {
  831. 'profile.hiddenMinicardLabelText': false,
  832. },
  833. },
  834. noValidateMulti,
  835. );
  836. });
  837. Migrations.add('add-receiveddate-allowed', () => {
  838. Boards.update(
  839. {
  840. allowsReceivedDate: {
  841. $exists: false,
  842. },
  843. },
  844. {
  845. $set: {
  846. allowsReceivedDate: true,
  847. },
  848. },
  849. noValidateMulti,
  850. );
  851. });
  852. Migrations.add('add-startdate-allowed', () => {
  853. Boards.update(
  854. {
  855. allowsStartDate: {
  856. $exists: false,
  857. },
  858. },
  859. {
  860. $set: {
  861. allowsStartDate: true,
  862. },
  863. },
  864. noValidateMulti,
  865. );
  866. });
  867. Migrations.add('add-duedate-allowed', () => {
  868. Boards.update(
  869. {
  870. allowsDueDate: {
  871. $exists: false,
  872. },
  873. },
  874. {
  875. $set: {
  876. allowsDueDate: true,
  877. },
  878. },
  879. noValidateMulti,
  880. );
  881. });
  882. Migrations.add('add-enddate-allowed', () => {
  883. Boards.update(
  884. {
  885. allowsEndDate: {
  886. $exists: false,
  887. },
  888. },
  889. {
  890. $set: {
  891. allowsEndDate: true,
  892. },
  893. },
  894. noValidateMulti,
  895. );
  896. });
  897. Migrations.add('add-members-allowed', () => {
  898. Boards.update(
  899. {
  900. allowsMembers: {
  901. $exists: false,
  902. },
  903. },
  904. {
  905. $set: {
  906. allowsMembers: true,
  907. },
  908. },
  909. noValidateMulti,
  910. );
  911. });
  912. Migrations.add('add-assignee-allowed', () => {
  913. Boards.update(
  914. {
  915. allowsAssignee: {
  916. $exists: false,
  917. },
  918. },
  919. {
  920. $set: {
  921. allowsAssignee: true,
  922. },
  923. },
  924. noValidateMulti,
  925. );
  926. });
  927. Migrations.add('add-labels-allowed', () => {
  928. Boards.update(
  929. {
  930. allowsLabels: {
  931. $exists: false,
  932. },
  933. },
  934. {
  935. $set: {
  936. allowsLabels: true,
  937. },
  938. },
  939. noValidateMulti,
  940. );
  941. });
  942. Migrations.add('add-checklists-allowed', () => {
  943. Boards.update(
  944. {
  945. allowsChecklists: {
  946. $exists: false,
  947. },
  948. },
  949. {
  950. $set: {
  951. allowsChecklists: true,
  952. },
  953. },
  954. noValidateMulti,
  955. );
  956. });
  957. Migrations.add('add-attachments-allowed', () => {
  958. Boards.update(
  959. {
  960. allowsAttachments: {
  961. $exists: false,
  962. },
  963. },
  964. {
  965. $set: {
  966. allowsAttachments: true,
  967. },
  968. },
  969. noValidateMulti,
  970. );
  971. });
  972. Migrations.add('add-comments-allowed', () => {
  973. Boards.update(
  974. {
  975. allowsComments: {
  976. $exists: false,
  977. },
  978. },
  979. {
  980. $set: {
  981. allowsComments: true,
  982. },
  983. },
  984. noValidateMulti,
  985. );
  986. });
  987. Migrations.add('add-assigned-by-allowed', () => {
  988. Boards.update(
  989. {
  990. allowsAssignedBy: {
  991. $exists: false,
  992. },
  993. },
  994. {
  995. $set: {
  996. allowsAssignedBy: true,
  997. },
  998. },
  999. noValidateMulti,
  1000. );
  1001. });
  1002. Migrations.add('add-requested-by-allowed', () => {
  1003. Boards.update(
  1004. {
  1005. allowsRequestedBy: {
  1006. $exists: false,
  1007. },
  1008. },
  1009. {
  1010. $set: {
  1011. allowsRequestedBy: true,
  1012. },
  1013. },
  1014. noValidateMulti,
  1015. );
  1016. });
  1017. Migrations.add('add-activities-allowed', () => {
  1018. Boards.update(
  1019. {
  1020. allowsActivities: {
  1021. $exists: false,
  1022. },
  1023. },
  1024. {
  1025. $set: {
  1026. allowsActivities: true,
  1027. },
  1028. },
  1029. noValidateMulti,
  1030. );
  1031. });
  1032. Migrations.add('add-description-title-allowed', () => {
  1033. Boards.update(
  1034. {
  1035. allowsDescriptionTitle: {
  1036. $exists: false,
  1037. },
  1038. },
  1039. {
  1040. $set: {
  1041. allowsDescriptionTitle: true,
  1042. },
  1043. },
  1044. noValidateMulti,
  1045. );
  1046. });
  1047. Migrations.add('add-description-text-allowed', () => {
  1048. Boards.update(
  1049. {
  1050. allowsDescriptionText: {
  1051. $exists: false,
  1052. },
  1053. },
  1054. {
  1055. $set: {
  1056. allowsDescriptionText: true,
  1057. },
  1058. },
  1059. noValidateMulti,
  1060. );
  1061. });
  1062. Migrations.add('add-description-text-allowed-on-minicard', () => {
  1063. Boards.update(
  1064. {
  1065. allowsDescriptionTextOnMinicard: {
  1066. $exists: false,
  1067. },
  1068. },
  1069. {
  1070. $set: {
  1071. allowsDescriptionTextOnMinicard: true,
  1072. },
  1073. },
  1074. noValidateMulti,
  1075. );
  1076. });
  1077. Migrations.add('add-sort-field-to-boards', () => {
  1078. Boards.find().forEach((board, index) => {
  1079. if (!board.hasOwnProperty('sort')) {
  1080. Boards.direct.update(board._id, { $set: { sort: index } }, noValidate);
  1081. }
  1082. });
  1083. });
  1084. Migrations.add('add-default-profile-view', () => {
  1085. Users.find().forEach(user => {
  1086. if (!user.hasOwnProperty('profile.boardView')) {
  1087. // Set default view
  1088. Users.direct.update(
  1089. { _id: user._id },
  1090. { $set: { 'profile.boardView': 'board-view-swimlanes' } },
  1091. noValidate,
  1092. );
  1093. }
  1094. });
  1095. });
  1096. Migrations.add('add-hide-logo-by-default', () => {
  1097. Settings.update(
  1098. {
  1099. hideLogo: {
  1100. hideLogo: false,
  1101. },
  1102. },
  1103. {
  1104. $set: {
  1105. hideLogo: true,
  1106. },
  1107. },
  1108. noValidateMulti,
  1109. );
  1110. });
  1111. Migrations.add('add-hide-card-counter-list-by-default', () => {
  1112. Settings.update(
  1113. {
  1114. hideCardCounterList: {
  1115. hideCardCounterList: false,
  1116. },
  1117. },
  1118. {
  1119. $set: {
  1120. hideCardCounterList: true,
  1121. },
  1122. },
  1123. noValidateMulti,
  1124. );
  1125. });
  1126. Migrations.add('add-hide-board-member-list-by-default', () => {
  1127. Settings.update(
  1128. {
  1129. hideBoardMemberList: {
  1130. hideBoardMember: false,
  1131. },
  1132. },
  1133. {
  1134. $set: {
  1135. hideBoardMemberList: true,
  1136. },
  1137. },
  1138. noValidateMulti,
  1139. );
  1140. });
  1141. Migrations.add('add-card-number-allowed', () => {
  1142. Boards.update(
  1143. {
  1144. allowsCardNumber: {
  1145. $exists: false,
  1146. },
  1147. },
  1148. {
  1149. $set: {
  1150. allowsCardNumber: false,
  1151. },
  1152. },
  1153. noValidateMulti,
  1154. );
  1155. });
  1156. Migrations.add('assign-boardwise-card-numbers', () => {
  1157. Boards.find().forEach(board => {
  1158. let nextCardNumber = board.getNextCardNumber();
  1159. Cards.find(
  1160. {
  1161. boardId: board._id,
  1162. cardNumber: {
  1163. $exists: false
  1164. }
  1165. },
  1166. {
  1167. sort: { createdAt: 1 }
  1168. }
  1169. ).forEach(card => {
  1170. Cards.update(
  1171. card._id,
  1172. { $set: { cardNumber: nextCardNumber } },
  1173. noValidate);
  1174. nextCardNumber++;
  1175. });
  1176. })
  1177. });
  1178. Migrations.add('add-card-details-show-lists', () => {
  1179. Boards.update(
  1180. {
  1181. allowsShowLists: {
  1182. $exists: false,
  1183. },
  1184. },
  1185. {
  1186. $set: {
  1187. allowsShowLists: true,
  1188. },
  1189. },
  1190. noValidateMulti,
  1191. );
  1192. });
  1193. /*
  1194. Migrations.add('migrate-attachments-collectionFS-to-ostrioFiles', () => {
  1195. Meteor.settings.public.ostrioFilesMigrationInProgress = "true";
  1196. AttachmentsOld.find().forEach(function(fileObj) {
  1197. const newFileName = fileObj.name();
  1198. const storagePath = Attachments.storagePath({});
  1199. // OLD:
  1200. // const filePath = path.join(storagePath, `${fileObj._id}-${newFileName}`);
  1201. // NEW: Save file only with filename of ObjectID, not including filename.
  1202. // Fixes https://github.com/wekan/wekan/issues/4416#issuecomment-1510517168
  1203. //const filePath = path.join(storagePath, `${fileObj._id}`);
  1204. const filePath = path.join(storagePath, `${fileObj._id}-${newFileName}`);
  1205. // This is "example" variable, change it to the userId that you might be using.
  1206. const userId = fileObj.userId;
  1207. const fileType = fileObj.type();
  1208. const fileSize = fileObj.size();
  1209. const fileId = fileObj._id;
  1210. const readStream = fileObj.createReadStream('attachments');
  1211. const writeStream = fs.createWriteStream(filePath);
  1212. writeStream.on('error', function(err) {
  1213. console.log('Writing error: ', err, filePath);
  1214. });
  1215. // Once we have a file, then upload it to our new data storage
  1216. readStream.on('end', () => {
  1217. console.log('Ended: ', filePath);
  1218. // UserFiles is the new Meteor-Files/FilesCollection collection instance
  1219. Attachments.addFile(
  1220. filePath,
  1221. {
  1222. fileName: newFileName,
  1223. type: fileType,
  1224. meta: {
  1225. boardId: fileObj.boardId,
  1226. cardId: fileObj.cardId,
  1227. listId: fileObj.listId,
  1228. swimlaneId: fileObj.swimlaneId,
  1229. uploadedBeforeMigration: fileObj.uploadedAt,
  1230. migrationTime: new Date(),
  1231. copies: fileObj.copies,
  1232. source: 'import'
  1233. },
  1234. userId,
  1235. size: fileSize,
  1236. fileId,
  1237. },
  1238. (err, fileRef) => {
  1239. if (err) {
  1240. console.log(err);
  1241. } else {
  1242. console.log('File Inserted: ', fileRef._id);
  1243. // Set the userId again
  1244. Attachments.update({ _id: fileRef._id }, { $set: { userId } });
  1245. fileObj.remove();
  1246. }
  1247. },
  1248. true,
  1249. ); // proceedAfterUpload
  1250. });
  1251. readStream.on('error', error => {
  1252. console.log('Error: ', filePath, error);
  1253. });
  1254. readStream.pipe(writeStream);
  1255. });
  1256. Meteor.settings.public.ostrioFilesMigrationInProgress = "false";
  1257. });
  1258. Migrations.add('migrate-avatars-collectionFS-to-ostrioFiles', () => {
  1259. Meteor.settings.public.ostrioFilesMigrationInProgress = "true";
  1260. AvatarsOld.find().forEach(function(fileObj) {
  1261. const newFileName = fileObj.name();
  1262. const storagePath = Avatars.storagePath({});
  1263. // OLD:
  1264. // const filePath = path.join(storagePath, `${fileObj._id}-${newFileName}`);
  1265. // NEW: Save file only with filename of ObjectID, not including filename.
  1266. // Fixes https://github.com/wekan/wekan/issues/4416#issuecomment-1510517168
  1267. //const filePath = path.join(storagePath, `${fileObj._id}`);
  1268. const filePath = path.join(storagePath, `${fileObj._id}-${newFileName}`);
  1269. // This is "example" variable, change it to the userId that you might be using.
  1270. const userId = fileObj.userId;
  1271. const fileType = fileObj.type();
  1272. const fileSize = fileObj.size();
  1273. const fileId = fileObj._id;
  1274. const readStream = fileObj.createReadStream('avatars');
  1275. const writeStream = fs.createWriteStream(filePath);
  1276. writeStream.on('error', function(err) {
  1277. console.log('Writing error: ', err, filePath);
  1278. });
  1279. // Once we have a file, then upload it to our new data storage
  1280. readStream.on('end', () => {
  1281. console.log('Ended: ', filePath);
  1282. // UserFiles is the new Meteor-Files/FilesCollection collection instance
  1283. Avatars.addFile(
  1284. filePath,
  1285. {
  1286. fileName: newFileName,
  1287. type: fileType,
  1288. meta: {
  1289. boardId: fileObj.boardId,
  1290. cardId: fileObj.cardId,
  1291. listId: fileObj.listId,
  1292. swimlaneId: fileObj.swimlaneId,
  1293. },
  1294. userId,
  1295. size: fileSize,
  1296. fileId,
  1297. },
  1298. (err, fileRef) => {
  1299. if (err) {
  1300. console.log(err);
  1301. } else {
  1302. console.log('File Inserted: ', newFileName, fileRef._id);
  1303. // Set the userId again
  1304. Avatars.update({ _id: fileRef._id }, { $set: { userId } });
  1305. Users.find().forEach(user => {
  1306. const old_url = fileObj.url();
  1307. new_url = Avatars.findOne({ _id: fileRef._id }).link(
  1308. 'original',
  1309. '/',
  1310. );
  1311. if (user.profile.avatarUrl !== undefined) {
  1312. if (user.profile.avatarUrl.startsWith(old_url)) {
  1313. // Set avatar url to new url
  1314. Users.direct.update(
  1315. { _id: user._id },
  1316. { $set: { 'profile.avatarUrl': new_url } },
  1317. noValidate,
  1318. );
  1319. console.log('User avatar updated: ', user._id, new_url);
  1320. }
  1321. }
  1322. });
  1323. fileObj.remove();
  1324. }
  1325. },
  1326. true, // proceedAfterUpload
  1327. );
  1328. });
  1329. readStream.on('error', error => {
  1330. console.log('Error: ', filePath, error);
  1331. });
  1332. readStream.pipe(writeStream);
  1333. });
  1334. Meteor.settings.public.ostrioFilesMigrationInProgress = "false";
  1335. });
  1336. Migrations.add('migrate-attachment-drop-index-cardId', () => {
  1337. try {
  1338. Attachments.collection._dropIndex({'cardId': 1});
  1339. } catch (error) {
  1340. }
  1341. });
  1342. Migrations.add('migrate-attachment-migration-fix-source-import', () => {
  1343. // there was an error at first versions, so source was import, instead of import
  1344. Attachments.update(
  1345. {"meta.source":"import,"},
  1346. {$set:{"meta.source":"import"}},
  1347. noValidateMulti
  1348. );
  1349. });
  1350. /*
  1351. Migrations.add('attachment-cardCopy-fix-boardId-etc', () => {
  1352. Attachments.find( {"meta.source": "copy"} ).forEach(_attachment => {
  1353. const cardId = _attachment.meta.cardId;
  1354. const card = Cards.findOne(cardId);
  1355. if (card.boardId !== undefined && card.listId !== undefined && card.swimlaneId !== undefined) {
  1356. console.log("update attachment id: ", _attachment._id);
  1357. Attachments.update(_attachment._id, {
  1358. $set: {
  1359. "meta.boardId": card.boardId,
  1360. "meta.listId": card.listId,
  1361. "meta.swimlaneId": card.swimlaneId,
  1362. }
  1363. });
  1364. }
  1365. });
  1366. });
  1367. Migrations.add('remove-unused-planning-poker', () => {
  1368. Cards.update(
  1369. {
  1370. "poker.question": false,
  1371. },
  1372. {
  1373. $unset: {
  1374. "poker": 1,
  1375. },
  1376. },
  1377. noValidateMulti,
  1378. );
  1379. });
  1380. Migrations.add('remove-user-profile-hiddenSystemMessages', () => {
  1381. Users.update(
  1382. {
  1383. "profile.hiddenSystemMessages": {
  1384. $exists: true,
  1385. },
  1386. },
  1387. {
  1388. $unset: {
  1389. "profile.hiddenSystemMessages": 1,
  1390. },
  1391. },
  1392. noValidateMulti,
  1393. );
  1394. });
  1395. Migrations.add('remove-user-profile-hideCheckedItems', () => {
  1396. Users.update(
  1397. {
  1398. "profile.hideCheckedItems": {
  1399. $exists: true,
  1400. },
  1401. },
  1402. {
  1403. $unset: {
  1404. "profile.hideCheckedItems": 1,
  1405. },
  1406. },
  1407. noValidateMulti,
  1408. );
  1409. });
  1410. // Migration disabled - using backward compatibility approach instead
  1411. // This migration was taking too long for large databases
  1412. // The feature now works with both old lists (without swimlaneId) and new lists (with swimlaneId)
  1413. /*
  1414. Migrations.add('migrate-lists-to-per-swimlane', () => {
  1415. if (process.env.DEBUG === 'true') {
  1416. console.log('Starting migration: migrate-lists-to-per-swimlane');
  1417. }
  1418. try {
  1419. // Get all boards
  1420. const boards = Boards.find({}).fetch();
  1421. boards.forEach(board => {
  1422. if (process.env.DEBUG === 'true') {
  1423. console.log(`Processing board: ${board.title} (${board._id})`);
  1424. }
  1425. // Get the default swimlane for this board
  1426. const defaultSwimlane = board.getDefaultSwimline();
  1427. if (!defaultSwimlane) {
  1428. if (process.env.DEBUG === 'true') {
  1429. console.log(`No default swimlane found for board ${board._id}, skipping`);
  1430. }
  1431. return;
  1432. }
  1433. // Get all lists for this board that don't have a swimlaneId or have empty swimlaneId
  1434. const listsWithoutSwimlane = Lists.find({
  1435. boardId: board._id,
  1436. $or: [
  1437. { swimlaneId: { $exists: false } },
  1438. { swimlaneId: '' },
  1439. { swimlaneId: null }
  1440. ]
  1441. }).fetch();
  1442. if (process.env.DEBUG === 'true') {
  1443. console.log(`Found ${listsWithoutSwimlane.length} lists without swimlaneId in board ${board._id}`);
  1444. }
  1445. // Update each list to belong to the default swimlane
  1446. listsWithoutSwimlane.forEach(list => {
  1447. if (process.env.DEBUG === 'true') {
  1448. console.log(`Updating list "${list.title}" to belong to swimlane "${defaultSwimlane.title}"`);
  1449. }
  1450. Lists.direct.update(list._id, {
  1451. $set: {
  1452. swimlaneId: defaultSwimlane._id
  1453. }
  1454. }, noValidate);
  1455. });
  1456. });
  1457. if (process.env.DEBUG === 'true') {
  1458. console.log('Migration migrate-lists-to-per-swimlane completed successfully');
  1459. }
  1460. } catch (error) {
  1461. console.error('Error during migration migrate-lists-to-per-swimlane:', error);
  1462. throw error;
  1463. }
  1464. });
  1465. */
  1466. // ============================================================================
  1467. // END OF DISABLED MIGRATIONS
  1468. // ============================================================================
  1469. // All migrations above have been disabled. The application now uses backward
  1470. // compatibility code to handle both migrated and non-migrated database structures.
  1471. // ============================================================================