2025-10-21 15:14:01 +03:00
#!/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 ;
2025-10-21 15:19:19 +03:00