mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 07:20:12 +01:00
245 lines
No EOL
6.9 KiB
JavaScript
245 lines
No EOL
6.9 KiB
JavaScript
/**
|
|
* Server-side Attachment Migration System
|
|
* Handles migration of attachments from old structure to new structure
|
|
*/
|
|
|
|
import { Meteor } from 'meteor/meteor';
|
|
import { ReactiveVar } from 'meteor/reactive-var';
|
|
import { check } from 'meteor/check';
|
|
import { ReactiveCache } from '/imports/reactiveCache';
|
|
import Attachments from '/models/attachments';
|
|
|
|
// Reactive variables for tracking migration progress
|
|
const migrationProgress = new ReactiveVar(0);
|
|
const migrationStatus = new ReactiveVar('');
|
|
const unconvertedAttachments = new ReactiveVar([]);
|
|
|
|
// Track migrated boards on server side
|
|
const migratedBoards = new Set();
|
|
|
|
class AttachmentMigrationService {
|
|
constructor() {
|
|
this.migrationCache = new Map();
|
|
}
|
|
|
|
/**
|
|
* Check if a board has been migrated
|
|
* @param {string} boardId - The board ID
|
|
* @returns {boolean} - True if board has been migrated
|
|
*/
|
|
isBoardMigrated(boardId) {
|
|
return migratedBoards.has(boardId);
|
|
}
|
|
|
|
/**
|
|
* Migrate all attachments for a board
|
|
* @param {string} boardId - The board ID
|
|
*/
|
|
async migrateBoardAttachments(boardId) {
|
|
try {
|
|
// Check if board has already been migrated
|
|
if (this.isBoardMigrated(boardId)) {
|
|
console.log(`Board ${boardId} has already been migrated, skipping`);
|
|
return { success: true, message: 'Board already migrated' };
|
|
}
|
|
|
|
console.log(`Starting attachment migration for board: ${boardId}`);
|
|
|
|
// Get all attachments for the board
|
|
const attachments = Attachments.find({
|
|
'meta.boardId': boardId
|
|
}).fetch();
|
|
|
|
const totalAttachments = attachments.length;
|
|
let migratedCount = 0;
|
|
|
|
migrationStatus.set(`Migrating ${totalAttachments} attachments...`);
|
|
migrationProgress.set(0);
|
|
|
|
for (const attachment of attachments) {
|
|
try {
|
|
// Check if attachment needs migration
|
|
if (this.needsMigration(attachment)) {
|
|
await this.migrateAttachment(attachment);
|
|
this.migrationCache.set(attachment._id, true);
|
|
}
|
|
|
|
migratedCount++;
|
|
const progress = Math.round((migratedCount / totalAttachments) * 100);
|
|
migrationProgress.set(progress);
|
|
migrationStatus.set(`Migrated ${migratedCount}/${totalAttachments} attachments...`);
|
|
|
|
} catch (error) {
|
|
console.error(`Error migrating attachment ${attachment._id}:`, error);
|
|
}
|
|
}
|
|
|
|
// Update unconverted attachments list
|
|
const remainingUnconverted = this.getUnconvertedAttachments(boardId);
|
|
unconvertedAttachments.set(remainingUnconverted);
|
|
|
|
migrationStatus.set('Attachment migration completed');
|
|
migrationProgress.set(100);
|
|
|
|
// Mark board as migrated
|
|
migratedBoards.add(boardId);
|
|
console.log(`Attachment migration completed for board: ${boardId}`);
|
|
console.log(`Marked board ${boardId} as migrated`);
|
|
|
|
return { success: true, message: 'Migration completed' };
|
|
|
|
} catch (error) {
|
|
console.error(`Error migrating attachments for board ${boardId}:`, error);
|
|
migrationStatus.set(`Migration failed: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if an attachment needs migration
|
|
* @param {Object} attachment - The attachment object
|
|
* @returns {boolean} - True if attachment needs migration
|
|
*/
|
|
needsMigration(attachment) {
|
|
if (this.migrationCache.has(attachment._id)) {
|
|
return false; // Already migrated
|
|
}
|
|
|
|
// Check if attachment has old structure
|
|
return !attachment.meta ||
|
|
!attachment.meta.cardId ||
|
|
!attachment.meta.boardId ||
|
|
!attachment.meta.listId;
|
|
}
|
|
|
|
/**
|
|
* Migrate a single attachment
|
|
* @param {Object} attachment - The attachment object
|
|
*/
|
|
async migrateAttachment(attachment) {
|
|
try {
|
|
// Get the card to find board and list information
|
|
const card = ReactiveCache.getCard(attachment.cardId);
|
|
if (!card) {
|
|
console.warn(`Card not found for attachment ${attachment._id}`);
|
|
return;
|
|
}
|
|
|
|
const list = ReactiveCache.getList(card.listId);
|
|
if (!list) {
|
|
console.warn(`List not found for attachment ${attachment._id}`);
|
|
return;
|
|
}
|
|
|
|
// Update attachment with new structure
|
|
const updateData = {
|
|
meta: {
|
|
cardId: attachment.cardId,
|
|
boardId: list.boardId,
|
|
listId: card.listId,
|
|
userId: attachment.userId,
|
|
createdAt: attachment.createdAt || new Date(),
|
|
migratedAt: new Date()
|
|
}
|
|
};
|
|
|
|
// Preserve existing meta data if it exists
|
|
if (attachment.meta) {
|
|
updateData.meta = {
|
|
...attachment.meta,
|
|
...updateData.meta
|
|
};
|
|
}
|
|
|
|
Attachments.update(attachment._id, { $set: updateData });
|
|
|
|
console.log(`Migrated attachment ${attachment._id}`);
|
|
|
|
} catch (error) {
|
|
console.error(`Error migrating attachment ${attachment._id}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get unconverted attachments for a board
|
|
* @param {string} boardId - The board ID
|
|
* @returns {Array} - Array of unconverted attachments
|
|
*/
|
|
getUnconvertedAttachments(boardId) {
|
|
try {
|
|
const attachments = Attachments.find({
|
|
'meta.boardId': boardId
|
|
}).fetch();
|
|
|
|
return attachments.filter(attachment => this.needsMigration(attachment));
|
|
} catch (error) {
|
|
console.error('Error getting unconverted attachments:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get migration progress
|
|
* @param {string} boardId - The board ID
|
|
* @returns {Object} - Migration progress data
|
|
*/
|
|
getMigrationProgress(boardId) {
|
|
const progress = migrationProgress.get();
|
|
const status = migrationStatus.get();
|
|
const unconverted = this.getUnconvertedAttachments(boardId);
|
|
|
|
return {
|
|
progress,
|
|
status,
|
|
unconvertedAttachments: unconverted
|
|
};
|
|
}
|
|
}
|
|
|
|
const attachmentMigrationService = new AttachmentMigrationService();
|
|
|
|
// Meteor methods
|
|
Meteor.methods({
|
|
async 'attachmentMigration.migrateBoardAttachments'(boardId) {
|
|
check(boardId, String);
|
|
|
|
if (!this.userId) {
|
|
throw new Meteor.Error('not-authorized');
|
|
}
|
|
|
|
return await attachmentMigrationService.migrateBoardAttachments(boardId);
|
|
},
|
|
|
|
'attachmentMigration.getProgress'(boardId) {
|
|
check(boardId, String);
|
|
|
|
if (!this.userId) {
|
|
throw new Meteor.Error('not-authorized');
|
|
}
|
|
|
|
return attachmentMigrationService.getMigrationProgress(boardId);
|
|
},
|
|
|
|
'attachmentMigration.getUnconvertedAttachments'(boardId) {
|
|
check(boardId, String);
|
|
|
|
if (!this.userId) {
|
|
throw new Meteor.Error('not-authorized');
|
|
}
|
|
|
|
return attachmentMigrationService.getUnconvertedAttachments(boardId);
|
|
},
|
|
|
|
'attachmentMigration.isBoardMigrated'(boardId) {
|
|
check(boardId, String);
|
|
|
|
if (!this.userId) {
|
|
throw new Meteor.Error('not-authorized');
|
|
}
|
|
|
|
return attachmentMigrationService.isBoardMigrated(boardId);
|
|
}
|
|
});
|
|
|
|
export { attachmentMigrationService }; |