attachmentMigrationManager.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. /**
  2. * Attachment Migration Manager
  3. * Handles migration of attachments from old structure to new structure
  4. * with UI feedback and spinners for unconverted attachments
  5. */
  6. import { ReactiveVar } from 'meteor/reactive-var';
  7. import { ReactiveCache } from '/imports/reactiveCache';
  8. // Reactive variables for attachment migration progress
  9. export const attachmentMigrationProgress = new ReactiveVar(0);
  10. export const attachmentMigrationStatus = new ReactiveVar('');
  11. export const isMigratingAttachments = new ReactiveVar(false);
  12. export const unconvertedAttachments = new ReactiveVar([]);
  13. class AttachmentMigrationManager {
  14. constructor() {
  15. this.migrationCache = new Map(); // Cache migrated attachment IDs
  16. }
  17. /**
  18. * Check if an attachment needs migration
  19. * @param {string} attachmentId - The attachment ID to check
  20. * @returns {boolean} - True if attachment needs migration
  21. */
  22. needsMigration(attachmentId) {
  23. if (this.migrationCache.has(attachmentId)) {
  24. return false; // Already migrated
  25. }
  26. try {
  27. const attachment = ReactiveCache.getAttachment(attachmentId);
  28. if (!attachment) return false;
  29. // Check if attachment has old structure (no meta field or missing required fields)
  30. return !attachment.meta ||
  31. !attachment.meta.cardId ||
  32. !attachment.meta.boardId ||
  33. !attachment.meta.listId;
  34. } catch (error) {
  35. console.error('Error checking if attachment needs migration:', error);
  36. return false;
  37. }
  38. }
  39. /**
  40. * Get all unconverted attachments for a board
  41. * @param {string} boardId - The board ID
  42. * @returns {Array} - Array of unconverted attachments
  43. */
  44. getUnconvertedAttachments(boardId) {
  45. try {
  46. const attachments = ReactiveCache.getAttachments({
  47. 'meta.boardId': boardId
  48. });
  49. return attachments.filter(attachment => this.needsMigration(attachment._id));
  50. } catch (error) {
  51. console.error('Error getting unconverted attachments:', error);
  52. return [];
  53. }
  54. }
  55. /**
  56. * Start migration for attachments in a board
  57. * @param {string} boardId - The board ID
  58. */
  59. async startAttachmentMigration(boardId) {
  60. if (isMigratingAttachments.get()) {
  61. return; // Already migrating
  62. }
  63. isMigratingAttachments.set(true);
  64. attachmentMigrationStatus.set('Starting attachment migration...');
  65. attachmentMigrationProgress.set(0);
  66. try {
  67. const unconverted = this.getUnconvertedAttachments(boardId);
  68. unconvertedAttachments.set(unconverted);
  69. if (unconverted.length === 0) {
  70. attachmentMigrationStatus.set('All attachments are already migrated');
  71. attachmentMigrationProgress.set(100);
  72. isMigratingAttachments.set(false);
  73. return;
  74. }
  75. // Start server-side migration
  76. Meteor.call('attachmentMigration.migrateBoardAttachments', boardId, (error, result) => {
  77. if (error) {
  78. console.error('Failed to start attachment migration:', error);
  79. attachmentMigrationStatus.set(`Migration failed: ${error.message}`);
  80. isMigratingAttachments.set(false);
  81. } else {
  82. console.log('Attachment migration started for board:', boardId);
  83. this.pollAttachmentMigrationProgress(boardId);
  84. }
  85. });
  86. } catch (error) {
  87. console.error('Error starting attachment migration:', error);
  88. attachmentMigrationStatus.set(`Migration failed: ${error.message}`);
  89. isMigratingAttachments.set(false);
  90. }
  91. }
  92. /**
  93. * Poll for attachment migration progress
  94. * @param {string} boardId - The board ID
  95. */
  96. pollAttachmentMigrationProgress(boardId) {
  97. const pollInterval = setInterval(() => {
  98. Meteor.call('attachmentMigration.getProgress', boardId, (error, result) => {
  99. if (error) {
  100. console.error('Error getting migration progress:', error);
  101. clearInterval(pollInterval);
  102. isMigratingAttachments.set(false);
  103. return;
  104. }
  105. if (result) {
  106. attachmentMigrationProgress.set(result.progress);
  107. attachmentMigrationStatus.set(result.status);
  108. unconvertedAttachments.set(result.unconvertedAttachments || []);
  109. // Stop polling if migration is complete
  110. if (result.progress >= 100 || result.status === 'completed') {
  111. clearInterval(pollInterval);
  112. isMigratingAttachments.set(false);
  113. this.migrationCache.clear(); // Clear cache to refresh data
  114. }
  115. }
  116. });
  117. }, 1000);
  118. }
  119. /**
  120. * Check if an attachment is currently being migrated
  121. * @param {string} attachmentId - The attachment ID
  122. * @returns {boolean} - True if attachment is being migrated
  123. */
  124. isAttachmentBeingMigrated(attachmentId) {
  125. const unconverted = unconvertedAttachments.get();
  126. return unconverted.some(attachment => attachment._id === attachmentId);
  127. }
  128. /**
  129. * Get migration status for an attachment
  130. * @param {string} attachmentId - The attachment ID
  131. * @returns {string} - Migration status ('migrated', 'migrating', 'unmigrated')
  132. */
  133. getAttachmentMigrationStatus(attachmentId) {
  134. if (this.migrationCache.has(attachmentId)) {
  135. return 'migrated';
  136. }
  137. if (this.isAttachmentBeingMigrated(attachmentId)) {
  138. return 'migrating';
  139. }
  140. return this.needsMigration(attachmentId) ? 'unmigrated' : 'migrated';
  141. }
  142. }
  143. export const attachmentMigrationManager = new AttachmentMigrationManager();