Browse Source

Fixed migrations.

Thanks to xet7 !
Lauri Ojansivu 1 week ago
parent
commit
63c314ca18

+ 27 - 13
client/components/boards/boardBody.js

@@ -5,7 +5,6 @@ import { boardConverter } from '/client/lib/boardConverter';
 import { migrationManager } from '/client/lib/migrationManager';
 import { attachmentMigrationManager } from '/client/lib/attachmentMigrationManager';
 import { Swimlanes } from '/models/swimlanes';
-import { Lists } from '/models/lists';
 
 const subManager = new SubsManager();
 const { calculateIndex } = Utils;
@@ -88,21 +87,26 @@ BlazeComponent.extendComponent({
       }
 
       // Check if board needs conversion (for old structure)
-      const needsConversion = boardConverter.needsConversion(boardId);
-      
-      if (needsConversion) {
-        this.isConverting.set(true);
-        const success = await boardConverter.convertBoard(boardId);
-        this.isConverting.set(false);
+      if (boardConverter.isBoardConverted(boardId)) {
+        console.log(`Board ${boardId} has already been converted, skipping conversion`);
+        this.isBoardReady.set(true);
+      } else {
+        const needsConversion = boardConverter.needsConversion(boardId);
         
-        if (success) {
-          this.isBoardReady.set(true);
+        if (needsConversion) {
+          this.isConverting.set(true);
+          const success = await boardConverter.convertBoard(boardId);
+          this.isConverting.set(false);
+          
+          if (success) {
+            this.isBoardReady.set(true);
+          } else {
+            console.error('Board conversion failed, setting ready to true anyway');
+            this.isBoardReady.set(true); // Still show board even if conversion failed
+          }
         } else {
-          console.error('Board conversion failed, setting ready to true anyway');
-          this.isBoardReady.set(true); // Still show board even if conversion failed
+          this.isBoardReady.set(true);
         }
-      } else {
-        this.isBoardReady.set(true);
       }
 
       // Start attachment migration in background if needed
@@ -132,12 +136,22 @@ BlazeComponent.extendComponent({
 
   async startAttachmentMigrationIfNeeded(boardId) {
     try {
+      // Check if board has already been migrated
+      if (attachmentMigrationManager.isBoardMigrated(boardId)) {
+        console.log(`Board ${boardId} has already been migrated, skipping`);
+        return;
+      }
+
       // Check if there are unconverted attachments
       const unconvertedAttachments = attachmentMigrationManager.getUnconvertedAttachments(boardId);
       
       if (unconvertedAttachments.length > 0) {
         console.log(`Starting attachment migration for ${unconvertedAttachments.length} attachments in board ${boardId}`);
         await attachmentMigrationManager.startAttachmentMigration(boardId);
+      } else {
+        // No attachments to migrate, mark board as migrated
+        // This will be handled by the migration manager itself
+        console.log(`Board ${boardId} has no attachments to migrate`);
       }
     } catch (error) {
       console.error('Error starting attachment migration:', error);

+ 51 - 1
client/lib/attachmentMigrationManager.js

@@ -13,9 +13,13 @@ export const attachmentMigrationStatus = new ReactiveVar('');
 export const isMigratingAttachments = new ReactiveVar(false);
 export const unconvertedAttachments = new ReactiveVar([]);
 
+// Global tracking of migrated boards (persistent across component reinitializations)
+const globalMigratedBoards = new Set();
+
 class AttachmentMigrationManager {
   constructor() {
     this.migrationCache = new Map(); // Cache migrated attachment IDs
+    this.migratedBoards = new Set(); // Track boards that have been migrated
   }
 
   /**
@@ -43,6 +47,33 @@ class AttachmentMigrationManager {
     }
   }
 
+  /**
+   * Check if a board has been migrated
+   * @param {string} boardId - The board ID
+   * @returns {boolean} - True if board has been migrated
+   */
+  isBoardMigrated(boardId) {
+    return globalMigratedBoards.has(boardId);
+  }
+
+  /**
+   * Check if a board has been migrated (server-side check)
+   * @param {string} boardId - The board ID
+   * @returns {Promise<boolean>} - True if board has been migrated
+   */
+  async isBoardMigratedServer(boardId) {
+    return new Promise((resolve) => {
+      Meteor.call('attachmentMigration.isBoardMigrated', boardId, (error, result) => {
+        if (error) {
+          console.error('Error checking board migration status:', error);
+          resolve(false);
+        } else {
+          resolve(result);
+        }
+      });
+    });
+  }
+
   /**
    * Get all unconverted attachments for a board
    * @param {string} boardId - The board ID
@@ -70,6 +101,20 @@ class AttachmentMigrationManager {
       return; // Already migrating
     }
 
+    // Check if this board has already been migrated (client-side check first)
+    if (globalMigratedBoards.has(boardId)) {
+      console.log(`Board ${boardId} has already been migrated (client-side), skipping`);
+      return;
+    }
+
+    // Double-check with server-side check
+    const serverMigrated = await this.isBoardMigratedServer(boardId);
+    if (serverMigrated) {
+      console.log(`Board ${boardId} has already been migrated (server-side), skipping`);
+      globalMigratedBoards.add(boardId); // Sync client-side tracking
+      return;
+    }
+
     isMigratingAttachments.set(true);
     attachmentMigrationStatus.set('Starting attachment migration...');
     attachmentMigrationProgress.set(0);
@@ -82,6 +127,8 @@ class AttachmentMigrationManager {
         attachmentMigrationStatus.set('All attachments are already migrated');
         attachmentMigrationProgress.set(100);
         isMigratingAttachments.set(false);
+        globalMigratedBoards.add(boardId); // Mark board as migrated
+        console.log(`Board ${boardId} has no attachments to migrate, marked as migrated`);
         return;
       }
 
@@ -89,7 +136,8 @@ class AttachmentMigrationManager {
       Meteor.call('attachmentMigration.migrateBoardAttachments', boardId, (error, result) => {
         if (error) {
           console.error('Failed to start attachment migration:', error);
-          attachmentMigrationStatus.set(`Migration failed: ${error.message}`);
+          const errorMessage = error.message || error.reason || error.toString();
+          attachmentMigrationStatus.set(`Migration failed: ${errorMessage}`);
           isMigratingAttachments.set(false);
         } else {
           console.log('Attachment migration started for board:', boardId);
@@ -128,6 +176,8 @@ class AttachmentMigrationManager {
             clearInterval(pollInterval);
             isMigratingAttachments.set(false);
             this.migrationCache.clear(); // Clear cache to refresh data
+            globalMigratedBoards.add(boardId); // Mark board as migrated
+            console.log(`Board ${boardId} migration completed and marked as migrated`);
           }
         }
       });

+ 23 - 1
client/lib/boardConverter.js

@@ -13,11 +13,23 @@ export const conversionStatus = new ReactiveVar('');
 export const conversionEstimatedTime = new ReactiveVar('');
 export const isConverting = new ReactiveVar(false);
 
+// Global tracking of converted boards (persistent across component reinitializations)
+const globalConvertedBoards = new Set();
+
 class BoardConverter {
   constructor() {
     this.conversionCache = new Map(); // Cache converted board IDs
   }
 
+  /**
+   * Check if a board has been converted
+   * @param {string} boardId - The board ID
+   * @returns {boolean} - True if board has been converted
+   */
+  isBoardConverted(boardId) {
+    return globalConvertedBoards.has(boardId);
+  }
+
   /**
    * Check if a board needs conversion
    * @param {string} boardId - The board ID to check
@@ -55,6 +67,12 @@ class BoardConverter {
    * @returns {Promise<boolean>} - True if conversion was successful
    */
   async convertBoard(boardId) {
+    // Check if board has already been converted
+    if (this.isBoardConverted(boardId)) {
+      console.log(`Board ${boardId} has already been converted, skipping`);
+      return true;
+    }
+
     if (this.conversionCache.has(boardId)) {
       return true; // Already converted
     }
@@ -87,7 +105,9 @@ class BoardConverter {
 
       if (listsToConvert.length === 0) {
         this.conversionCache.set(boardId, true);
+        globalConvertedBoards.add(boardId); // Mark board as converted
         isConverting.set(false);
+        console.log(`Board ${boardId} has no lists to convert, marked as converted`);
         return true;
       }
 
@@ -124,9 +144,11 @@ class BoardConverter {
 
       // Mark as converted
       this.conversionCache.set(boardId, true);
+      globalConvertedBoards.add(boardId); // Mark board as converted
       
       conversionStatus.set('Board conversion completed!');
       conversionProgress.set(100);
+      console.log(`Board ${boardId} conversion completed and marked as converted`);
       
       // Clear status after a delay
       setTimeout(() => {
@@ -159,7 +181,7 @@ class BoardConverter {
 
     // Update lists in batch
     updates.forEach(update => {
-      ReactiveCache.getCollection('lists').update(update._id, {
+      Lists.update(update._id, {
         $set: { swimlaneId: update.swimlaneId }
       });
     });

+ 168 - 168
package-lock.json

@@ -667,79 +667,79 @@
       "dev": true
     },
     "@smithy/abort-controller": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.0.tgz",
-      "integrity": "sha512-PLUYa+SUKOEZtXFURBu/CNxlsxfaFGxSBPcStL13KpVeVWIfdezWyDqkz7iDLmwnxojXD0s5KzuB5HGHvt4Aeg==",
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.1.tgz",
+      "integrity": "sha512-OvVe992TXYHR7QpYebmtw+/MF5AP9vU0fjfyfW1VmNYeA/dfibLhN13xrzIj+EO0HYMPur5lUIB9hRZ7IhjLDQ==",
       "optional": true,
       "requires": {
-        "@smithy/types": "^4.6.0",
+        "@smithy/types": "^4.7.0",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/config-resolver": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.3.0.tgz",
-      "integrity": "sha512-9oH+n8AVNiLPK/iK/agOsoWfrKZ3FGP3502tkksd6SRsKMYiu7AFX0YXo6YBADdsAj7C+G/aLKdsafIJHxuCkQ==",
+      "version": "4.3.1",
+      "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.3.1.tgz",
+      "integrity": "sha512-tWDwrWy37CDVGeaP8AIGZPFL2RoFtmd5Y+nTzLw5qroXNedT2S66EY2d+XzB1zxulCd6nfDXnAQu4auq90aj5Q==",
       "optional": true,
       "requires": {
-        "@smithy/node-config-provider": "^4.3.0",
-        "@smithy/types": "^4.6.0",
+        "@smithy/node-config-provider": "^4.3.1",
+        "@smithy/types": "^4.7.0",
         "@smithy/util-config-provider": "^4.2.0",
-        "@smithy/util-middleware": "^4.2.0",
+        "@smithy/util-middleware": "^4.2.1",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/core": {
-      "version": "3.15.0",
-      "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.15.0.tgz",
-      "integrity": "sha512-VJWncXgt+ExNn0U2+Y7UywuATtRYaodGQKFo9mDyh70q+fJGedfrqi2XuKU1BhiLeXgg6RZrW7VEKfeqFhHAJA==",
+      "version": "3.16.0",
+      "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.16.0.tgz",
+      "integrity": "sha512-T6eJ+yhnCP5plm6aEaenUpxkHTd5zVCKpyWAbP4ekJ7R5wSmKQjmvQIA58CXB1sgrwaYZJXOJMeRtpghxP7n1g==",
       "optional": true,
       "requires": {
-        "@smithy/middleware-serde": "^4.2.0",
-        "@smithy/protocol-http": "^5.3.0",
-        "@smithy/types": "^4.6.0",
+        "@smithy/middleware-serde": "^4.2.1",
+        "@smithy/protocol-http": "^5.3.1",
+        "@smithy/types": "^4.7.0",
         "@smithy/util-base64": "^4.3.0",
         "@smithy/util-body-length-browser": "^4.2.0",
-        "@smithy/util-middleware": "^4.2.0",
-        "@smithy/util-stream": "^4.5.0",
+        "@smithy/util-middleware": "^4.2.1",
+        "@smithy/util-stream": "^4.5.1",
         "@smithy/util-utf8": "^4.2.0",
         "@smithy/uuid": "^1.1.0",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/credential-provider-imds": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.0.tgz",
-      "integrity": "sha512-SOhFVvFH4D5HJZytb0bLKxCrSnwcqPiNlrw+S4ZXjMnsC+o9JcUQzbZOEQcA8yv9wJFNhfsUiIUKiEnYL68Big==",
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.1.tgz",
+      "integrity": "sha512-Y7Gq6xZvAUJOf60prfpknyKIJoIU89q/t6Cr4AWLYZBaaIhEdWJRIWvLqiqL5Hb6iK8btorKHI8jT6ZuQB+BVg==",
       "optional": true,
       "requires": {
-        "@smithy/node-config-provider": "^4.3.0",
-        "@smithy/property-provider": "^4.2.0",
-        "@smithy/types": "^4.6.0",
-        "@smithy/url-parser": "^4.2.0",
+        "@smithy/node-config-provider": "^4.3.1",
+        "@smithy/property-provider": "^4.2.1",
+        "@smithy/types": "^4.7.0",
+        "@smithy/url-parser": "^4.2.1",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/fetch-http-handler": {
-      "version": "5.3.1",
-      "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.1.tgz",
-      "integrity": "sha512-3AvYYbB+Dv5EPLqnJIAgYw/9+WzeBiUYS8B+rU0pHq5NMQMvrZmevUROS4V2GAt0jEOn9viBzPLrZE+riTNd5Q==",
+      "version": "5.3.2",
+      "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.2.tgz",
+      "integrity": "sha512-3CXDhyjl6nz0na+te37f+aGqmDwJeyeo9GK7ThPStoa/ruZcUm17UPRC4xJvbm8Z4JCvbnh54mRCFtiR/IzXjw==",
       "optional": true,
       "requires": {
-        "@smithy/protocol-http": "^5.3.0",
-        "@smithy/querystring-builder": "^4.2.0",
-        "@smithy/types": "^4.6.0",
+        "@smithy/protocol-http": "^5.3.1",
+        "@smithy/querystring-builder": "^4.2.1",
+        "@smithy/types": "^4.7.0",
         "@smithy/util-base64": "^4.3.0",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/hash-node": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.0.tgz",
-      "integrity": "sha512-ugv93gOhZGysTctZh9qdgng8B+xO0cj+zN0qAZ+Sgh7qTQGPOJbMdIuyP89KNfUyfAqFSNh5tMvC+h2uCpmTtA==",
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.1.tgz",
+      "integrity": "sha512-eqyR+zua9LI8K0NhYMUEh8HDy7zaT1gRuB3d1kNIKeSG9nc2JxNbKXYNRdmIvAWG3wJyl9uUWPs+H3k8uDes1Q==",
       "optional": true,
       "requires": {
-        "@smithy/types": "^4.6.0",
+        "@smithy/types": "^4.7.0",
         "@smithy/util-buffer-from": "^4.2.0",
         "@smithy/util-utf8": "^4.2.0",
         "tslib": "^2.6.2"
@@ -767,12 +767,12 @@
       }
     },
     "@smithy/invalid-dependency": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.0.tgz",
-      "integrity": "sha512-ZmK5X5fUPAbtvRcUPtk28aqIClVhbfcmfoS4M7UQBTnDdrNxhsrxYVv0ZEl5NaPSyExsPWqL4GsPlRvtlwg+2A==",
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.1.tgz",
+      "integrity": "sha512-mGH4fyQwVun9jtAbNQjU5Dt2pItOM1ULQrceaISyyu8pEjreBjyC0T5BN+zU2ltqKF3NefjQ+ApfoAk1w1UplQ==",
       "optional": true,
       "requires": {
-        "@smithy/types": "^4.6.0",
+        "@smithy/types": "^4.7.0",
         "tslib": "^2.6.2"
       }
     },
@@ -786,166 +786,166 @@
       }
     },
     "@smithy/middleware-content-length": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.0.tgz",
-      "integrity": "sha512-6ZAnwrXFecrA4kIDOcz6aLBhU5ih2is2NdcZtobBDSdSHtE9a+MThB5uqyK4XXesdOCvOcbCm2IGB95birTSOQ==",
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.1.tgz",
+      "integrity": "sha512-+V6TdTAcS/dGILfe4hZP5lVnCuUvcX05yj+GihbOpy/ylGzUYhE/oYmv4vU33vMj5rfpdcfuyuESHkJTTRDXGw==",
       "optional": true,
       "requires": {
-        "@smithy/protocol-http": "^5.3.0",
-        "@smithy/types": "^4.6.0",
+        "@smithy/protocol-http": "^5.3.1",
+        "@smithy/types": "^4.7.0",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/middleware-endpoint": {
-      "version": "4.3.1",
-      "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.1.tgz",
-      "integrity": "sha512-JtM4SjEgImLEJVXdsbvWHYiJ9dtuKE8bqLlvkvGi96LbejDL6qnVpVxEFUximFodoQbg0Gnkyff9EKUhFhVJFw==",
+      "version": "4.3.2",
+      "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.2.tgz",
+      "integrity": "sha512-3UP7E5SD0rF6cQEWVMxfbMvpC0fv9fTbusMQfKAXlff5g7L2tn2kspiiGX+nqyK78FV2kP/O2WS7rbIvhfw6/Q==",
       "optional": true,
       "requires": {
-        "@smithy/core": "^3.15.0",
-        "@smithy/middleware-serde": "^4.2.0",
-        "@smithy/node-config-provider": "^4.3.0",
-        "@smithy/shared-ini-file-loader": "^4.3.0",
-        "@smithy/types": "^4.6.0",
-        "@smithy/url-parser": "^4.2.0",
-        "@smithy/util-middleware": "^4.2.0",
+        "@smithy/core": "^3.16.0",
+        "@smithy/middleware-serde": "^4.2.1",
+        "@smithy/node-config-provider": "^4.3.1",
+        "@smithy/shared-ini-file-loader": "^4.3.1",
+        "@smithy/types": "^4.7.0",
+        "@smithy/url-parser": "^4.2.1",
+        "@smithy/util-middleware": "^4.2.1",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/middleware-retry": {
-      "version": "4.4.1",
-      "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.1.tgz",
-      "integrity": "sha512-wXxS4ex8cJJteL0PPQmWYkNi9QKDWZIpsndr0wZI2EL+pSSvA/qqxXU60gBOJoIc2YgtZSWY/PE86qhKCCKP1w==",
+      "version": "4.4.2",
+      "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.2.tgz",
+      "integrity": "sha512-cuPmDJi7AE7PkdfeqJaHKBR33mXCl1MPxrboQDR/zZUo9u947m0gnYRd25NTSRER5LZpNDCvVTSedeAC9dHckA==",
       "optional": true,
       "requires": {
-        "@smithy/node-config-provider": "^4.3.0",
-        "@smithy/protocol-http": "^5.3.0",
-        "@smithy/service-error-classification": "^4.2.0",
-        "@smithy/smithy-client": "^4.7.1",
-        "@smithy/types": "^4.6.0",
-        "@smithy/util-middleware": "^4.2.0",
-        "@smithy/util-retry": "^4.2.0",
+        "@smithy/node-config-provider": "^4.3.1",
+        "@smithy/protocol-http": "^5.3.1",
+        "@smithy/service-error-classification": "^4.2.1",
+        "@smithy/smithy-client": "^4.8.0",
+        "@smithy/types": "^4.7.0",
+        "@smithy/util-middleware": "^4.2.1",
+        "@smithy/util-retry": "^4.2.1",
         "@smithy/uuid": "^1.1.0",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/middleware-serde": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.0.tgz",
-      "integrity": "sha512-rpTQ7D65/EAbC6VydXlxjvbifTf4IH+sADKg6JmAvhkflJO2NvDeyU9qsWUNBelJiQFcXKejUHWRSdmpJmEmiw==",
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.1.tgz",
+      "integrity": "sha512-0J1EDeGGBNz0h0R/UGKudF7gBMS+UMJEWuNPY1hDV/RTyyKgBfsKH87nKCeCSB81EgjnBDFsnfXD2ZMRCfIPWA==",
       "optional": true,
       "requires": {
-        "@smithy/protocol-http": "^5.3.0",
-        "@smithy/types": "^4.6.0",
+        "@smithy/protocol-http": "^5.3.1",
+        "@smithy/types": "^4.7.0",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/middleware-stack": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.0.tgz",
-      "integrity": "sha512-G5CJ//eqRd9OARrQu9MK1H8fNm2sMtqFh6j8/rPozhEL+Dokpvi1Og+aCixTuwDAGZUkJPk6hJT5jchbk/WCyg==",
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.1.tgz",
+      "integrity": "sha512-gWKgBqYYrcdtkEMzN8hEtypab7zgU4VVZHSwURAR5YGrvGJxbBh5mC9RPmVWS7TZxr/vB4yMKfxEQTrYRKRQ3Q==",
       "optional": true,
       "requires": {
-        "@smithy/types": "^4.6.0",
+        "@smithy/types": "^4.7.0",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/node-config-provider": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.0.tgz",
-      "integrity": "sha512-5QgHNuWdT9j9GwMPPJCKxy2KDxZ3E5l4M3/5TatSZrqYVoEiqQrDfAq8I6KWZw7RZOHtVtCzEPdYz7rHZixwcA==",
+      "version": "4.3.1",
+      "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.1.tgz",
+      "integrity": "sha512-Ap8Wd95HCrWRktMAZNc0AVzdPdUSPHsG59+DMe+4aH74FLDnVTo/7XDcRhSkSZCHeDjaDtzAh5OvnHOE0VHwUg==",
       "optional": true,
       "requires": {
-        "@smithy/property-provider": "^4.2.0",
-        "@smithy/shared-ini-file-loader": "^4.3.0",
-        "@smithy/types": "^4.6.0",
+        "@smithy/property-provider": "^4.2.1",
+        "@smithy/shared-ini-file-loader": "^4.3.1",
+        "@smithy/types": "^4.7.0",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/node-http-handler": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.3.0.tgz",
-      "integrity": "sha512-RHZ/uWCmSNZ8cneoWEVsVwMZBKy/8123hEpm57vgGXA3Irf/Ja4v9TVshHK2ML5/IqzAZn0WhINHOP9xl+Qy6Q==",
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.0.tgz",
+      "integrity": "sha512-E00fuesARqnmdc1vR4qurQjQH+QWcsKjmM6kYoJBWjxgqNfp1WHc1SwfC18EdVaYamgctxyXV6kWhHmanhYgCg==",
       "optional": true,
       "requires": {
-        "@smithy/abort-controller": "^4.2.0",
-        "@smithy/protocol-http": "^5.3.0",
-        "@smithy/querystring-builder": "^4.2.0",
-        "@smithy/types": "^4.6.0",
+        "@smithy/abort-controller": "^4.2.1",
+        "@smithy/protocol-http": "^5.3.1",
+        "@smithy/querystring-builder": "^4.2.1",
+        "@smithy/types": "^4.7.0",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/property-provider": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.0.tgz",
-      "integrity": "sha512-rV6wFre0BU6n/tx2Ztn5LdvEdNZ2FasQbPQmDOPfV9QQyDmsCkOAB0osQjotRCQg+nSKFmINhyda0D3AnjSBJw==",
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.1.tgz",
+      "integrity": "sha512-2zthf6j/u4XV3nRvulJgQsZdAs9xNf7dJPE5+Wvrx4yAsNrmtchadydASqRLXEw67ovl8c+HFa58QEXD/jUMSg==",
       "optional": true,
       "requires": {
-        "@smithy/types": "^4.6.0",
+        "@smithy/types": "^4.7.0",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/protocol-http": {
-      "version": "5.3.0",
-      "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.0.tgz",
-      "integrity": "sha512-6POSYlmDnsLKb7r1D3SVm7RaYW6H1vcNcTWGWrF7s9+2noNYvUsm7E4tz5ZQ9HXPmKn6Hb67pBDRIjrT4w/d7Q==",
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.1.tgz",
+      "integrity": "sha512-DqbfSgeZC0qo3/3fLgr5UEdOE7/o/VlVOt6LtpShwVcw3PIoqQMRCUTzMpJ0keAVb86Cl1w5YtW7uDUzeNMMLA==",
       "optional": true,
       "requires": {
-        "@smithy/types": "^4.6.0",
+        "@smithy/types": "^4.7.0",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/querystring-builder": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.0.tgz",
-      "integrity": "sha512-Q4oFD0ZmI8yJkiPPeGUITZj++4HHYCW3pYBYfIobUCkYpI6mbkzmG1MAQQ3lJYYWj3iNqfzOenUZu+jqdPQ16A==",
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.1.tgz",
+      "integrity": "sha512-2Qf5x7Afn6ofV3XLYL9+oaOwWK2FUC/LLTarex0SaXEKctVdzCdOOzEfaAZJSwSSiYqFWF6e2r0m7PFDzA44fA==",
       "optional": true,
       "requires": {
-        "@smithy/types": "^4.6.0",
+        "@smithy/types": "^4.7.0",
         "@smithy/util-uri-escape": "^4.2.0",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/querystring-parser": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.0.tgz",
-      "integrity": "sha512-BjATSNNyvVbQxOOlKse0b0pSezTWGMvA87SvoFoFlkRsKXVsN3bEtjCxvsNXJXfnAzlWFPaT9DmhWy1vn0sNEA==",
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.1.tgz",
+      "integrity": "sha512-y1DmifEgOF5J1MmrLP2arzI17tEaVqD+NUnfE+sVcpPcEHmAUL0TF9gQzAi5s6GGHUyDurO+zHvZQOeo7LuJnQ==",
       "optional": true,
       "requires": {
-        "@smithy/types": "^4.6.0",
+        "@smithy/types": "^4.7.0",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/service-error-classification": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.0.tgz",
-      "integrity": "sha512-Ylv1ttUeKatpR0wEOMnHf1hXMktPUMObDClSWl2TpCVT4DwtJhCeighLzSLbgH3jr5pBNM0LDXT5yYxUvZ9WpA==",
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.1.tgz",
+      "integrity": "sha512-NEcg3bGL9MddDd0GtH1+6bLg+e9SpbNEAVV8vEM4uWgqixECItz6wf0sYcq+N0lQjeRljdwaG3wxd2YgJ7JfbQ==",
       "optional": true,
       "requires": {
-        "@smithy/types": "^4.6.0"
+        "@smithy/types": "^4.7.0"
       }
     },
     "@smithy/shared-ini-file-loader": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.0.tgz",
-      "integrity": "sha512-VCUPPtNs+rKWlqqntX0CbVvWyjhmX30JCtzO+s5dlzzxrvSfRh5SY0yxnkirvc1c80vdKQttahL71a9EsdolSQ==",
+      "version": "4.3.1",
+      "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.1.tgz",
+      "integrity": "sha512-V4XVUUCsuVeSNkjeXLR4Y5doyNkTx29Cp8NfKoklgpSsWawyxmJbVvJ1kFHRulOmdBlLuHoqDrAirN8ZoduUCA==",
       "optional": true,
       "requires": {
-        "@smithy/types": "^4.6.0",
+        "@smithy/types": "^4.7.0",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/signature-v4": {
-      "version": "5.3.0",
-      "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.0.tgz",
-      "integrity": "sha512-MKNyhXEs99xAZaFhm88h+3/V+tCRDQ+PrDzRqL0xdDpq4gjxcMmf5rBA3YXgqZqMZ/XwemZEurCBQMfxZOWq/g==",
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.1.tgz",
+      "integrity": "sha512-7jimpk6X2jzV3UmesOFFV675N/4D8QqNg6NdZFNa/RmWAco+jyX/TbX2mHFImNm+DoafpwEfcDNsPxDSYF0Pxw==",
       "optional": true,
       "requires": {
         "@smithy/is-array-buffer": "^4.2.0",
-        "@smithy/protocol-http": "^5.3.0",
-        "@smithy/types": "^4.6.0",
+        "@smithy/protocol-http": "^5.3.1",
+        "@smithy/types": "^4.7.0",
         "@smithy/util-hex-encoding": "^4.2.0",
-        "@smithy/util-middleware": "^4.2.0",
+        "@smithy/util-middleware": "^4.2.1",
         "@smithy/util-uri-escape": "^4.2.0",
         "@smithy/util-utf8": "^4.2.0",
         "tslib": "^2.6.2"
@@ -963,37 +963,37 @@
       }
     },
     "@smithy/smithy-client": {
-      "version": "4.7.1",
-      "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.7.1.tgz",
-      "integrity": "sha512-WXVbiyNf/WOS/RHUoFMkJ6leEVpln5ojCjNBnzoZeMsnCg3A0BRhLK3WYc4V7PmYcYPZh9IYzzAg9XcNSzYxYQ==",
+      "version": "4.8.0",
+      "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.8.0.tgz",
+      "integrity": "sha512-gbpNLnuDnguDcXQvbeIAd05F9EDK4HasFtiRzJoM5NbsvXGnW2dGd4mHaShR+ZNveoP9KaWlwF8Hj4ZtipaM3Q==",
       "optional": true,
       "requires": {
-        "@smithy/core": "^3.15.0",
-        "@smithy/middleware-endpoint": "^4.3.1",
-        "@smithy/middleware-stack": "^4.2.0",
-        "@smithy/protocol-http": "^5.3.0",
-        "@smithy/types": "^4.6.0",
-        "@smithy/util-stream": "^4.5.0",
+        "@smithy/core": "^3.16.0",
+        "@smithy/middleware-endpoint": "^4.3.2",
+        "@smithy/middleware-stack": "^4.2.1",
+        "@smithy/protocol-http": "^5.3.1",
+        "@smithy/types": "^4.7.0",
+        "@smithy/util-stream": "^4.5.1",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/types": {
-      "version": "4.6.0",
-      "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz",
-      "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==",
+      "version": "4.7.0",
+      "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.7.0.tgz",
+      "integrity": "sha512-KM8Or+jCDCrUI3wYYhj7ehrC7aATB1NdJ1aFEE/YLKNLVH257k9RNeOqKdg0JOxjyEpVD7KKsmmob9mRy1Ho2g==",
       "optional": true,
       "requires": {
         "tslib": "^2.6.2"
       }
     },
     "@smithy/url-parser": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.0.tgz",
-      "integrity": "sha512-AlBmD6Idav2ugmoAL6UtR6ItS7jU5h5RNqLMZC7QrLCoITA9NzIN3nx9GWi8g4z1pfWh2r9r96SX/jHiNwPJ9A==",
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.1.tgz",
+      "integrity": "sha512-dHm6hDcl79Ededl0oKgpSq3mM5b7Xdw+jic8bq1G7Z2spVpm7HpHJuLCV9PUJLjMbDbZfRUf5GEOnnOIvgfYgQ==",
       "optional": true,
       "requires": {
-        "@smithy/querystring-parser": "^4.2.0",
-        "@smithy/types": "^4.6.0",
+        "@smithy/querystring-parser": "^4.2.1",
+        "@smithy/types": "^4.7.0",
         "tslib": "^2.6.2"
       }
     },
@@ -1067,40 +1067,40 @@
       }
     },
     "@smithy/util-defaults-mode-browser": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.0.tgz",
-      "integrity": "sha512-H4MAj8j8Yp19Mr7vVtGgi7noJjvjJbsKQJkvNnLlrIFduRFT5jq5Eri1k838YW7rN2g5FTnXpz5ktKVr1KVgPQ==",
+      "version": "4.3.1",
+      "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.1.tgz",
+      "integrity": "sha512-B3kaaqtc11rIc7SN3g6TYGdUrQfCkoHvpqbhd9kdfRUQZG7M7dcc0oLcCjMuBhCSUdtorkK7OA5uGq9BB+isaA==",
       "optional": true,
       "requires": {
-        "@smithy/property-provider": "^4.2.0",
-        "@smithy/smithy-client": "^4.7.1",
-        "@smithy/types": "^4.6.0",
+        "@smithy/property-provider": "^4.2.1",
+        "@smithy/smithy-client": "^4.8.0",
+        "@smithy/types": "^4.7.0",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/util-defaults-mode-node": {
-      "version": "4.2.1",
-      "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.1.tgz",
-      "integrity": "sha512-PuDcgx7/qKEMzV1QFHJ7E4/MMeEjaA7+zS5UNcHCLPvvn59AeZQ0DSDGMpqC2xecfa/1cNGm4l8Ec/VxCuY7Ug==",
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.2.tgz",
+      "integrity": "sha512-cneOHBPi/DGbjz65oV8wID+uUbtzrFAQ8w3a7uS3C1jjrInSrinAitup8SouDpmi8jr5GVOAck1/hsR3n/WvaQ==",
       "optional": true,
       "requires": {
-        "@smithy/config-resolver": "^4.3.0",
-        "@smithy/credential-provider-imds": "^4.2.0",
-        "@smithy/node-config-provider": "^4.3.0",
-        "@smithy/property-provider": "^4.2.0",
-        "@smithy/smithy-client": "^4.7.1",
-        "@smithy/types": "^4.6.0",
+        "@smithy/config-resolver": "^4.3.1",
+        "@smithy/credential-provider-imds": "^4.2.1",
+        "@smithy/node-config-provider": "^4.3.1",
+        "@smithy/property-provider": "^4.2.1",
+        "@smithy/smithy-client": "^4.8.0",
+        "@smithy/types": "^4.7.0",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/util-endpoints": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.0.tgz",
-      "integrity": "sha512-TXeCn22D56vvWr/5xPqALc9oO+LN+QpFjrSM7peG/ckqEPoI3zaKZFp+bFwfmiHhn5MGWPaLCqDOJPPIixk9Wg==",
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.1.tgz",
+      "integrity": "sha512-lJudabG/ll+BD22i8IgxZgxS+1hEdUfFqtC1tNubC9vlGwInUktcXodTe5CvM+xDiqGZfqYLY7mKFdabCIrkYw==",
       "optional": true,
       "requires": {
-        "@smithy/node-config-provider": "^4.3.0",
-        "@smithy/types": "^4.6.0",
+        "@smithy/node-config-provider": "^4.3.1",
+        "@smithy/types": "^4.7.0",
         "tslib": "^2.6.2"
       }
     },
@@ -1114,35 +1114,35 @@
       }
     },
     "@smithy/util-middleware": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.0.tgz",
-      "integrity": "sha512-u9OOfDa43MjagtJZ8AapJcmimP+K2Z7szXn8xbty4aza+7P1wjFmy2ewjSbhEiYQoW1unTlOAIV165weYAaowA==",
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.1.tgz",
+      "integrity": "sha512-4rf5Ma0e0uuKmtzMihsvs3jnb9iGMRDWrUe6mfdZBWm52PW1xVHdEeP4+swhheF+YAXhVH/O+taKJuqOrVsG3w==",
       "optional": true,
       "requires": {
-        "@smithy/types": "^4.6.0",
+        "@smithy/types": "^4.7.0",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/util-retry": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.0.tgz",
-      "integrity": "sha512-BWSiuGbwRnEE2SFfaAZEX0TqaxtvtSYPM/J73PFVm+A29Fg1HTPiYFb8TmX1DXp4hgcdyJcNQmprfd5foeORsg==",
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.1.tgz",
+      "integrity": "sha512-0DQqQtZ9brT/QCMts9ssPnsU6CmQAgzkAvTIGcTHoMbntQa7v5VPxxpiyyiTK/BIl8y0vCZSXcOS+kOMXAYRpg==",
       "optional": true,
       "requires": {
-        "@smithy/service-error-classification": "^4.2.0",
-        "@smithy/types": "^4.6.0",
+        "@smithy/service-error-classification": "^4.2.1",
+        "@smithy/types": "^4.7.0",
         "tslib": "^2.6.2"
       }
     },
     "@smithy/util-stream": {
-      "version": "4.5.0",
-      "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.0.tgz",
-      "integrity": "sha512-0TD5M5HCGu5diEvZ/O/WquSjhJPasqv7trjoqHyWjNh/FBeBl7a0ztl9uFMOsauYtRfd8jvpzIAQhDHbx+nvZw==",
+      "version": "4.5.1",
+      "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.1.tgz",
+      "integrity": "sha512-kVnOiYDDb84ZUGwpQBiVQROWR7epNXikxMGw971Mww3+eufKl2NHYyao2Gg4Wd3iG+D9hF/d9VrmMBxBcVprXw==",
       "optional": true,
       "requires": {
-        "@smithy/fetch-http-handler": "^5.3.1",
-        "@smithy/node-http-handler": "^4.3.0",
-        "@smithy/types": "^4.6.0",
+        "@smithy/fetch-http-handler": "^5.3.2",
+        "@smithy/node-http-handler": "^4.4.0",
+        "@smithy/types": "^4.7.0",
         "@smithy/util-base64": "^4.3.0",
         "@smithy/util-buffer-from": "^4.2.0",
         "@smithy/util-hex-encoding": "^4.2.0",

+ 43 - 2
server/attachmentMigration.js

@@ -5,24 +5,44 @@
 
 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
@@ -61,7 +81,12 @@ class AttachmentMigrationService {
       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);
@@ -176,15 +201,19 @@ const attachmentMigrationService = new AttachmentMigrationService();
 
 // Meteor methods
 Meteor.methods({
-  'attachmentMigration.migrateBoardAttachments'(boardId) {
+  async 'attachmentMigration.migrateBoardAttachments'(boardId) {
+    check(boardId, String);
+    
     if (!this.userId) {
       throw new Meteor.Error('not-authorized');
     }
 
-    return attachmentMigrationService.migrateBoardAttachments(boardId);
+    return await attachmentMigrationService.migrateBoardAttachments(boardId);
   },
 
   'attachmentMigration.getProgress'(boardId) {
+    check(boardId, String);
+    
     if (!this.userId) {
       throw new Meteor.Error('not-authorized');
     }
@@ -193,11 +222,23 @@ Meteor.methods({
   },
 
   '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);
   }
 });