Browse Source

Fixed sidebar migrations to be per-board, not global. Clarified translations.

Thanks to xet7 !
Lauri Ojansivu 1 day ago
parent
commit
e4638d5fbc

+ 0 - 2
client/components/sidebar/sidebarMigrations.jade

@@ -58,8 +58,6 @@ template(name='migrationsSidebar')
               else
                 span.badge.badge-success {{_ 'migration-complete'}}
         
-        hr
-        h4 {{_ 'global-migrations'}}
         .migration-item
           a.js-run-migration(data-migration="fixAvatarUrls")
             .migration-name

+ 11 - 9
client/components/sidebar/sidebarMigrations.js

@@ -57,17 +57,17 @@ BlazeComponent.extendComponent({
       }
     });
 
-    // Check fix avatar URLs migration (global)
-      Meteor.call('fixAvatarUrls.needsMigration', (err, res) => {
+    // Check fix avatar URLs migration (board-specific)
+    Meteor.call('fixAvatarUrls.needsMigration', boardId, (err, res) => {
       if (!err) {
         const statuses = this.migrationStatuses.get();
-          statuses.fixAvatarUrls = res;
+        statuses.fixAvatarUrls = res;
         this.migrationStatuses.set(statuses);
       }
     });
 
-    // Check fix all file URLs migration (global)
-    Meteor.call('fixAllFileUrls.needsMigration', (err, res) => {
+    // Check fix all file URLs migration (board-specific)
+    Meteor.call('fixAllFileUrls.needsMigration', boardId, (err, res) => {
       if (!err) {
         const statuses = this.migrationStatuses.get();
         statuses.fixAllFileUrls = res;
@@ -190,10 +190,12 @@ BlazeComponent.extendComponent({
       
       case 'fixAvatarUrls':
         methodName = 'fixAvatarUrls.execute';
+        methodArgs = [boardId];
         break;
       
       case 'fixAllFileUrls':
         methodName = 'fixAllFileUrls.execute';
+        methodArgs = [boardId];
         break;
     }
     
@@ -231,12 +233,12 @@ BlazeComponent.extendComponent({
           { step: 'fix_missing_ids', name: 'Fix Missing IDs', duration: 600 },
         ],
         fixAvatarUrls: [
-          { step: 'scan_users', name: 'Scan Users', duration: 500 },
-          { step: 'fix_urls', name: 'Fix Avatar URLs', duration: 900 },
+          { step: 'scan_users', name: 'Checking board member avatars', duration: 500 },
+          { step: 'fix_urls', name: 'Fixing avatar URLs', duration: 900 },
         ],
         fixAllFileUrls: [
-          { step: 'scan_files', name: 'Scan Files', duration: 600 },
-          { step: 'fix_urls', name: 'Fix File URLs', duration: 1000 },
+          { step: 'scan_files', name: 'Checking board file attachments', duration: 600 },
+          { step: 'fix_urls', name: 'Fixing file URLs', duration: 1000 },
         ],
       };
 

+ 7 - 8
imports/i18n/data/en.i18n.json

@@ -1419,10 +1419,9 @@
   "fix-missing-lists-migration": "Fix Missing Lists",
   "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.",
   "fix-avatar-urls-migration": "Fix Avatar URLs",
-  "fix-avatar-urls-migration-description": "Updates avatar URLs to use the correct storage backend and fixes broken avatar references.",
+  "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.",
   "fix-all-file-urls-migration": "Fix All File URLs",
-  "fix-all-file-urls-migration-description": "Updates all file attachment URLs to use the correct storage backend and fixes broken file references.",
-  "global-migrations": "Global Migrations",
+  "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.",
   "migration-needed": "Migration Needed",
   "migration-complete": "Complete",
   "migration-running": "Running...",
@@ -1438,8 +1437,8 @@
   "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?",
   "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?",
   "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?",
-  "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs across all boards to use the correct storage backend. This is a global operation. Continue?",
-  "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs across all boards to use the correct storage backend. This is a global operation. Continue?",
+  "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?",
+  "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?",
   "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore",
   
   "migration-progress-title": "Board Migration in Progress",
@@ -1466,9 +1465,9 @@
   "step-restore-cards": "Restore Cards",
   "step-restore-swimlanes": "Restore Swimlanes",
   "step-fix-missing-ids": "Fix Missing IDs",
-  "step-scan-users": "Scan Users",
-  "step-scan-files": "Scan Files",
-  "step-fix-file-urls": "Fix File URLs",
+  "step-scan-users": "Checking board member avatars",
+  "step-scan-files": "Checking board file attachments",
+  "step-fix-file-urls": "Fixing file URLs",
   "cleanup": "Cleanup",
   "cleanup-old-jobs": "Cleanup Old Jobs",
   "completed": "Completed",

+ 82 - 32
server/migrations/fixAllFileUrls.js

@@ -3,10 +3,14 @@
  * Ensures all attachment and avatar URLs are universal and work regardless of ROOT_URL and PORT settings
  */
 
+import { Meteor } from 'meteor/meteor';
+import { check } from 'meteor/check';
 import { ReactiveCache } from '/imports/reactiveCache';
+import Boards from '/models/boards';
 import Users from '/models/users';
 import Attachments from '/models/attachments';
 import Avatars from '/models/avatars';
+import Cards from '/models/cards';
 import { generateUniversalAttachmentUrl, generateUniversalAvatarUrl, cleanFileUrl, extractFileIdFromUrl, isUniversalFileUrl } from '/models/lib/universalUrlGenerator';
 
 class FixAllFileUrlsMigration {
@@ -16,11 +20,19 @@ class FixAllFileUrlsMigration {
   }
 
   /**
-   * Check if migration is needed
+   * Check if migration is needed for a board
    */
-  needsMigration() {
-    // Check for problematic avatar URLs
-    const users = ReactiveCache.getUsers({});
+  needsMigration(boardId) {
+    // Get all users who are members of this board
+    const board = ReactiveCache.getBoard(boardId);
+    if (!board || !board.members) {
+      return false;
+    }
+    
+    const memberIds = board.members.map(m => m.userId);
+    
+    // Check for problematic avatar URLs for board members
+    const users = ReactiveCache.getUsers({ _id: { $in: memberIds } });
     for (const user of users) {
       if (user.profile && user.profile.avatarUrl) {
         const avatarUrl = user.profile.avatarUrl;
@@ -30,8 +42,11 @@ class FixAllFileUrlsMigration {
       }
     }
 
-    // Check for problematic attachment URLs
-    const attachments = ReactiveCache.getAttachments({});
+    // Check for problematic attachment URLs on this board
+    const cards = ReactiveCache.getCards({ boardId });
+    const cardIds = cards.map(c => c._id);
+    const attachments = ReactiveCache.getAttachments({ cardId: { $in: cardIds } });
+    
     for (const attachment of attachments) {
       if (attachment.url && this.hasProblematicUrl(attachment.url)) {
         return true;
@@ -78,46 +93,53 @@ class FixAllFileUrlsMigration {
   }
 
   /**
-   * Execute the migration
+   * Execute the migration for a board
    */
-  async execute() {
+  async execute(boardId) {
     let filesFixed = 0;
     let errors = [];
 
-    console.log(`Starting universal file URL migration...`);
+    console.log(`Starting universal file URL migration for board ${boardId}...`);
 
     try {
-      // Fix avatar URLs
-      const avatarFixed = await this.fixAvatarUrls();
+      // Fix avatar URLs for board members
+      const avatarFixed = await this.fixAvatarUrls(boardId);
       filesFixed += avatarFixed;
 
-      // Fix attachment URLs
-      const attachmentFixed = await this.fixAttachmentUrls();
+      // Fix attachment URLs for board cards
+      const attachmentFixed = await this.fixAttachmentUrls(boardId);
       filesFixed += attachmentFixed;
 
       // Fix card attachment references
-      const cardFixed = await this.fixCardAttachmentUrls();
+      const cardFixed = await this.fixCardAttachmentUrls(boardId);
       filesFixed += cardFixed;
 
     } catch (error) {
-      console.error('Error during file URL migration:', error);
+      console.error('Error during file URL migration for board', boardId, ':', error);
       errors.push(error.message);
     }
 
-    console.log(`Universal file URL migration completed. Fixed ${filesFixed} file URLs.`);
+    console.log(`Universal file URL migration completed for board ${boardId}. Fixed ${filesFixed} file URLs.`);
     
     return {
       success: errors.length === 0,
       filesFixed,
-      errors
+      errors,
+      changes: [`Fixed ${filesFixed} file URLs for this board`]
     };
   }
 
   /**
-   * Fix avatar URLs in user profiles
+   * Fix avatar URLs in user profiles for board members
    */
-  async fixAvatarUrls() {
-    const users = ReactiveCache.getUsers({});
+  async fixAvatarUrls(boardId) {
+    const board = ReactiveCache.getBoard(boardId);
+    if (!board || !board.members) {
+      return 0;
+    }
+    
+    const memberIds = board.members.map(m => m.userId);
+    const users = ReactiveCache.getUsers({ _id: { $in: memberIds } });
     let avatarsFixed = 0;
 
     for (const user of users) {
@@ -164,10 +186,12 @@ class FixAllFileUrlsMigration {
   }
 
   /**
-   * Fix attachment URLs in attachment records
+   * Fix attachment URLs in attachment records for this board
    */
-  async fixAttachmentUrls() {
-    const attachments = ReactiveCache.getAttachments({});
+  async fixAttachmentUrls(boardId) {
+    const cards = ReactiveCache.getCards({ boardId });
+    const cardIds = cards.map(c => c._id);
+    const attachments = ReactiveCache.getAttachments({ cardId: { $in: cardIds } });
     let attachmentsFixed = 0;
 
     for (const attachment of attachments) {
@@ -202,10 +226,12 @@ class FixAllFileUrlsMigration {
   }
 
   /**
-   * Fix attachment URLs in the Attachments collection
+   * Fix attachment URLs in the Attachments collection for this board
    */
-  async fixCardAttachmentUrls() {
-    const attachments = ReactiveCache.getAttachments({});
+  async fixCardAttachmentUrls(boardId) {
+    const cards = ReactiveCache.getCards({ boardId });
+    const cardIds = cards.map(c => c._id);
+    const attachments = ReactiveCache.getAttachments({ cardId: { $in: cardIds } });
     let attachmentsFixed = 0;
 
     for (const attachment of attachments) {
@@ -244,19 +270,43 @@ export const fixAllFileUrlsMigration = new FixAllFileUrlsMigration();
 
 // Meteor methods
 Meteor.methods({
-  'fixAllFileUrls.execute'() {
+  'fixAllFileUrls.execute'(boardId) {
+    check(boardId, String);
+    
     if (!this.userId) {
-      throw new Meteor.Error('not-authorized');
+      throw new Meteor.Error('not-authorized', 'You must be logged in');
+    }
+
+    // Check if user is board admin
+    const board = ReactiveCache.getBoard(boardId);
+    if (!board) {
+      throw new Meteor.Error('board-not-found', 'Board not found');
+    }
+
+    const user = ReactiveCache.getUser(this.userId);
+    if (!user) {
+      throw new Meteor.Error('user-not-found', 'User not found');
+    }
+
+    // Only board admins can run migrations
+    const isBoardAdmin = board.members && board.members.some(
+      member => member.userId === this.userId && member.isAdmin
+    );
+
+    if (!isBoardAdmin && !user.isAdmin) {
+      throw new Meteor.Error('not-authorized', 'Only board administrators can run migrations');
     }
     
-    return fixAllFileUrlsMigration.execute();
+    return fixAllFileUrlsMigration.execute(boardId);
   },
 
-  'fixAllFileUrls.needsMigration'() {
+  'fixAllFileUrls.needsMigration'(boardId) {
+    check(boardId, String);
+    
     if (!this.userId) {
-      throw new Meteor.Error('not-authorized');
+      throw new Meteor.Error('not-authorized', 'You must be logged in');
     }
     
-    return fixAllFileUrlsMigration.needsMigration();
+    return fixAllFileUrlsMigration.needsMigration(boardId);
   }
 });

+ 60 - 15
server/migrations/fixAvatarUrls.js

@@ -3,7 +3,10 @@
  * Removes problematic auth parameters from existing avatar URLs
  */
 
+import { Meteor } from 'meteor/meteor';
+import { check } from 'meteor/check';
 import { ReactiveCache } from '/imports/reactiveCache';
+import Boards from '/models/boards';
 import Users from '/models/users';
 import { generateUniversalAvatarUrl, cleanFileUrl, extractFileIdFromUrl, isUniversalFileUrl } from '/models/lib/universalUrlGenerator';
 
@@ -14,10 +17,17 @@ class FixAvatarUrlsMigration {
   }
 
   /**
-   * Check if migration is needed
+   * Check if migration is needed for a board
    */
-  needsMigration() {
-    const users = ReactiveCache.getUsers({});
+  needsMigration(boardId) {
+    // Get all users who are members of this board
+    const board = ReactiveCache.getBoard(boardId);
+    if (!board || !board.members) {
+      return false;
+    }
+    
+    const memberIds = board.members.map(m => m.userId);
+    const users = ReactiveCache.getUsers({ _id: { $in: memberIds } });
     
     for (const user of users) {
       if (user.profile && user.profile.avatarUrl) {
@@ -32,13 +42,23 @@ class FixAvatarUrlsMigration {
   }
 
   /**
-   * Execute the migration
+   * Execute the migration for a board
    */
-  async execute() {
-    const users = ReactiveCache.getUsers({});
+  async execute(boardId) {
+    // Get all users who are members of this board
+    const board = ReactiveCache.getBoard(boardId);
+    if (!board || !board.members) {
+      return {
+        success: false,
+        error: 'Board not found or has no members'
+      };
+    }
+    
+    const memberIds = board.members.map(m => m.userId);
+    const users = ReactiveCache.getUsers({ _id: { $in: memberIds } });
     let avatarsFixed = 0;
 
-    console.log(`Starting avatar URL fix migration...`);
+    console.log(`Starting avatar URL fix migration for board ${boardId}...`);
 
     for (const user of users) {
       if (user.profile && user.profile.avatarUrl) {
@@ -96,11 +116,12 @@ class FixAvatarUrlsMigration {
       }
     }
 
-    console.log(`Avatar URL fix migration completed. Fixed ${avatarsFixed} avatar URLs.`);
+    console.log(`Avatar URL fix migration completed for board ${boardId}. Fixed ${avatarsFixed} avatar URLs.`);
     
     return {
       success: true,
-      avatarsFixed
+      avatarsFixed,
+      changes: [`Fixed ${avatarsFixed} avatar URLs for board members`]
     };
   }
 }
@@ -110,19 +131,43 @@ export const fixAvatarUrlsMigration = new FixAvatarUrlsMigration();
 
 // Meteor method
 Meteor.methods({
-  'fixAvatarUrls.execute'() {
+  'fixAvatarUrls.execute'(boardId) {
+    check(boardId, String);
+    
     if (!this.userId) {
-      throw new Meteor.Error('not-authorized');
+      throw new Meteor.Error('not-authorized', 'You must be logged in');
+    }
+
+    // Check if user is board admin
+    const board = ReactiveCache.getBoard(boardId);
+    if (!board) {
+      throw new Meteor.Error('board-not-found', 'Board not found');
+    }
+
+    const user = ReactiveCache.getUser(this.userId);
+    if (!user) {
+      throw new Meteor.Error('user-not-found', 'User not found');
+    }
+
+    // Only board admins can run migrations
+    const isBoardAdmin = board.members && board.members.some(
+      member => member.userId === this.userId && member.isAdmin
+    );
+
+    if (!isBoardAdmin && !user.isAdmin) {
+      throw new Meteor.Error('not-authorized', 'Only board administrators can run migrations');
     }
     
-    return fixAvatarUrlsMigration.execute();
+    return fixAvatarUrlsMigration.execute(boardId);
   },
 
-  'fixAvatarUrls.needsMigration'() {
+  'fixAvatarUrls.needsMigration'(boardId) {
+    check(boardId, String);
+    
     if (!this.userId) {
-      throw new Meteor.Error('not-authorized');
+      throw new Meteor.Error('not-authorized', 'You must be logged in');
     }
     
-    return fixAvatarUrlsMigration.needsMigration();
+    return fixAvatarUrlsMigration.needsMigration(boardId);
   }
 });