wekan/server/migrations/restoreAllArchived.js

266 lines
8.1 KiB
JavaScript

/**
* Restore All Archived Migration
*
* Restores all archived swimlanes, lists, and cards.
* If any restored items are missing swimlaneId, listId, or cardId,
* creates/assigns proper IDs to make them visible.
*/
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import Boards from '/models/boards';
import Lists from '/models/lists';
import Cards from '/models/cards';
import Swimlanes from '/models/swimlanes';
class RestoreAllArchivedMigration {
constructor() {
this.name = 'restoreAllArchived';
this.version = 1;
}
/**
* Check if migration is needed for a board
*/
needsMigration(boardId) {
try {
const archivedSwimlanes = ReactiveCache.getSwimlanes({ boardId, archived: true });
const archivedLists = ReactiveCache.getLists({ boardId, archived: true });
const archivedCards = ReactiveCache.getCards({ boardId, archived: true });
return archivedSwimlanes.length > 0 || archivedLists.length > 0 || archivedCards.length > 0;
} catch (error) {
console.error('Error checking if restoreAllArchived migration is needed:', error);
return false;
}
}
/**
* Execute the migration
*/
async executeMigration(boardId) {
try {
const results = {
swimlanesRestored: 0,
listsRestored: 0,
cardsRestored: 0,
itemsFixed: 0,
errors: []
};
const board = ReactiveCache.getBoard(boardId);
if (!board) {
throw new Error('Board not found');
}
// Get archived items
const archivedSwimlanes = ReactiveCache.getSwimlanes({ boardId, archived: true });
const archivedLists = ReactiveCache.getLists({ boardId, archived: true });
const archivedCards = ReactiveCache.getCards({ boardId, archived: true });
// Get active items for reference
const activeSwimlanes = ReactiveCache.getSwimlanes({ boardId, archived: false });
const activeLists = ReactiveCache.getLists({ boardId, archived: false });
// Restore all archived swimlanes
for (const swimlane of archivedSwimlanes) {
Swimlanes.update(swimlane._id, {
$set: {
archived: false,
updatedAt: new Date()
}
});
results.swimlanesRestored++;
if (process.env.DEBUG === 'true') {
console.log(`Restored swimlane: ${swimlane.title}`);
}
}
// Restore all archived lists and fix missing swimlaneId
for (const list of archivedLists) {
const updateFields = {
archived: false,
updatedAt: new Date()
};
// Fix missing swimlaneId
if (!list.swimlaneId) {
// Try to find a suitable swimlane or use default
let targetSwimlane = activeSwimlanes.find(s => !s.archived);
if (!targetSwimlane) {
// No active swimlane found, create default
const swimlaneId = Swimlanes.insert({
title: TAPi18n.__('default'),
boardId: boardId,
sort: 0,
createdAt: new Date(),
updatedAt: new Date(),
archived: false
});
targetSwimlane = ReactiveCache.getSwimlane(swimlaneId);
}
updateFields.swimlaneId = targetSwimlane._id;
results.itemsFixed++;
if (process.env.DEBUG === 'true') {
console.log(`Fixed missing swimlaneId for list: ${list.title}`);
}
}
Lists.update(list._id, {
$set: updateFields
});
results.listsRestored++;
if (process.env.DEBUG === 'true') {
console.log(`Restored list: ${list.title}`);
}
}
// Refresh lists after restoration
const allLists = ReactiveCache.getLists({ boardId, archived: false });
const allSwimlanes = ReactiveCache.getSwimlanes({ boardId, archived: false });
// Restore all archived cards and fix missing IDs
for (const card of archivedCards) {
const updateFields = {
archived: false,
updatedAt: new Date()
};
let needsFix = false;
// Fix missing listId
if (!card.listId) {
// Find or create a default list
let targetList = allLists.find(l => !l.archived);
if (!targetList) {
// No active list found, create one
const defaultSwimlane = allSwimlanes.find(s => !s.archived) || allSwimlanes[0];
const listId = Lists.insert({
title: TAPi18n.__('default'),
boardId: boardId,
swimlaneId: defaultSwimlane._id,
sort: 0,
createdAt: new Date(),
updatedAt: new Date(),
archived: false
});
targetList = ReactiveCache.getList(listId);
}
updateFields.listId = targetList._id;
needsFix = true;
}
// Fix missing swimlaneId
if (!card.swimlaneId) {
// Try to get swimlaneId from the card's list
if (card.listId || updateFields.listId) {
const cardList = allLists.find(l => l._id === (updateFields.listId || card.listId));
if (cardList && cardList.swimlaneId) {
updateFields.swimlaneId = cardList.swimlaneId;
} else {
// Fall back to first available swimlane
const defaultSwimlane = allSwimlanes.find(s => !s.archived) || allSwimlanes[0];
updateFields.swimlaneId = defaultSwimlane._id;
}
} else {
// Fall back to first available swimlane
const defaultSwimlane = allSwimlanes.find(s => !s.archived) || allSwimlanes[0];
updateFields.swimlaneId = defaultSwimlane._id;
}
needsFix = true;
}
if (needsFix) {
results.itemsFixed++;
if (process.env.DEBUG === 'true') {
console.log(`Fixed missing IDs for card: ${card.title}`);
}
}
Cards.update(card._id, {
$set: updateFields
});
results.cardsRestored++;
if (process.env.DEBUG === 'true') {
console.log(`Restored card: ${card.title}`);
}
}
return {
success: true,
changes: [
`Restored ${results.swimlanesRestored} archived swimlanes`,
`Restored ${results.listsRestored} archived lists`,
`Restored ${results.cardsRestored} archived cards`,
`Fixed ${results.itemsFixed} items with missing IDs`
],
results
};
} catch (error) {
console.error('Error executing restoreAllArchived migration:', error);
return {
success: false,
error: error.message
};
}
}
}
const restoreAllArchivedMigration = new RestoreAllArchivedMigration();
// Register Meteor methods
Meteor.methods({
'restoreAllArchived.needsMigration'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in');
}
return restoreAllArchivedMigration.needsMigration(boardId);
},
'restoreAllArchived.execute'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in');
}
// Check if user is board admin
const board = ReactiveCache.getBoard(boardId);
if (!board) {
throw new Meteor.Error('board-not-found', 'Board not found');
}
const user = ReactiveCache.getUser(this.userId);
if (!user) {
throw new Meteor.Error('user-not-found', 'User not found');
}
// Only board admins can run migrations
const isBoardAdmin = board.members && board.members.some(
member => member.userId === this.userId && member.isAdmin
);
if (!isBoardAdmin && !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Only board administrators can run migrations');
}
return restoreAllArchivedMigration.executeMigration(boardId);
}
});
export default restoreAllArchivedMigration;