فهرست منبع

Made possible to start WeKan immediately without running any database migrations.

Thanks to xet7 !
Lauri Ojansivu 5 روز پیش
والد
کامیت
3ccdc2e307
5فایلهای تغییر یافته به همراه198 افزوده شده و 11 حذف شده
  1. 2 2
      client/components/swimlanes/swimlanes.js
  2. 118 0
      models/attachments_old.js
  3. 29 0
      models/avatars_old.js
  4. 12 0
      models/presences.js
  5. 37 9
      server/migrations.js

+ 2 - 2
client/components/swimlanes/swimlanes.js

@@ -104,8 +104,8 @@ function initSortable(boardComponent, $listsDom) {
         }
       }
 
-      // Get the original swimlane ID of the list
-      const originalSwimlaneId = list.swimlaneId;
+      // Get the original swimlane ID of the list (handle backward compatibility)
+      const originalSwimlaneId = list.getEffectiveSwimlaneId ? list.getEffectiveSwimlaneId() : (list.swimlaneId || null);
 
       /*
             Reverted incomplete change list width,

+ 118 - 0
models/attachments_old.js

@@ -0,0 +1,118 @@
+import { ReactiveCache } from '/imports/reactiveCache';
+
+const storeName = 'attachments';
+const defaultStoreOptions = {
+  beforeWrite: fileObj => {
+    if (!fileObj.isImage()) {
+      return {
+        type: 'application/octet-stream',
+      };
+    }
+    return {};
+  },
+};
+let store;
+store = new FS.Store.GridFS(storeName, {
+  // XXX Add a new store for cover thumbnails so we don't load big images in
+  // the general board view
+  // If the uploaded document is not an image we need to enforce browser
+  // download instead of execution. This is particularly important for HTML
+  // files that the browser will just execute if we don't serve them with the
+  // appropriate `application/octet-stream` MIME header which can lead to user
+  // data leaks. I imagine other formats (like PDF) can also be attack vectors.
+  // See https://github.com/wekan/wekan/issues/99
+  // XXX Should we use `beforeWrite` option of CollectionFS instead of
+  // collection-hooks?
+  // We should use `beforeWrite`.
+  ...defaultStoreOptions,
+});
+AttachmentsOld = new FS.Collection('attachments', {
+  stores: [store],
+});
+
+if (Meteor.isServer) {
+  Meteor.startup(() => {
+    AttachmentsOld.files._ensureIndex({ cardId: 1 });
+  });
+
+  AttachmentsOld.allow({
+    insert(userId, doc) {
+      return allowIsBoardMember(userId, ReactiveCache.getBoard(doc.boardId));
+    },
+    update(userId, doc) {
+      return allowIsBoardMember(userId, ReactiveCache.getBoard(doc.boardId));
+    },
+    remove(userId, doc) {
+      return allowIsBoardMember(userId, ReactiveCache.getBoard(doc.boardId));
+    },
+    // We authorize the attachment download either:
+    // - if the board is public, everyone (even unconnected) can download it
+    // - if the board is private, only board members can download it
+    download(userId, doc) {
+      const board = ReactiveCache.getBoard(doc.boardId);
+      if (board.isPublic()) {
+        return true;
+      } else {
+        return board.hasMember(userId);
+      }
+    },
+
+    fetch: ['boardId'],
+  });
+}
+
+// XXX Enforce a schema for the AttachmentsOld CollectionFS
+
+if (Meteor.isServer) {
+  AttachmentsOld.files.after.insert((userId, doc) => {
+    // If the attachment doesn't have a source field
+    // or its source is different than import
+    if (!doc.source || doc.source !== 'import') {
+      // Add activity about adding the attachment
+      Activities.insert({
+        userId,
+        type: 'card',
+        activityType: 'addAttachment',
+        attachmentId: doc._id,
+        // this preserves the name so that notifications can be meaningful after
+        // this file is removed
+        attachmentName: doc.original.name,
+        boardId: doc.boardId,
+        cardId: doc.cardId,
+        listId: doc.listId,
+        swimlaneId: doc.swimlaneId,
+      });
+    } else {
+      // Don't add activity about adding the attachment as the activity
+      // be imported and delete source field
+      AttachmentsOld.update(
+        {
+          _id: doc._id,
+        },
+        {
+          $unset: {
+            source: '',
+          },
+        },
+      );
+    }
+  });
+
+  AttachmentsOld.files.before.remove((userId, doc) => {
+    Activities.insert({
+      userId,
+      type: 'card',
+      activityType: 'deleteAttachment',
+      attachmentId: doc._id,
+      // this preserves the name so that notifications can be meaningful after
+      // this file is removed
+      attachmentName: doc.original.name,
+      boardId: doc.boardId,
+      cardId: doc.cardId,
+      listId: doc.listId,
+      swimlaneId: doc.swimlaneId,
+    });
+  });
+}
+
+export default AttachmentsOld;

+ 29 - 0
models/avatars_old.js

@@ -0,0 +1,29 @@
+AvatarsOld = new FS.Collection('avatars', {
+  stores: [new FS.Store.GridFS('avatars')],
+  filter: {
+    maxSize: 72000,
+    allow: {
+      contentTypes: ['image/*'],
+    },
+  },
+});
+
+function isOwner(userId, file) {
+  return userId && userId === file.userId;
+}
+
+AvatarsOld.allow({
+  insert: isOwner,
+  update: isOwner,
+  remove: isOwner,
+  download() {
+    return true;
+  },
+  fetch: ['userId'],
+});
+
+AvatarsOld.files.before.insert((userId, doc) => {
+  doc.userId = userId;
+});
+
+export default AvatarsOld;

+ 12 - 0
models/presences.js

@@ -0,0 +1,12 @@
+if (Meteor.isServer) {
+  Meteor.startup(() => {
+    // Date of 7 days ago
+    let lastWeek = new Date();
+    lastWeek.setDate(lastWeek.getDate() - 7);
+
+    presences.remove({ ttl: { $lte: lastWeek } });
+
+    // Create index for serverId that is queried often
+    presences._collection._ensureIndex({ serverId: -1 });
+  });
+}

+ 37 - 9
server/migrations.js

@@ -26,14 +26,23 @@ import Triggers from '../models/triggers';
 import UnsavedEdits from '../models/unsavedEdits';
 import Users from '../models/users';
 
-// Anytime you change the schema of one of the collection in a non-backward
-// compatible way you have to write a migration in this file using the following
-// API:
+// MIGRATIONS DISABLED - BACKWARD COMPATIBILITY APPROACH
+// All migrations have been disabled to prevent downtime and performance issues
+// with large databases. Instead, the application now uses backward compatibility
+// code that detects the current database structure and works with both migrated
+// and non-migrated data without requiring any migrations to run.
 //
+// This approach ensures:
+// 1. No migration downtime
+// 2. Immediate functionality for all database states
+// 3. Gradual data structure updates as users interact with the system
+// 4. Full backward compatibility with existing data
+//
+// Original migration API (now disabled):
 //   Migrations.add(name, migrationCallback, optionalOrder);
 
 // Note that we have extra migrations defined in `sandstorm.js` that are
-// exclusive to Sandstorm and shouldn’t be executed in the general case.
+// exclusive to Sandstorm and shouldn't be executed in the general case.
 // XXX I guess if we had ES6 modules we could
 // `import { isSandstorm } from sandstorm.js` and define the migration here as
 // well, but for now I want to avoid definied too many globals.
@@ -54,6 +63,18 @@ const noValidate = {
 };
 const noValidateMulti = { ...noValidate, multi: true };
 
+// ============================================================================
+// ALL MIGRATIONS DISABLED - BACKWARD COMPATIBILITY APPROACH
+// ============================================================================
+// All migrations below have been disabled to prevent downtime and performance
+// issues with large databases. The application now uses backward compatibility
+// code in the models and client code to handle both migrated and non-migrated
+// database structures without requiring any migrations to run.
+//
+// This ensures immediate functionality regardless of database state.
+// ============================================================================
+
+/*
 Migrations.add('board-background-color', () => {
   const defaultColor = '#16A085';
   Boards.update(
@@ -106,7 +127,6 @@ Migrations.add('add-boardmemberlist-allowed', () => {
     noValidateMulti,
   );
 });
-*/
 
 Migrations.add('lowercase-board-permission', () => {
   ['Public', 'Private'].forEach(permission => {
@@ -148,8 +168,6 @@ Migrations.add('card-covers', () => {
   Attachments.update({}, { $unset: { cover: '' } }, noValidateMulti);
 });
 
-*/
-
 Migrations.add('use-css-class-for-boards-colors', () => {
   const associationTable = {
     '#27AE60': 'nephritis',
@@ -1407,7 +1425,6 @@ Migrations.add('migrate-avatars-collectionFS-to-ostrioFiles', () => {
   });
   Meteor.settings.public.ostrioFilesMigrationInProgress = "false";
 });
-*/
 
 Migrations.add('migrate-attachment-drop-index-cardId', () => {
   try {
@@ -1442,7 +1459,6 @@ Migrations.add('attachment-cardCopy-fix-boardId-etc', () => {
     }
   });
 });
-*/
 
 Migrations.add('remove-unused-planning-poker', () => {
   Cards.update(
@@ -1490,6 +1506,10 @@ Migrations.add('remove-user-profile-hideCheckedItems', () => {
   );
 });
 
+// Migration disabled - using backward compatibility approach instead
+// This migration was taking too long for large databases
+// The feature now works with both old lists (without swimlaneId) and new lists (with swimlaneId)
+/*
 Migrations.add('migrate-lists-to-per-swimlane', () => {
   if (process.env.DEBUG === 'true') {
     console.log('Starting migration: migrate-lists-to-per-swimlane');
@@ -1550,3 +1570,11 @@ Migrations.add('migrate-lists-to-per-swimlane', () => {
     throw error;
   }
 });
+*/
+
+// ============================================================================
+// END OF DISABLED MIGRATIONS
+// ============================================================================
+// All migrations above have been disabled. The application now uses backward
+// compatibility code to handle both migrated and non-migrated database structures.
+// ============================================================================