From 58df525b4915a99d0f603cc2536fd1fad1d20b29 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 21 Oct 2025 15:31:34 +0300 Subject: [PATCH] Fix duplicated lists and do not show debug messages when env DEBUG is not true. Part 3. Thanks to xet7 ! Fixes #5952 --- client/components/boards/boardBody.js | 68 ++++- client/components/boards/boardHeader.js | 16 +- fix-duplicate-lists.js | 287 ------------------ server/methods/fixDuplicateLists.js | 34 ++- server/migrations/fixMissingListsMigration.js | 16 +- 5 files changed, 113 insertions(+), 308 deletions(-) delete mode 100644 fix-duplicate-lists.js diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js index 1be4a30cd..d4afe0bb2 100644 --- a/client/components/boards/boardBody.js +++ b/client/components/boards/boardBody.js @@ -63,7 +63,9 @@ BlazeComponent.extendComponent({ title: 'Default', boardId: boardId, }); - console.log(`Created default swimlane ${swimlaneId} for board ${boardId}`); + if (process.env.DEBUG === 'true') { + console.log(`Created default swimlane ${swimlaneId} for board ${boardId}`); + } } this._swimlaneCreated.add(boardId); } else { @@ -123,6 +125,9 @@ BlazeComponent.extendComponent({ // Fix missing lists migration (for cards with wrong listId references) await this.fixMissingLists(boardId); + // Fix duplicate lists created by WeKan 8.10 + await this.fixDuplicateLists(boardId); + // Start attachment migration in background if needed this.startAttachmentMigrationIfNeeded(boardId); } catch (error) { @@ -312,7 +317,9 @@ BlazeComponent.extendComponent({ }); if (result && result.success) { - console.log(`Successfully fixed missing lists for board ${boardId}: created ${result.createdLists} lists, updated ${result.updatedCards} cards`); + if (process.env.DEBUG === 'true') { + console.log(`Successfully fixed missing lists for board ${boardId}: created ${result.createdLists} lists, updated ${result.updatedCards} cards`); + } } } catch (error) { @@ -320,6 +327,55 @@ BlazeComponent.extendComponent({ } }, + async fixDuplicateLists(boardId) { + try { + const board = ReactiveCache.getBoard(boardId); + if (!board) return; + + // Check if board has already been processed for duplicate lists fix + if (board.fixDuplicateListsCompleted) { + if (process.env.DEBUG === 'true') { + console.log(`Board ${boardId} has already been processed for duplicate lists fix`); + } + return; + } + + if (process.env.DEBUG === 'true') { + console.log(`Starting duplicate lists fix for board ${boardId}`); + } + + // Execute the duplicate lists fix + const result = await new Promise((resolve, reject) => { + Meteor.call('fixDuplicateLists.fixBoard', boardId, (error, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); + + if (result && result.fixed > 0) { + if (process.env.DEBUG === 'true') { + console.log(`Successfully fixed ${result.fixed} duplicate lists for board ${boardId}: ${result.fixedSwimlanes} swimlanes, ${result.fixedLists} lists`); + } + + // Mark board as processed + Meteor.call('boards.update', boardId, { $set: { fixDuplicateListsCompleted: true } }); + } else if (process.env.DEBUG === 'true') { + console.log(`No duplicate lists found for board ${boardId}`); + // Still mark as processed to avoid repeated checks + Meteor.call('boards.update', boardId, { $set: { fixDuplicateListsCompleted: true } }); + } else { + // Still mark as processed to avoid repeated checks + Meteor.call('boards.update', boardId, { $set: { fixDuplicateListsCompleted: true } }); + } + + } catch (error) { + console.error('Error fixing duplicate lists:', error); + } + }, + async startAttachmentMigrationIfNeeded(boardId) { try { // Check if board has already been migrated @@ -1201,9 +1257,13 @@ BlazeComponent.extendComponent({ const firstSwimlane = currentBoard.swimlanes()[0]; Meteor.call('createCardWithDueDate', currentBoard._id, firstList._id, myTitle, startDate.toDate(), firstSwimlane._id, function(error, result) { if (error) { - console.log(error); + if (process.env.DEBUG === 'true') { + console.log(error); + } } else { - console.log("Card Created", result); + if (process.env.DEBUG === 'true') { + console.log("Card Created", result); + } } }); closeModal(); diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js index 72271f44a..b95a45395 100644 --- a/client/components/boards/boardHeader.js +++ b/client/components/boards/boardHeader.js @@ -82,18 +82,26 @@ BlazeComponent.extendComponent({ }, 'click .js-toggle-board-view': Popup.open('boardChangeView'), 'click .js-toggle-sidebar'() { - console.log('Hamburger menu clicked'); + if (process.env.DEBUG === 'true') { + console.log('Hamburger menu clicked'); + } // Use the same approach as keyboard shortcuts if (typeof Sidebar !== 'undefined' && Sidebar && typeof Sidebar.toggle === 'function') { - console.log('Using Sidebar.toggle()'); + if (process.env.DEBUG === 'true') { + console.log('Using Sidebar.toggle()'); + } Sidebar.toggle(); } else { - console.warn('Sidebar not available, trying alternative approach'); + if (process.env.DEBUG === 'true') { + console.warn('Sidebar not available, trying alternative approach'); + } // Try to trigger the sidebar through the global Blaze helper if (typeof Blaze !== 'undefined' && Blaze._globalHelpers && Blaze._globalHelpers.Sidebar) { const sidebar = Blaze._globalHelpers.Sidebar(); if (sidebar && typeof sidebar.toggle === 'function') { - console.log('Using Blaze helper Sidebar.toggle()'); + if (process.env.DEBUG === 'true') { + console.log('Using Blaze helper Sidebar.toggle()'); + } sidebar.toggle(); } } diff --git a/fix-duplicate-lists.js b/fix-duplicate-lists.js deleted file mode 100644 index 50270e9e0..000000000 --- a/fix-duplicate-lists.js +++ /dev/null @@ -1,287 +0,0 @@ -#!/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 setup -const 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 execution -async 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 directly -if (require.main === module) { - main(); -} - -module.exports = DuplicateListsFixer; - diff --git a/server/methods/fixDuplicateLists.js b/server/methods/fixDuplicateLists.js index 9acfa18b9..f5e79bab5 100644 --- a/server/methods/fixDuplicateLists.js +++ b/server/methods/fixDuplicateLists.js @@ -15,7 +15,9 @@ Meteor.methods({ throw new Meteor.Error('not-authorized'); } - console.log('Starting duplicate lists fix for all boards...'); + if (process.env.DEBUG === 'true') { + console.log('Starting duplicate lists fix for all boards...'); + } const allBoards = Boards.find({}).fetch(); let totalFixed = 0; @@ -27,7 +29,7 @@ Meteor.methods({ totalFixed += result.fixed; totalBoardsProcessed++; - if (result.fixed > 0) { + if (result.fixed > 0 && process.env.DEBUG === 'true') { console.log(`Fixed ${result.fixed} duplicate lists in board "${board.title}" (${board._id})`); } } catch (error) { @@ -35,7 +37,9 @@ Meteor.methods({ } } - console.log(`Duplicate lists fix completed. Processed ${totalBoardsProcessed} boards, fixed ${totalFixed} duplicate lists.`); + 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`, @@ -55,7 +59,9 @@ Meteor.methods({ }, fixDuplicateListsForBoard(boardId) { - console.log(`Fixing duplicate lists for board ${boardId}...`); + if (process.env.DEBUG === 'true') { + console.log(`Fixing duplicate lists for board ${boardId}...`); + } // First, fix duplicate swimlanes const swimlaneResult = this.fixDuplicateSwimlanes(boardId); @@ -94,7 +100,9 @@ Meteor.methods({ const keepSwimlane = group[0]; const removeSwimlanes = group.slice(1); - console.log(`Found ${group.length} duplicate swimlanes with title "${title}", keeping oldest (${keepSwimlane._id})`); + 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 => { @@ -116,11 +124,15 @@ Meteor.methods({ ); // Remove duplicate list Lists.remove(list._id); - console.log(`Moved cards from duplicate list "${list.title}" to existing list in kept swimlane`); + 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 } }); - console.log(`Moved list "${list.title}" to kept swimlane`); + if (process.env.DEBUG === 'true') { + console.log(`Moved list "${list.title}" to kept swimlane`); + } } }); @@ -157,7 +169,9 @@ Meteor.methods({ 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})`); + 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 => { @@ -170,7 +184,9 @@ Meteor.methods({ // Remove duplicate list Lists.remove(list._id); fixed++; - console.log(`Moved cards from duplicate list "${list.title}" to kept list`); + if (process.env.DEBUG === 'true') { + console.log(`Moved cards from duplicate list "${list.title}" to kept list`); + } }); } }); diff --git a/server/migrations/fixMissingListsMigration.js b/server/migrations/fixMissingListsMigration.js index 0ba01af51..22e5b16de 100644 --- a/server/migrations/fixMissingListsMigration.js +++ b/server/migrations/fixMissingListsMigration.js @@ -55,7 +55,9 @@ class FixMissingListsMigration { 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}`); + if (process.env.DEBUG === 'true') { + console.log(`Found mismatched card: ${card._id}, listId: ${card.listId}, card swimlaneId: ${card.swimlaneId}, list swimlaneId: ${expectedSwimlaneId}`); + } return true; } } @@ -72,7 +74,9 @@ class FixMissingListsMigration { */ async executeMigration(boardId) { try { - console.log(`Starting fix missing lists migration for board ${boardId}`); + if (process.env.DEBUG === 'true') { + console.log(`Starting fix missing lists migration for board ${boardId}`); + } const board = ReactiveCache.getBoard(boardId); if (!board) { @@ -165,7 +169,9 @@ class FixMissingListsMigration { targetList = { _id: newListId, ...newListData }; createdLists++; - console.log(`Created new list "${originalList.title}" for swimlane ${swimlaneId}`); + if (process.env.DEBUG === 'true') { + console.log(`Created new list "${originalList.title}" for swimlane ${swimlaneId}`); + } } // Update all cards in this group to use the correct listId @@ -189,7 +195,9 @@ class FixMissingListsMigration { } }); - console.log(`Fix missing lists migration completed for board ${boardId}: created ${createdLists} lists, updated ${updatedCards} cards`); + if (process.env.DEBUG === 'true') { + console.log(`Fix missing lists migration completed for board ${boardId}: created ${createdLists} lists, updated ${updatedCards} cards`); + } return { success: true,