/** * Server-side Migration Runner * Handles actual execution of database migrations with progress tracking */ import { Meteor } from 'meteor/meteor'; import { Migrations } from 'meteor/percolate:migrations'; import { ReactiveVar } from 'meteor/reactive-var'; // Server-side reactive variables for migration progress export const serverMigrationProgress = new ReactiveVar(0); export const serverMigrationStatus = new ReactiveVar(''); export const serverMigrationCurrentStep = new ReactiveVar(''); export const serverMigrationSteps = new ReactiveVar([]); export const serverIsMigrating = new ReactiveVar(false); class ServerMigrationRunner { constructor() { this.migrationSteps = this.initializeMigrationSteps(); this.currentStepIndex = 0; this.startTime = null; } /** * Initialize migration steps with their actual migration functions */ initializeMigrationSteps() { return [ { id: 'board-background-color', name: 'Board Background Colors', description: 'Setting up board background colors', weight: 1, completed: false, progress: 0, migrationFunction: this.runBoardBackgroundColorMigration }, { id: 'add-cardcounterlist-allowed', name: 'Card Counter List Settings', description: 'Adding card counter list permissions', weight: 1, completed: false, progress: 0, migrationFunction: this.runCardCounterListMigration }, { id: 'add-boardmemberlist-allowed', name: 'Board Member List Settings', description: 'Adding board member list permissions', weight: 1, completed: false, progress: 0, migrationFunction: this.runBoardMemberListMigration }, { id: 'lowercase-board-permission', name: 'Board Permission Standardization', description: 'Converting board permissions to lowercase', weight: 1, completed: false, progress: 0, migrationFunction: this.runLowercaseBoardPermissionMigration }, { id: 'change-attachments-type-for-non-images', name: 'Attachment Type Standardization', description: 'Updating attachment types for non-images', weight: 2, completed: false, progress: 0, migrationFunction: this.runAttachmentTypeMigration }, { id: 'card-covers', name: 'Card Covers System', description: 'Setting up card cover functionality', weight: 2, completed: false, progress: 0, migrationFunction: this.runCardCoversMigration }, { id: 'use-css-class-for-boards-colors', name: 'Board Color CSS Classes', description: 'Converting board colors to CSS classes', weight: 2, completed: false, progress: 0, migrationFunction: this.runBoardColorCSSMigration }, { id: 'denormalize-star-number-per-board', name: 'Board Star Counts', description: 'Calculating star counts per board', weight: 3, completed: false, progress: 0, migrationFunction: this.runStarNumberMigration }, { id: 'add-member-isactive-field', name: 'Member Activity Status', description: 'Adding member activity tracking', weight: 2, completed: false, progress: 0, migrationFunction: this.runMemberIsActiveMigration }, { id: 'add-sort-checklists', name: 'Checklist Sorting', description: 'Adding sort order to checklists', weight: 2, completed: false, progress: 0, migrationFunction: this.runSortChecklistsMigration }, { id: 'add-swimlanes', name: 'Swimlanes System', description: 'Setting up swimlanes functionality', weight: 4, completed: false, progress: 0, migrationFunction: this.runSwimlanesMigration }, { id: 'add-views', name: 'Board Views', description: 'Adding board view options', weight: 2, completed: false, progress: 0, migrationFunction: this.runViewsMigration }, { id: 'add-checklist-items', name: 'Checklist Items', description: 'Setting up checklist items system', weight: 3, completed: false, progress: 0, migrationFunction: this.runChecklistItemsMigration }, { id: 'add-card-types', name: 'Card Types', description: 'Adding card type functionality', weight: 2, completed: false, progress: 0, migrationFunction: this.runCardTypesMigration }, { id: 'add-custom-fields-to-cards', name: 'Custom Fields', description: 'Adding custom fields to cards', weight: 3, completed: false, progress: 0, migrationFunction: this.runCustomFieldsMigration }, { id: 'migrate-attachments-collectionFS-to-ostrioFiles', name: 'Migrate Attachments to Meteor-Files', description: 'Migrating attachments from CollectionFS to Meteor-Files', weight: 8, completed: false, progress: 0, migrationFunction: this.runAttachmentMigration }, { id: 'migrate-avatars-collectionFS-to-ostrioFiles', name: 'Migrate Avatars to Meteor-Files', description: 'Migrating avatars from CollectionFS to Meteor-Files', weight: 6, completed: false, progress: 0, migrationFunction: this.runAvatarMigration }, { id: 'migrate-lists-to-per-swimlane', name: 'Migrate Lists to Per-Swimlane', description: 'Migrating lists to per-swimlane structure', weight: 5, completed: false, progress: 0, migrationFunction: this.runListsToPerSwimlaneMigration } ]; } /** * Start migration process */ async startMigration() { if (serverIsMigrating.get()) { return; // Already migrating } serverIsMigrating.set(true); serverMigrationSteps.set([...this.migrationSteps]); this.startTime = Date.now(); try { for (let i = 0; i < this.migrationSteps.length; i++) { const step = this.migrationSteps[i]; this.currentStepIndex = i; if (step.completed) { continue; // Skip already completed steps } serverMigrationCurrentStep.set(step.name); serverMigrationStatus.set(`Running: ${step.description}`); // Run the migration step await this.runMigrationStep(step); // Mark as completed step.completed = true; step.progress = 100; // Update progress this.updateProgress(); // Allow other processes to run await new Promise(resolve => setTimeout(resolve, 100)); } // Migration completed serverMigrationStatus.set('All migrations completed successfully!'); serverMigrationProgress.set(100); serverMigrationCurrentStep.set(''); // Clear status after delay setTimeout(() => { serverIsMigrating.set(false); serverMigrationStatus.set(''); serverMigrationProgress.set(0); }, 3000); } catch (error) { console.error('Migration failed:', error); serverMigrationStatus.set(`Migration failed: ${error.message}`); serverIsMigrating.set(false); } } /** * Run a single migration step */ async runMigrationStep(step) { try { // Update progress during migration const progressSteps = 10; for (let i = 0; i <= progressSteps; i++) { step.progress = (i / progressSteps) * 100; this.updateProgress(); // Run actual migration function if (i === progressSteps) { await step.migrationFunction.call(this); } // Allow other processes to run await new Promise(resolve => setTimeout(resolve, 50)); } } catch (error) { console.error(`Migration step ${step.name} failed:`, error); throw error; } } /** * Update progress variables */ updateProgress() { const totalWeight = this.migrationSteps.reduce((total, step) => total + step.weight, 0); const completedWeight = this.migrationSteps.reduce((total, step) => { return total + (step.completed ? step.weight : step.progress * step.weight / 100); }, 0); const progress = Math.round((completedWeight / totalWeight) * 100); serverMigrationProgress.set(progress); serverMigrationSteps.set([...this.migrationSteps]); } // Individual migration functions async runBoardBackgroundColorMigration() { // Implementation for board background color migration console.log('Running board background color migration'); } async runCardCounterListMigration() { // Implementation for card counter list migration console.log('Running card counter list migration'); } async runBoardMemberListMigration() { // Implementation for board member list migration console.log('Running board member list migration'); } async runLowercaseBoardPermissionMigration() { // Implementation for lowercase board permission migration console.log('Running lowercase board permission migration'); } async runAttachmentTypeMigration() { // Implementation for attachment type migration console.log('Running attachment type migration'); } async runCardCoversMigration() { // Implementation for card covers migration console.log('Running card covers migration'); } async runBoardColorCSSMigration() { // Implementation for board color CSS migration console.log('Running board color CSS migration'); } async runStarNumberMigration() { // Implementation for star number migration console.log('Running star number migration'); } async runMemberIsActiveMigration() { // Implementation for member is active migration console.log('Running member is active migration'); } async runSortChecklistsMigration() { // Implementation for sort checklists migration console.log('Running sort checklists migration'); } async runSwimlanesMigration() { // Implementation for swimlanes migration console.log('Running swimlanes migration'); } async runViewsMigration() { // Implementation for views migration console.log('Running views migration'); } async runChecklistItemsMigration() { // Implementation for checklist items migration console.log('Running checklist items migration'); } async runCardTypesMigration() { // Implementation for card types migration console.log('Running card types migration'); } async runCustomFieldsMigration() { // Implementation for custom fields migration console.log('Running custom fields migration'); } async runAttachmentMigration() { // Implementation for attachment migration from CollectionFS to Meteor-Files console.log('Running attachment migration from CollectionFS to Meteor-Files'); } async runAvatarMigration() { // Implementation for avatar migration from CollectionFS to Meteor-Files console.log('Running avatar migration from CollectionFS to Meteor-Files'); } async runListsToPerSwimlaneMigration() { // Implementation for lists to per-swimlane migration console.log('Running lists to per-swimlane migration'); } } // Export singleton instance export const serverMigrationRunner = new ServerMigrationRunner(); // Meteor methods for client-server communication Meteor.methods({ 'migration.start'() { if (!this.userId) { throw new Meteor.Error('not-authorized'); } return serverMigrationRunner.startMigration(); }, 'migration.getProgress'() { return { progress: serverMigrationProgress.get(), status: serverMigrationStatus.get(), currentStep: serverMigrationCurrentStep.get(), steps: serverMigrationSteps.get(), isMigrating: serverIsMigrating.get() }; } });