| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 | #!/usr/bin/env node/** * Standalone script to fix duplicate lists created by WeKan 8.10 *  * Usage: *   node fix-duplicate-lists.js *  * This script will: * 1. Connect to the MongoDB database * 2. Identify boards with duplicate lists/swimlanes * 3. Fix the duplicates by merging them * 4. Report the results */const { MongoClient } = require('mongodb');// Configuration - adjust these for your setupconst MONGO_URL = process.env.MONGO_URL || 'mongodb://localhost:27017/wekan';const DB_NAME = process.env.MONGO_DB_NAME || 'wekan';class DuplicateListsFixer {  constructor() {    this.client = null;    this.db = null;  }  async connect() {    console.log('Connecting to MongoDB...');    this.client = new MongoClient(MONGO_URL);    await this.client.connect();    this.db = this.client.db(DB_NAME);    console.log('Connected to MongoDB');  }  async disconnect() {    if (this.client) {      await this.client.close();      console.log('Disconnected from MongoDB');    }  }  async getReport() {    console.log('Analyzing boards for duplicate lists...');        const boards = await this.db.collection('boards').find({}).toArray();    const report = [];    for (const board of boards) {      const swimlanes = await this.db.collection('swimlanes').find({ boardId: board._id }).toArray();      const lists = await this.db.collection('lists').find({ boardId: board._id }).toArray();            // 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: boards.length,      boardsWithDuplicates: report.length,      report    };  }  async fixBoard(boardId) {    console.log(`Fixing duplicate lists for board ${boardId}...`);        // Fix duplicate swimlanes    const swimlaneResult = await this.fixDuplicateSwimlanes(boardId);        // Fix duplicate lists    const listResult = await this.fixDuplicateLists(boardId);        return {      boardId,      fixedSwimlanes: swimlaneResult.fixed,      fixedLists: listResult.fixed,      fixed: swimlaneResult.fixed + listResult.fixed    };  }  async fixDuplicateSwimlanes(boardId) {    const swimlanes = await this.db.collection('swimlanes').find({ boardId }).toArray();    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    for (const [title, group] of Object.entries(swimlaneGroups)) {      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);        console.log(`Found ${group.length} duplicate swimlanes with title "${title}", keeping oldest (${keepSwimlane._id})`);        // Move all lists from duplicate swimlanes to the kept swimlane        for (const swimlane of removeSwimlanes) {          const lists = await this.db.collection('lists').find({ swimlaneId: swimlane._id }).toArray();          for (const list of lists) {            // Check if a list with the same title already exists in the kept swimlane            const existingList = await this.db.collection('lists').findOne({              boardId,              swimlaneId: keepSwimlane._id,              title: list.title            });            if (existingList) {              // Move cards to existing list              await this.db.collection('cards').updateMany(                { listId: list._id },                { $set: { listId: existingList._id } }              );              // Remove duplicate list              await this.db.collection('lists').deleteOne({ _id: list._id });              console.log(`Moved cards from duplicate list "${list.title}" to existing list in kept swimlane`);            } else {              // Move list to kept swimlane              await this.db.collection('lists').updateOne(                { _id: list._id },                { $set: { swimlaneId: keepSwimlane._id } }              );              console.log(`Moved list "${list.title}" to kept swimlane`);            }          }          // Remove duplicate swimlane          await this.db.collection('swimlanes').deleteOne({ _id: swimlane._id });          fixed++;        }      }    }    return { fixed };  }  async fixDuplicateLists(boardId) {    const lists = await this.db.collection('lists').find({ boardId }).toArray();    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    for (const [key, group] of Object.entries(listGroups)) {      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);        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        for (const list of removeLists) {          await this.db.collection('cards').updateMany(            { listId: list._id },            { $set: { listId: keepList._id } }          );                    // Remove duplicate list          await this.db.collection('lists').deleteOne({ _id: list._id });          fixed++;          console.log(`Moved cards from duplicate list "${list.title}" to kept list`);        }      }    }    return { fixed };  }  async fixAllBoards() {    console.log('Starting duplicate lists fix for all boards...');        const allBoards = await this.db.collection('boards').find({}).toArray();    let totalFixed = 0;    let totalBoardsProcessed = 0;    for (const board of allBoards) {      try {        const result = await this.fixBoard(board._id);        totalFixed += result.fixed;        totalBoardsProcessed++;                if (result.fixed > 0) {          console.log(`Fixed ${result.fixed} duplicate lists in board "${board.title}" (${board._id})`);        }      } catch (error) {        console.error(`Error fixing board ${board._id}:`, error);      }    }    console.log(`Duplicate lists fix completed. Processed ${totalBoardsProcessed} boards, fixed ${totalFixed} duplicate lists.`);        return {      message: `Fixed ${totalFixed} duplicate lists across ${totalBoardsProcessed} boards`,      totalFixed,      totalBoardsProcessed    };  }}// Main executionasync function main() {  const fixer = new DuplicateListsFixer();    try {    await fixer.connect();        // Get report first    const report = await fixer.getReport();        if (report.boardsWithDuplicates === 0) {      console.log('No duplicate lists found!');      return;    }    console.log(`Found ${report.boardsWithDuplicates} boards with duplicate lists:`);    report.report.forEach(board => {      console.log(`- Board "${board.boardTitle}" (${board.boardId}): ${board.duplicateSwimlanes} duplicate swimlanes, ${board.duplicateLists} duplicate lists`);    });    // Perform the fix    const result = await fixer.fixAllBoards();    console.log('Fix completed:', result);      } catch (error) {    console.error('Error:', error);    process.exit(1);  } finally {    await fixer.disconnect();  }}// Run if called directlyif (require.main === module) {  main();}module.exports = DuplicateListsFixer;
 |