| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255 | import { Meteor } from 'meteor/meteor';import { check } from 'meteor/check';import Boards from '/models/boards';import Lists from '/models/lists';import Swimlanes from '/models/swimlanes';import Cards from '/models/cards';/** * Fix duplicate lists and swimlanes created by WeKan 8.10 * This method identifies and removes duplicate lists while preserving cards */Meteor.methods({  'fixDuplicateLists.fixAllBoards'() {    if (!this.userId) {      throw new Meteor.Error('not-authorized');    }    if (process.env.DEBUG === 'true') {      console.log('Starting duplicate lists fix for all boards...');    }        const allBoards = Boards.find({}).fetch();    let totalFixed = 0;    let totalBoardsProcessed = 0;    for (const board of allBoards) {      try {        const result = fixDuplicateListsForBoard(board._id);        totalFixed += result.fixed;        totalBoardsProcessed++;                if (result.fixed > 0 && process.env.DEBUG === 'true') {          console.log(`Fixed ${result.fixed} duplicate lists in board "${board.title}" (${board._id})`);        }      } catch (error) {        console.error(`Error fixing board ${board._id}:`, error);      }    }    if (process.env.DEBUG === 'true') {      console.log(`Duplicate lists fix completed. Processed ${totalBoardsProcessed} boards, fixed ${totalFixed} duplicate lists.`);    }        return {      message: `Fixed ${totalFixed} duplicate lists across ${totalBoardsProcessed} boards`,      totalFixed,      totalBoardsProcessed    };  },  'fixDuplicateLists.fixBoard'(boardId) {    check(boardId, String);        if (!this.userId) {      throw new Meteor.Error('not-authorized');    }    return fixDuplicateListsForBoard(boardId);  }});// Helper functions defined outside of Meteor.methodsfunction fixDuplicateListsForBoard(boardId) {    if (process.env.DEBUG === 'true') {      console.log(`Fixing duplicate lists for board ${boardId}...`);    }        // First, fix duplicate swimlanes    const swimlaneResult = fixDuplicateSwimlanes(boardId);        // Then, fix duplicate lists    const listResult = fixDuplicateLists(boardId);        return {      boardId,      fixedSwimlanes: swimlaneResult.fixed,      fixedLists: listResult.fixed,      fixed: swimlaneResult.fixed + listResult.fixed    };}// Helper functions defined outside of Meteor.methodsfunction fixDuplicateSwimlanes(boardId) {    const swimlanes = Swimlanes.find({ boardId }).fetch();    const swimlaneGroups = {};    let fixed = 0;    // Group swimlanes by title    swimlanes.forEach(swimlane => {      const key = swimlane.title || 'Default';      if (!swimlaneGroups[key]) {        swimlaneGroups[key] = [];      }      swimlaneGroups[key].push(swimlane);    });    // For each group with duplicates, keep the oldest and remove the rest    Object.keys(swimlaneGroups).forEach(title => {      const group = swimlaneGroups[title];      if (group.length > 1) {        // Sort by creation date, keep the oldest        group.sort((a, b) => new Date(a.createdAt || 0) - new Date(b.createdAt || 0));        const keepSwimlane = group[0];        const removeSwimlanes = group.slice(1);        if (process.env.DEBUG === 'true') {          console.log(`Found ${group.length} duplicate swimlanes with title "${title}", keeping oldest (${keepSwimlane._id})`);        }        // Move all lists from duplicate swimlanes to the kept swimlane        removeSwimlanes.forEach(swimlane => {          const lists = Lists.find({ swimlaneId: swimlane._id }).fetch();          lists.forEach(list => {            // Check if a list with the same title already exists in the kept swimlane            const existingList = Lists.findOne({              boardId,              swimlaneId: keepSwimlane._id,              title: list.title            });            if (existingList) {              // Move cards to existing list              Cards.update(                { listId: list._id },                { $set: { listId: existingList._id } },                { multi: true }              );              // Remove duplicate list              Lists.remove(list._id);              if (process.env.DEBUG === 'true') {                console.log(`Moved cards from duplicate list "${list.title}" to existing list in kept swimlane`);              }            } else {              // Move list to kept swimlane              Lists.update(list._id, { $set: { swimlaneId: keepSwimlane._id } });              if (process.env.DEBUG === 'true') {                console.log(`Moved list "${list.title}" to kept swimlane`);              }            }          });          // Remove duplicate swimlane          Swimlanes.remove(swimlane._id);          fixed++;        });      }    });    return { fixed };}function fixDuplicateLists(boardId) {    const lists = Lists.find({ boardId }).fetch();    const listGroups = {};    let fixed = 0;    // Group lists by title and swimlaneId    lists.forEach(list => {      const key = `${list.swimlaneId || 'null'}-${list.title}`;      if (!listGroups[key]) {        listGroups[key] = [];      }      listGroups[key].push(list);    });    // For each group with duplicates, keep the oldest and remove the rest    Object.keys(listGroups).forEach(key => {      const group = listGroups[key];      if (group.length > 1) {        // Sort by creation date, keep the oldest        group.sort((a, b) => new Date(a.createdAt || 0) - new Date(b.createdAt || 0));        const keepList = group[0];        const removeLists = group.slice(1);        if (process.env.DEBUG === 'true') {          console.log(`Found ${group.length} duplicate lists with title "${keepList.title}" in swimlane ${keepList.swimlaneId}, keeping oldest (${keepList._id})`);        }        // Move all cards from duplicate lists to the kept list        removeLists.forEach(list => {          Cards.update(            { listId: list._id },            { $set: { listId: keepList._id } },            { multi: true }          );                    // Remove duplicate list          Lists.remove(list._id);          fixed++;          if (process.env.DEBUG === 'true') {            console.log(`Moved cards from duplicate list "${list.title}" to kept list`);          }        });      }    });    return { fixed };}Meteor.methods({  'fixDuplicateLists.getReport'() {    if (!this.userId) {      throw new Meteor.Error('not-authorized');    }    const allBoards = Boards.find({}).fetch();    const report = [];    for (const board of allBoards) {      const swimlanes = Swimlanes.find({ boardId: board._id }).fetch();      const lists = Lists.find({ boardId: board._id }).fetch();            // Check for duplicate swimlanes      const swimlaneGroups = {};      swimlanes.forEach(swimlane => {        const key = swimlane.title || 'Default';        if (!swimlaneGroups[key]) {          swimlaneGroups[key] = [];        }        swimlaneGroups[key].push(swimlane);      });      // Check for duplicate lists      const listGroups = {};      lists.forEach(list => {        const key = `${list.swimlaneId || 'null'}-${list.title}`;        if (!listGroups[key]) {          listGroups[key] = [];        }        listGroups[key].push(list);      });      const duplicateSwimlanes = Object.values(swimlaneGroups).filter(group => group.length > 1);      const duplicateLists = Object.values(listGroups).filter(group => group.length > 1);      if (duplicateSwimlanes.length > 0 || duplicateLists.length > 0) {        report.push({          boardId: board._id,          boardTitle: board.title,          duplicateSwimlanes: duplicateSwimlanes.length,          duplicateLists: duplicateLists.length,          totalSwimlanes: swimlanes.length,          totalLists: lists.length        });      }    }    return {      totalBoards: allBoards.length,      boardsWithDuplicates: report.length,      report    };  }});
 |