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

Thanks to xet7 !
This commit is contained in:
Lauri Ojansivu 2025-11-05 20:22:56 +02:00
parent bc5854dd29
commit e4638d5fbc
5 changed files with 160 additions and 66 deletions

View file

@ -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);
}
});

View file

@ -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);
}
});