2025-10-21 15:14:01 +03:00
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' ) ;
}
2025-10-21 15:31:34 +03:00
if ( process . env . DEBUG === 'true' ) {
console . log ( 'Starting duplicate lists fix for all boards...' ) ;
}
2025-10-21 15:14:01 +03:00
const allBoards = Boards . find ( { } ) . fetch ( ) ;
let totalFixed = 0 ;
let totalBoardsProcessed = 0 ;
for ( const board of allBoards ) {
try {
2025-10-22 23:31:36 +03:00
const result = fixDuplicateListsForBoard ( board . _id ) ;
2025-10-21 15:14:01 +03:00
totalFixed += result . fixed ;
totalBoardsProcessed ++ ;
2025-10-21 15:31:34 +03:00
if ( result . fixed > 0 && process . env . DEBUG === 'true' ) {
2025-10-21 15:14:01 +03:00
console . log ( ` Fixed ${ result . fixed } duplicate lists in board " ${ board . title } " ( ${ board . _id } ) ` ) ;
}
} catch ( error ) {
console . error ( ` Error fixing board ${ board . _id } : ` , error ) ;
}
}
2025-10-21 15:31:34 +03:00
if ( process . env . DEBUG === 'true' ) {
console . log ( ` Duplicate lists fix completed. Processed ${ totalBoardsProcessed } boards, fixed ${ totalFixed } duplicate lists. ` ) ;
}
2025-10-21 15:14:01 +03:00
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' ) ;
}
2025-10-22 23:31:36 +03:00
return fixDuplicateListsForBoard ( boardId ) ;
}
} ) ;
2025-10-21 15:14:01 +03:00
2025-10-22 23:31:36 +03:00
// Helper functions defined outside of Meteor.methods
function fixDuplicateListsForBoard ( boardId ) {
2025-10-21 15:31:34 +03:00
if ( process . env . DEBUG === 'true' ) {
console . log ( ` Fixing duplicate lists for board ${ boardId } ... ` ) ;
}
2025-10-21 15:14:01 +03:00
// First, fix duplicate swimlanes
2025-10-22 23:31:36 +03:00
const swimlaneResult = fixDuplicateSwimlanes ( boardId ) ;
2025-10-21 15:14:01 +03:00
// Then, fix duplicate lists
2025-10-22 23:31:36 +03:00
const listResult = fixDuplicateLists ( boardId ) ;
2025-10-21 15:14:01 +03:00
return {
boardId ,
fixedSwimlanes : swimlaneResult . fixed ,
fixedLists : listResult . fixed ,
fixed : swimlaneResult . fixed + listResult . fixed
} ;
2025-10-22 23:31:36 +03:00
}
2025-10-21 15:14:01 +03:00
2025-10-22 23:31:36 +03:00
// Helper functions defined outside of Meteor.methods
function fixDuplicateSwimlanes ( boardId ) {
2025-10-21 15:14:01 +03:00
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 ) ;
2025-10-21 15:31:34 +03:00
if ( process . env . DEBUG === 'true' ) {
console . log ( ` Found ${ group . length } duplicate swimlanes with title " ${ title } ", keeping oldest ( ${ keepSwimlane . _id } ) ` ) ;
}
2025-10-21 15:14:01 +03:00
// 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 ) ;
2025-10-21 15:31:34 +03:00
if ( process . env . DEBUG === 'true' ) {
console . log ( ` Moved cards from duplicate list " ${ list . title } " to existing list in kept swimlane ` ) ;
}
2025-10-21 15:14:01 +03:00
} else {
// Move list to kept swimlane
Lists . update ( list . _id , { $set : { swimlaneId : keepSwimlane . _id } } ) ;
2025-10-21 15:31:34 +03:00
if ( process . env . DEBUG === 'true' ) {
console . log ( ` Moved list " ${ list . title } " to kept swimlane ` ) ;
}
2025-10-21 15:14:01 +03:00
}
} ) ;
// Remove duplicate swimlane
Swimlanes . remove ( swimlane . _id ) ;
fixed ++ ;
} ) ;
}
} ) ;
return { fixed } ;
2025-10-22 23:31:36 +03:00
}
2025-10-21 15:14:01 +03:00
2025-10-22 23:31:36 +03:00
function fixDuplicateLists ( boardId ) {
2025-10-21 15:14:01 +03:00
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 ) ;
2025-10-21 15:31:34 +03:00
if ( process . env . DEBUG === 'true' ) {
console . log ( ` Found ${ group . length } duplicate lists with title " ${ keepList . title } " in swimlane ${ keepList . swimlaneId } , keeping oldest ( ${ keepList . _id } ) ` ) ;
}
2025-10-21 15:14:01 +03:00
// 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 ++ ;
2025-10-21 15:31:34 +03:00
if ( process . env . DEBUG === 'true' ) {
console . log ( ` Moved cards from duplicate list " ${ list . title } " to kept list ` ) ;
}
2025-10-21 15:14:01 +03:00
} ) ;
}
} ) ;
return { fixed } ;
2025-10-22 23:31:36 +03:00
}
2025-10-21 15:14:01 +03:00
2025-10-22 23:31:36 +03:00
Meteor . methods ( {
2025-10-21 15:14:01 +03:00
'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
} ;
}
} ) ;
2025-10-21 15:19:19 +03:00