attachmentMigration.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. /**
  2. * Server-side Attachment Migration System
  3. * Handles migration of attachments from old structure to new structure
  4. */
  5. import { Meteor } from 'meteor/meteor';
  6. import { ReactiveVar } from 'meteor/reactive-var';
  7. import { ReactiveCache } from '/imports/reactiveCache';
  8. // Reactive variables for tracking migration progress
  9. const migrationProgress = new ReactiveVar(0);
  10. const migrationStatus = new ReactiveVar('');
  11. const unconvertedAttachments = new ReactiveVar([]);
  12. class AttachmentMigrationService {
  13. constructor() {
  14. this.migrationCache = new Map();
  15. }
  16. /**
  17. * Migrate all attachments for a board
  18. * @param {string} boardId - The board ID
  19. */
  20. async migrateBoardAttachments(boardId) {
  21. try {
  22. console.log(`Starting attachment migration for board: ${boardId}`);
  23. // Get all attachments for the board
  24. const attachments = Attachments.find({
  25. 'meta.boardId': boardId
  26. }).fetch();
  27. const totalAttachments = attachments.length;
  28. let migratedCount = 0;
  29. migrationStatus.set(`Migrating ${totalAttachments} attachments...`);
  30. migrationProgress.set(0);
  31. for (const attachment of attachments) {
  32. try {
  33. // Check if attachment needs migration
  34. if (this.needsMigration(attachment)) {
  35. await this.migrateAttachment(attachment);
  36. this.migrationCache.set(attachment._id, true);
  37. }
  38. migratedCount++;
  39. const progress = Math.round((migratedCount / totalAttachments) * 100);
  40. migrationProgress.set(progress);
  41. migrationStatus.set(`Migrated ${migratedCount}/${totalAttachments} attachments...`);
  42. } catch (error) {
  43. console.error(`Error migrating attachment ${attachment._id}:`, error);
  44. }
  45. }
  46. // Update unconverted attachments list
  47. const remainingUnconverted = this.getUnconvertedAttachments(boardId);
  48. unconvertedAttachments.set(remainingUnconverted);
  49. migrationStatus.set('Attachment migration completed');
  50. migrationProgress.set(100);
  51. console.log(`Attachment migration completed for board: ${boardId}`);
  52. } catch (error) {
  53. console.error(`Error migrating attachments for board ${boardId}:`, error);
  54. migrationStatus.set(`Migration failed: ${error.message}`);
  55. throw error;
  56. }
  57. }
  58. /**
  59. * Check if an attachment needs migration
  60. * @param {Object} attachment - The attachment object
  61. * @returns {boolean} - True if attachment needs migration
  62. */
  63. needsMigration(attachment) {
  64. if (this.migrationCache.has(attachment._id)) {
  65. return false; // Already migrated
  66. }
  67. // Check if attachment has old structure
  68. return !attachment.meta ||
  69. !attachment.meta.cardId ||
  70. !attachment.meta.boardId ||
  71. !attachment.meta.listId;
  72. }
  73. /**
  74. * Migrate a single attachment
  75. * @param {Object} attachment - The attachment object
  76. */
  77. async migrateAttachment(attachment) {
  78. try {
  79. // Get the card to find board and list information
  80. const card = ReactiveCache.getCard(attachment.cardId);
  81. if (!card) {
  82. console.warn(`Card not found for attachment ${attachment._id}`);
  83. return;
  84. }
  85. const list = ReactiveCache.getList(card.listId);
  86. if (!list) {
  87. console.warn(`List not found for attachment ${attachment._id}`);
  88. return;
  89. }
  90. // Update attachment with new structure
  91. const updateData = {
  92. meta: {
  93. cardId: attachment.cardId,
  94. boardId: list.boardId,
  95. listId: card.listId,
  96. userId: attachment.userId,
  97. createdAt: attachment.createdAt || new Date(),
  98. migratedAt: new Date()
  99. }
  100. };
  101. // Preserve existing meta data if it exists
  102. if (attachment.meta) {
  103. updateData.meta = {
  104. ...attachment.meta,
  105. ...updateData.meta
  106. };
  107. }
  108. Attachments.update(attachment._id, { $set: updateData });
  109. console.log(`Migrated attachment ${attachment._id}`);
  110. } catch (error) {
  111. console.error(`Error migrating attachment ${attachment._id}:`, error);
  112. throw error;
  113. }
  114. }
  115. /**
  116. * Get unconverted attachments for a board
  117. * @param {string} boardId - The board ID
  118. * @returns {Array} - Array of unconverted attachments
  119. */
  120. getUnconvertedAttachments(boardId) {
  121. try {
  122. const attachments = Attachments.find({
  123. 'meta.boardId': boardId
  124. }).fetch();
  125. return attachments.filter(attachment => this.needsMigration(attachment));
  126. } catch (error) {
  127. console.error('Error getting unconverted attachments:', error);
  128. return [];
  129. }
  130. }
  131. /**
  132. * Get migration progress
  133. * @param {string} boardId - The board ID
  134. * @returns {Object} - Migration progress data
  135. */
  136. getMigrationProgress(boardId) {
  137. const progress = migrationProgress.get();
  138. const status = migrationStatus.get();
  139. const unconverted = this.getUnconvertedAttachments(boardId);
  140. return {
  141. progress,
  142. status,
  143. unconvertedAttachments: unconverted
  144. };
  145. }
  146. }
  147. const attachmentMigrationService = new AttachmentMigrationService();
  148. // Meteor methods
  149. Meteor.methods({
  150. 'attachmentMigration.migrateBoardAttachments'(boardId) {
  151. if (!this.userId) {
  152. throw new Meteor.Error('not-authorized');
  153. }
  154. return attachmentMigrationService.migrateBoardAttachments(boardId);
  155. },
  156. 'attachmentMigration.getProgress'(boardId) {
  157. if (!this.userId) {
  158. throw new Meteor.Error('not-authorized');
  159. }
  160. return attachmentMigrationService.getMigrationProgress(boardId);
  161. },
  162. 'attachmentMigration.getUnconvertedAttachments'(boardId) {
  163. if (!this.userId) {
  164. throw new Meteor.Error('not-authorized');
  165. }
  166. return attachmentMigrationService.getUnconvertedAttachments(boardId);
  167. }
  168. });
  169. export { attachmentMigrationService };