| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 | /** * Attachment Migration Manager * Handles migration of attachments from old structure to new structure * with UI feedback and spinners for unconverted attachments */import { ReactiveVar } from 'meteor/reactive-var';import { ReactiveCache } from '/imports/reactiveCache';// Reactive variables for attachment migration progressexport const attachmentMigrationProgress = new ReactiveVar(0);export const attachmentMigrationStatus = new ReactiveVar('');export const isMigratingAttachments = new ReactiveVar(false);export const unconvertedAttachments = new ReactiveVar([]);class AttachmentMigrationManager {  constructor() {    this.migrationCache = new Map(); // Cache migrated attachment IDs  }  /**   * Check if an attachment needs migration   * @param {string} attachmentId - The attachment ID to check   * @returns {boolean} - True if attachment needs migration   */  needsMigration(attachmentId) {    if (this.migrationCache.has(attachmentId)) {      return false; // Already migrated    }    try {      const attachment = ReactiveCache.getAttachment(attachmentId);      if (!attachment) return false;      // Check if attachment has old structure (no meta field or missing required fields)      return !attachment.meta ||              !attachment.meta.cardId ||              !attachment.meta.boardId ||             !attachment.meta.listId;    } catch (error) {      console.error('Error checking if attachment needs migration:', error);      return false;    }  }  /**   * Get all unconverted attachments for a board   * @param {string} boardId - The board ID   * @returns {Array} - Array of unconverted attachments   */  getUnconvertedAttachments(boardId) {    try {      const attachments = ReactiveCache.getAttachments({        'meta.boardId': boardId      });      return attachments.filter(attachment => this.needsMigration(attachment._id));    } catch (error) {      console.error('Error getting unconverted attachments:', error);      return [];    }  }  /**   * Start migration for attachments in a board   * @param {string} boardId - The board ID   */  async startAttachmentMigration(boardId) {    if (isMigratingAttachments.get()) {      return; // Already migrating    }    isMigratingAttachments.set(true);    attachmentMigrationStatus.set('Starting attachment migration...');    attachmentMigrationProgress.set(0);    try {      const unconverted = this.getUnconvertedAttachments(boardId);      unconvertedAttachments.set(unconverted);      if (unconverted.length === 0) {        attachmentMigrationStatus.set('All attachments are already migrated');        attachmentMigrationProgress.set(100);        isMigratingAttachments.set(false);        return;      }      // Start server-side migration      Meteor.call('attachmentMigration.migrateBoardAttachments', boardId, (error, result) => {        if (error) {          console.error('Failed to start attachment migration:', error);          attachmentMigrationStatus.set(`Migration failed: ${error.message}`);          isMigratingAttachments.set(false);        } else {          console.log('Attachment migration started for board:', boardId);          this.pollAttachmentMigrationProgress(boardId);        }      });    } catch (error) {      console.error('Error starting attachment migration:', error);      attachmentMigrationStatus.set(`Migration failed: ${error.message}`);      isMigratingAttachments.set(false);    }  }  /**   * Poll for attachment migration progress   * @param {string} boardId - The board ID   */  pollAttachmentMigrationProgress(boardId) {    const pollInterval = setInterval(() => {      Meteor.call('attachmentMigration.getProgress', boardId, (error, result) => {        if (error) {          console.error('Error getting migration progress:', error);          clearInterval(pollInterval);          isMigratingAttachments.set(false);          return;        }        if (result) {          attachmentMigrationProgress.set(result.progress);          attachmentMigrationStatus.set(result.status);          unconvertedAttachments.set(result.unconvertedAttachments || []);          // Stop polling if migration is complete          if (result.progress >= 100 || result.status === 'completed') {            clearInterval(pollInterval);            isMigratingAttachments.set(false);            this.migrationCache.clear(); // Clear cache to refresh data          }        }      });    }, 1000);  }  /**   * Check if an attachment is currently being migrated   * @param {string} attachmentId - The attachment ID   * @returns {boolean} - True if attachment is being migrated   */  isAttachmentBeingMigrated(attachmentId) {    const unconverted = unconvertedAttachments.get();    return unconverted.some(attachment => attachment._id === attachmentId);  }  /**   * Get migration status for an attachment   * @param {string} attachmentId - The attachment ID   * @returns {string} - Migration status ('migrated', 'migrating', 'unmigrated')   */  getAttachmentMigrationStatus(attachmentId) {    if (this.migrationCache.has(attachmentId)) {      return 'migrated';    }    if (this.isAttachmentBeingMigrated(attachmentId)) {      return 'migrating';    }    return this.needsMigration(attachmentId) ? 'unmigrated' : 'migrated';  }}export const attachmentMigrationManager = new AttachmentMigrationManager();
 |