wekan/server/migrationRunner.js

405 lines
12 KiB
JavaScript
Raw Normal View History

/**
* 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()
};
}
});