fixAvatarUrls.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. /**
  2. * Fix Avatar URLs Migration
  3. * Removes problematic auth parameters from existing avatar URLs
  4. */
  5. import { ReactiveCache } from '/imports/reactiveCache';
  6. import Users from '/models/users';
  7. import { generateUniversalAvatarUrl, cleanFileUrl, extractFileIdFromUrl, isUniversalFileUrl } from '/models/lib/universalUrlGenerator';
  8. class FixAvatarUrlsMigration {
  9. constructor() {
  10. this.name = 'fixAvatarUrls';
  11. this.version = 1;
  12. }
  13. /**
  14. * Check if migration is needed
  15. */
  16. needsMigration() {
  17. const users = ReactiveCache.getUsers({});
  18. for (const user of users) {
  19. if (user.profile && user.profile.avatarUrl) {
  20. const avatarUrl = user.profile.avatarUrl;
  21. if (avatarUrl.includes('auth=false') || avatarUrl.includes('brokenIsFine=true')) {
  22. return true;
  23. }
  24. }
  25. }
  26. return false;
  27. }
  28. /**
  29. * Execute the migration
  30. */
  31. async execute() {
  32. const users = ReactiveCache.getUsers({});
  33. let avatarsFixed = 0;
  34. console.log(`Starting avatar URL fix migration...`);
  35. for (const user of users) {
  36. if (user.profile && user.profile.avatarUrl) {
  37. const avatarUrl = user.profile.avatarUrl;
  38. let needsUpdate = false;
  39. let cleanUrl = avatarUrl;
  40. // Check if URL has problematic parameters
  41. if (avatarUrl.includes('auth=false') || avatarUrl.includes('brokenIsFine=true')) {
  42. // Remove problematic parameters
  43. cleanUrl = cleanUrl.replace(/[?&]auth=false/g, '');
  44. cleanUrl = cleanUrl.replace(/[?&]brokenIsFine=true/g, '');
  45. cleanUrl = cleanUrl.replace(/\?&/g, '?');
  46. cleanUrl = cleanUrl.replace(/\?$/g, '');
  47. needsUpdate = true;
  48. }
  49. // Check if URL is using old CollectionFS format
  50. if (avatarUrl.includes('/cfs/files/avatars/')) {
  51. cleanUrl = cleanUrl.replace('/cfs/files/avatars/', '/cdn/storage/avatars/');
  52. needsUpdate = true;
  53. }
  54. // Check if URL is missing the /cdn/storage/avatars/ prefix
  55. if (avatarUrl.includes('avatars/') && !avatarUrl.includes('/cdn/storage/avatars/') && !avatarUrl.includes('/cfs/files/avatars/')) {
  56. // This might be a relative URL, make it absolute
  57. if (!avatarUrl.startsWith('http') && !avatarUrl.startsWith('/')) {
  58. cleanUrl = `/cdn/storage/avatars/${avatarUrl}`;
  59. needsUpdate = true;
  60. }
  61. }
  62. // If we have a file ID, generate a universal URL
  63. const fileId = extractFileIdFromUrl(avatarUrl, 'avatar');
  64. if (fileId && !isUniversalFileUrl(cleanUrl, 'avatar')) {
  65. cleanUrl = generateUniversalAvatarUrl(fileId);
  66. needsUpdate = true;
  67. }
  68. if (needsUpdate) {
  69. // Update user's avatar URL
  70. Users.update(user._id, {
  71. $set: {
  72. 'profile.avatarUrl': cleanUrl,
  73. modifiedAt: new Date()
  74. }
  75. });
  76. avatarsFixed++;
  77. if (process.env.DEBUG === 'true') {
  78. console.log(`Fixed avatar URL for user ${user.username}: ${avatarUrl} -> ${cleanUrl}`);
  79. }
  80. }
  81. }
  82. }
  83. console.log(`Avatar URL fix migration completed. Fixed ${avatarsFixed} avatar URLs.`);
  84. return {
  85. success: true,
  86. avatarsFixed
  87. };
  88. }
  89. }
  90. // Export singleton instance
  91. export const fixAvatarUrlsMigration = new FixAvatarUrlsMigration();
  92. // Meteor method
  93. Meteor.methods({
  94. 'fixAvatarUrls.execute'() {
  95. if (!this.userId) {
  96. throw new Meteor.Error('not-authorized');
  97. }
  98. return fixAvatarUrlsMigration.execute();
  99. },
  100. 'fixAvatarUrls.needsMigration'() {
  101. if (!this.userId) {
  102. throw new Meteor.Error('not-authorized');
  103. }
  104. return fixAvatarUrlsMigration.needsMigration();
  105. }
  106. });