| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 | 
							- /**
 
-  * Fix Missing Lists Migration
 
-  * 
 
-  * This migration fixes the issue where cards have incorrect listId references
 
-  * due to the per-swimlane lists change. It detects cards with mismatched
 
-  * listId/swimlaneId and creates the missing lists.
 
-  * 
 
-  * Issue: When upgrading from v7.94 to v8.02, cards that were in different
 
-  * swimlanes but shared the same list now have wrong listId references.
 
-  * 
 
-  * Example:
 
-  * - Card1: listId: 'HB93dWNnY5bgYdtxc', swimlaneId: 'sK69SseWkh3tMbJvg'
 
-  * - Card2: listId: 'HB93dWNnY5bgYdtxc', swimlaneId: 'XeecF9nZxGph4zcT4'
 
-  * 
 
-  * Card2 should have a different listId that corresponds to its swimlane.
 
-  */
 
- import { Meteor } from 'meteor/meteor';
 
- import { check } from 'meteor/check';
 
- import { ReactiveCache } from '/imports/reactiveCache';
 
- import Boards from '/models/boards';
 
- import Lists from '/models/lists';
 
- import Cards from '/models/cards';
 
- class FixMissingListsMigration {
 
-   constructor() {
 
-     this.name = 'fix-missing-lists';
 
-     this.version = 1;
 
-   }
 
-   /**
 
-    * Check if migration is needed for a board
 
-    */
 
-   needsMigration(boardId) {
 
-     try {
 
-       const board = ReactiveCache.getBoard(boardId);
 
-       if (!board) return false;
 
-       // Check if board has already been processed
 
-       if (board.fixMissingListsCompleted) {
 
-         return false;
 
-       }
 
-       // Check if there are cards with mismatched listId/swimlaneId
 
-       const cards = ReactiveCache.getCards({ boardId });
 
-       const lists = ReactiveCache.getLists({ boardId });
 
-       
 
-       // Create a map of listId -> swimlaneId for existing lists
 
-       const listSwimlaneMap = new Map();
 
-       lists.forEach(list => {
 
-         listSwimlaneMap.set(list._id, list.swimlaneId || '');
 
-       });
 
-       // Check for cards with mismatched listId/swimlaneId
 
-       for (const card of cards) {
 
-         const expectedSwimlaneId = listSwimlaneMap.get(card.listId);
 
-         if (expectedSwimlaneId && expectedSwimlaneId !== card.swimlaneId) {
 
-           console.log(`Found mismatched card: ${card._id}, listId: ${card.listId}, card swimlaneId: ${card.swimlaneId}, list swimlaneId: ${expectedSwimlaneId}`);
 
-           return true;
 
-         }
 
-       }
 
-       return false;
 
-     } catch (error) {
 
-       console.error('Error checking if migration is needed:', error);
 
-       return false;
 
-     }
 
-   }
 
-   /**
 
-    * Execute the migration for a board
 
-    */
 
-   async executeMigration(boardId) {
 
-     try {
 
-       console.log(`Starting fix missing lists migration for board ${boardId}`);
 
-       
 
-       const board = ReactiveCache.getBoard(boardId);
 
-       if (!board) {
 
-         throw new Error(`Board ${boardId} not found`);
 
-       }
 
-       const cards = ReactiveCache.getCards({ boardId });
 
-       const lists = ReactiveCache.getLists({ boardId });
 
-       const swimlanes = ReactiveCache.getSwimlanes({ boardId });
 
-       // Create maps for efficient lookup
 
-       const listSwimlaneMap = new Map();
 
-       const swimlaneListsMap = new Map();
 
-       
 
-       lists.forEach(list => {
 
-         listSwimlaneMap.set(list._id, list.swimlaneId || '');
 
-         if (!swimlaneListsMap.has(list.swimlaneId || '')) {
 
-           swimlaneListsMap.set(list.swimlaneId || '', []);
 
-         }
 
-         swimlaneListsMap.get(list.swimlaneId || '').push(list);
 
-       });
 
-       // Group cards by swimlaneId
 
-       const cardsBySwimlane = new Map();
 
-       cards.forEach(card => {
 
-         if (!cardsBySwimlane.has(card.swimlaneId)) {
 
-           cardsBySwimlane.set(card.swimlaneId, []);
 
-         }
 
-         cardsBySwimlane.get(card.swimlaneId).push(card);
 
-       });
 
-       let createdLists = 0;
 
-       let updatedCards = 0;
 
-       // Process each swimlane
 
-       for (const [swimlaneId, swimlaneCards] of cardsBySwimlane) {
 
-         if (!swimlaneId) continue;
 
-         // Get existing lists for this swimlane
 
-         const existingLists = swimlaneListsMap.get(swimlaneId) || [];
 
-         const existingListTitles = new Set(existingLists.map(list => list.title));
 
-         // Group cards by their current listId
 
-         const cardsByListId = new Map();
 
-         swimlaneCards.forEach(card => {
 
-           if (!cardsByListId.has(card.listId)) {
 
-             cardsByListId.set(card.listId, []);
 
-           }
 
-           cardsByListId.get(card.listId).push(card);
 
-         });
 
-         // For each listId used by cards in this swimlane
 
-         for (const [listId, cardsInList] of cardsByListId) {
 
-           const originalList = lists.find(l => l._id === listId);
 
-           if (!originalList) continue;
 
-           // Check if this list's swimlaneId matches the card's swimlaneId
 
-           const listSwimlaneId = listSwimlaneMap.get(listId);
 
-           if (listSwimlaneId === swimlaneId) {
 
-             // List is already correctly assigned to this swimlane
 
-             continue;
 
-           }
 
-           // Check if we already have a list with the same title in this swimlane
 
-           let targetList = existingLists.find(list => list.title === originalList.title);
 
-           
 
-           if (!targetList) {
 
-             // Create a new list for this swimlane
 
-             const newListData = {
 
-               title: originalList.title,
 
-               boardId: boardId,
 
-               swimlaneId: swimlaneId,
 
-               sort: originalList.sort || 0,
 
-               archived: originalList.archived || false,
 
-               createdAt: new Date(),
 
-               modifiedAt: new Date(),
 
-               type: originalList.type || 'list'
 
-             };
 
-             // Copy other properties if they exist
 
-             if (originalList.color) newListData.color = originalList.color;
 
-             if (originalList.wipLimit) newListData.wipLimit = originalList.wipLimit;
 
-             if (originalList.wipLimitEnabled) newListData.wipLimitEnabled = originalList.wipLimitEnabled;
 
-             if (originalList.wipLimitSoft) newListData.wipLimitSoft = originalList.wipLimitSoft;
 
-             if (originalList.starred) newListData.starred = originalList.starred;
 
-             if (originalList.collapsed) newListData.collapsed = originalList.collapsed;
 
-             // Insert the new list
 
-             const newListId = Lists.insert(newListData);
 
-             targetList = { _id: newListId, ...newListData };
 
-             createdLists++;
 
-             
 
-             console.log(`Created new list "${originalList.title}" for swimlane ${swimlaneId}`);
 
-           }
 
-           // Update all cards in this group to use the correct listId
 
-           for (const card of cardsInList) {
 
-             Cards.update(card._id, {
 
-               $set: {
 
-                 listId: targetList._id,
 
-                 modifiedAt: new Date()
 
-               }
 
-             });
 
-             updatedCards++;
 
-           }
 
-         }
 
-       }
 
-       // Mark board as processed
 
-       Boards.update(boardId, {
 
-         $set: {
 
-           fixMissingListsCompleted: true,
 
-           fixMissingListsCompletedAt: new Date()
 
-         }
 
-       });
 
-       console.log(`Fix missing lists migration completed for board ${boardId}: created ${createdLists} lists, updated ${updatedCards} cards`);
 
-       
 
-       return {
 
-         success: true,
 
-         createdLists,
 
-         updatedCards
 
-       };
 
-     } catch (error) {
 
-       console.error(`Error executing fix missing lists migration for board ${boardId}:`, error);
 
-       throw error;
 
-     }
 
-   }
 
-   /**
 
-    * Get migration status for a board
 
-    */
 
-   getMigrationStatus(boardId) {
 
-     try {
 
-       const board = ReactiveCache.getBoard(boardId);
 
-       if (!board) {
 
-         return { status: 'board_not_found' };
 
-       }
 
-       if (board.fixMissingListsCompleted) {
 
-         return { 
 
-           status: 'completed',
 
-           completedAt: board.fixMissingListsCompletedAt
 
-         };
 
-       }
 
-       const needsMigration = this.needsMigration(boardId);
 
-       return {
 
-         status: needsMigration ? 'needed' : 'not_needed'
 
-       };
 
-     } catch (error) {
 
-       console.error('Error getting migration status:', error);
 
-       return { status: 'error', error: error.message };
 
-     }
 
-   }
 
- }
 
- // Export singleton instance
 
- export const fixMissingListsMigration = new FixMissingListsMigration();
 
- // Meteor methods
 
- Meteor.methods({
 
-   'fixMissingListsMigration.check'(boardId) {
 
-     check(boardId, String);
 
-     
 
-     if (!this.userId) {
 
-       throw new Meteor.Error('not-authorized');
 
-     }
 
-     
 
-     return fixMissingListsMigration.getMigrationStatus(boardId);
 
-   },
 
-   'fixMissingListsMigration.execute'(boardId) {
 
-     check(boardId, String);
 
-     
 
-     if (!this.userId) {
 
-       throw new Meteor.Error('not-authorized');
 
-     }
 
-     
 
-     return fixMissingListsMigration.executeMigration(boardId);
 
-   },
 
-   'fixMissingListsMigration.needsMigration'(boardId) {
 
-     check(boardId, String);
 
-     
 
-     if (!this.userId) {
 
-       throw new Meteor.Error('not-authorized');
 
-     }
 
-     
 
-     return fixMissingListsMigration.needsMigration(boardId);
 
-   }
 
- });
 
 
  |