| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 | /** * 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 progressconst migrationProgress = new ReactiveVar(0);const migrationStatus = new ReactiveVar('');const unconvertedAttachments = new ReactiveVar([]);// Track migrated boards on server sideconst 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 methodsMeteor.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 };
 |