mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 15:30:13 +01:00
235 lines
7.3 KiB
JavaScript
235 lines
7.3 KiB
JavaScript
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');
|
|
}
|
|
|
|
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 = this.fixDuplicateListsForBoard(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
|
|
};
|
|
},
|
|
|
|
'fixDuplicateLists.fixBoard'(boardId) {
|
|
check(boardId, String);
|
|
|
|
if (!this.userId) {
|
|
throw new Meteor.Error('not-authorized');
|
|
}
|
|
|
|
return this.fixDuplicateListsForBoard(boardId);
|
|
},
|
|
|
|
fixDuplicateListsForBoard(boardId) {
|
|
console.log(`Fixing duplicate lists for board ${boardId}...`);
|
|
|
|
// First, fix duplicate swimlanes
|
|
const swimlaneResult = this.fixDuplicateSwimlanes(boardId);
|
|
|
|
// Then, fix duplicate lists
|
|
const listResult = this.fixDuplicateLists(boardId);
|
|
|
|
return {
|
|
boardId,
|
|
fixedSwimlanes: swimlaneResult.fixed,
|
|
fixedLists: listResult.fixed,
|
|
fixed: swimlaneResult.fixed + listResult.fixed
|
|
};
|
|
},
|
|
|
|
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);
|
|
|
|
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);
|
|
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`);
|
|
}
|
|
});
|
|
|
|
// Remove duplicate swimlane
|
|
Swimlanes.remove(swimlane._id);
|
|
fixed++;
|
|
});
|
|
}
|
|
});
|
|
|
|
return { fixed };
|
|
},
|
|
|
|
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);
|
|
|
|
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++;
|
|
console.log(`Moved cards from duplicate list "${list.title}" to kept list`);
|
|
});
|
|
}
|
|
});
|
|
|
|
return { fixed };
|
|
},
|
|
|
|
'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
|
|
};
|
|
}
|
|
});
|
|
|