| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767 |
- /**
- * Comprehensive Board Migration System
- *
- * This migration handles all database structure changes from previous Wekan versions
- * to the current per-swimlane lists structure. It ensures:
- *
- * 1. All cards are visible with proper swimlaneId and listId
- * 2. Lists are per-swimlane (no shared lists across swimlanes)
- * 3. No empty lists are created
- * 4. Handles various database structure versions from git history
- *
- * Supported versions and their database structures:
- * - v7.94 and earlier: Shared lists across all swimlanes
- * - v8.00-v8.02: Transition period with mixed structures
- * - v8.03+: Per-swimlane lists structure
- */
- import { Meteor } from 'meteor/meteor';
- import { check } from 'meteor/check';
- import { ReactiveCache } from '/imports/reactiveCache';
- import Boards from '/models/boards';
- import Lists from '/models/lists';
- import Cards from '/models/cards';
- import Swimlanes from '/models/swimlanes';
- import Attachments from '/models/attachments';
- import { generateUniversalAttachmentUrl, isUniversalFileUrl } from '/models/lib/universalUrlGenerator';
- class ComprehensiveBoardMigration {
- constructor() {
- this.name = 'comprehensive-board-migration';
- this.version = 1;
- this.migrationSteps = [
- 'analyze_board_structure',
- 'fix_orphaned_cards',
- 'convert_shared_lists',
- 'ensure_per_swimlane_lists',
- 'cleanup_empty_lists',
- 'validate_migration'
- ];
- }
- /**
- * Check if migration is needed for a board
- */
- needsMigration(boardId) {
- try {
- const board = ReactiveCache.getBoard(boardId);
- if (!board) return false;
- // Check if board has already been processed
- if (board.comprehensiveMigrationCompleted) {
- return false;
- }
- // Check for various issues that need migration
- const issues = this.detectMigrationIssues(boardId);
- return issues.length > 0;
- } catch (error) {
- console.error('Error checking if migration is needed:', error);
- return false;
- }
- }
- /**
- * Detect all migration issues in a board
- */
- detectMigrationIssues(boardId) {
- const issues = [];
-
- try {
- const cards = ReactiveCache.getCards({ boardId });
- const lists = ReactiveCache.getLists({ boardId });
- const swimlanes = ReactiveCache.getSwimlanes({ boardId });
- // Issue 1: Cards with missing swimlaneId
- const cardsWithoutSwimlane = cards.filter(card => !card.swimlaneId);
- if (cardsWithoutSwimlane.length > 0) {
- issues.push({
- type: 'cards_without_swimlane',
- count: cardsWithoutSwimlane.length,
- description: `${cardsWithoutSwimlane.length} cards missing swimlaneId`
- });
- }
- // Issue 2: Cards with missing listId
- const cardsWithoutList = cards.filter(card => !card.listId);
- if (cardsWithoutList.length > 0) {
- issues.push({
- type: 'cards_without_list',
- count: cardsWithoutList.length,
- description: `${cardsWithoutList.length} cards missing listId`
- });
- }
- // Issue 3: Lists without swimlaneId (shared lists)
- const sharedLists = lists.filter(list => !list.swimlaneId || list.swimlaneId === '');
- if (sharedLists.length > 0) {
- issues.push({
- type: 'shared_lists',
- count: sharedLists.length,
- description: `${sharedLists.length} lists without swimlaneId (shared lists)`
- });
- }
- // Issue 4: Cards with mismatched listId/swimlaneId
- const listSwimlaneMap = new Map();
- lists.forEach(list => {
- listSwimlaneMap.set(list._id, list.swimlaneId || '');
- });
- const mismatchedCards = cards.filter(card => {
- if (!card.listId || !card.swimlaneId) return false;
- const listSwimlaneId = listSwimlaneMap.get(card.listId);
- return listSwimlaneId && listSwimlaneId !== card.swimlaneId;
- });
- if (mismatchedCards.length > 0) {
- issues.push({
- type: 'mismatched_cards',
- count: mismatchedCards.length,
- description: `${mismatchedCards.length} cards with mismatched listId/swimlaneId`
- });
- }
- // Issue 5: Empty lists (lists with no cards)
- const emptyLists = lists.filter(list => {
- const listCards = cards.filter(card => card.listId === list._id);
- return listCards.length === 0;
- });
- if (emptyLists.length > 0) {
- issues.push({
- type: 'empty_lists',
- count: emptyLists.length,
- description: `${emptyLists.length} empty lists (no cards)`
- });
- }
- } catch (error) {
- console.error('Error detecting migration issues:', error);
- issues.push({
- type: 'detection_error',
- count: 1,
- description: `Error detecting issues: ${error.message}`
- });
- }
- return issues;
- }
- /**
- * Execute the comprehensive migration for a board
- */
- async executeMigration(boardId, progressCallback = null) {
- try {
- if (process.env.DEBUG === 'true') {
- console.log(`Starting comprehensive board migration for board ${boardId}`);
- }
- const board = ReactiveCache.getBoard(boardId);
- if (!board) {
- throw new Error(`Board ${boardId} not found`);
- }
- const results = {
- boardId,
- steps: {},
- totalCardsProcessed: 0,
- totalListsProcessed: 0,
- totalListsCreated: 0,
- totalListsRemoved: 0,
- errors: []
- };
- const totalSteps = this.migrationSteps.length;
- let currentStep = 0;
- // Helper function to update progress
- const updateProgress = (stepName, stepProgress, stepStatus, stepDetails = null) => {
- currentStep++;
- const overallProgress = Math.round((currentStep / totalSteps) * 100);
-
- const progressData = {
- overallProgress,
- currentStep: currentStep,
- totalSteps,
- stepName,
- stepProgress,
- stepStatus,
- stepDetails,
- boardId
- };
- if (progressCallback) {
- progressCallback(progressData);
- }
- if (process.env.DEBUG === 'true') {
- console.log(`Migration Progress: ${stepName} - ${stepStatus} (${stepProgress}%)`);
- }
- };
- // Step 1: Analyze board structure
- updateProgress('analyze_board_structure', 0, 'Starting analysis...');
- results.steps.analyze = await this.analyzeBoardStructure(boardId);
- updateProgress('analyze_board_structure', 100, 'Analysis complete', {
- issuesFound: results.steps.analyze.issueCount,
- needsMigration: results.steps.analyze.needsMigration
- });
-
- // Step 2: Fix orphaned cards
- updateProgress('fix_orphaned_cards', 0, 'Fixing orphaned cards...');
- results.steps.fixOrphanedCards = await this.fixOrphanedCards(boardId, (progress, status) => {
- updateProgress('fix_orphaned_cards', progress, status);
- });
- results.totalCardsProcessed += results.steps.fixOrphanedCards.cardsFixed || 0;
- updateProgress('fix_orphaned_cards', 100, 'Orphaned cards fixed', {
- cardsFixed: results.steps.fixOrphanedCards.cardsFixed
- });
- // Step 3: Convert shared lists to per-swimlane lists
- updateProgress('convert_shared_lists', 0, 'Converting shared lists...');
- results.steps.convertSharedLists = await this.convertSharedListsToPerSwimlane(boardId, (progress, status) => {
- updateProgress('convert_shared_lists', progress, status);
- });
- results.totalListsProcessed += results.steps.convertSharedLists.listsProcessed || 0;
- results.totalListsCreated += results.steps.convertSharedLists.listsCreated || 0;
- updateProgress('convert_shared_lists', 100, 'Shared lists converted', {
- listsProcessed: results.steps.convertSharedLists.listsProcessed,
- listsCreated: results.steps.convertSharedLists.listsCreated
- });
- // Step 4: Ensure all lists are per-swimlane
- updateProgress('ensure_per_swimlane_lists', 0, 'Ensuring per-swimlane structure...');
- results.steps.ensurePerSwimlane = await this.ensurePerSwimlaneLists(boardId);
- results.totalListsProcessed += results.steps.ensurePerSwimlane.listsProcessed || 0;
- updateProgress('ensure_per_swimlane_lists', 100, 'Per-swimlane structure ensured', {
- listsProcessed: results.steps.ensurePerSwimlane.listsProcessed
- });
- // Step 5: Cleanup empty lists
- updateProgress('cleanup_empty_lists', 0, 'Cleaning up empty lists...');
- results.steps.cleanupEmpty = await this.cleanupEmptyLists(boardId);
- results.totalListsRemoved += results.steps.cleanupEmpty.listsRemoved || 0;
- updateProgress('cleanup_empty_lists', 100, 'Empty lists cleaned up', {
- listsRemoved: results.steps.cleanupEmpty.listsRemoved
- });
- // Step 6: Validate migration
- updateProgress('validate_migration', 0, 'Validating migration...');
- results.steps.validate = await this.validateMigration(boardId);
- updateProgress('validate_migration', 100, 'Migration validated', {
- migrationSuccessful: results.steps.validate.migrationSuccessful,
- totalCards: results.steps.validate.totalCards,
- totalLists: results.steps.validate.totalLists
- });
- // Step 7: Fix avatar URLs
- updateProgress('fix_avatar_urls', 0, 'Fixing avatar URLs...');
- results.steps.fixAvatarUrls = await this.fixAvatarUrls(boardId);
- updateProgress('fix_avatar_urls', 100, 'Avatar URLs fixed', {
- avatarsFixed: results.steps.fixAvatarUrls.avatarsFixed
- });
- // Step 8: Fix attachment URLs
- updateProgress('fix_attachment_urls', 0, 'Fixing attachment URLs...');
- results.steps.fixAttachmentUrls = await this.fixAttachmentUrls(boardId);
- updateProgress('fix_attachment_urls', 100, 'Attachment URLs fixed', {
- attachmentsFixed: results.steps.fixAttachmentUrls.attachmentsFixed
- });
- // Mark board as processed
- Boards.update(boardId, {
- $set: {
- comprehensiveMigrationCompleted: true,
- comprehensiveMigrationCompletedAt: new Date(),
- comprehensiveMigrationResults: results
- }
- });
- if (process.env.DEBUG === 'true') {
- console.log(`Comprehensive board migration completed for board ${boardId}:`, results);
- }
- return {
- success: true,
- results
- };
- } catch (error) {
- console.error(`Error executing comprehensive migration for board ${boardId}:`, error);
- throw error;
- }
- }
- /**
- * Step 1: Analyze board structure
- */
- async analyzeBoardStructure(boardId) {
- const issues = this.detectMigrationIssues(boardId);
- return {
- issues,
- issueCount: issues.length,
- needsMigration: issues.length > 0
- };
- }
- /**
- * Step 2: Fix orphaned cards (cards with missing swimlaneId or listId)
- */
- async fixOrphanedCards(boardId, progressCallback = null) {
- const cards = ReactiveCache.getCards({ boardId });
- const swimlanes = ReactiveCache.getSwimlanes({ boardId });
- const lists = ReactiveCache.getLists({ boardId });
- let cardsFixed = 0;
- const defaultSwimlane = swimlanes.find(s => s.title === 'Default') || swimlanes[0];
- const totalCards = cards.length;
- for (let i = 0; i < cards.length; i++) {
- const card = cards[i];
- let needsUpdate = false;
- const updates = {};
- // Fix missing swimlaneId
- if (!card.swimlaneId) {
- updates.swimlaneId = defaultSwimlane._id;
- needsUpdate = true;
- }
- // Fix missing listId
- if (!card.listId) {
- // Find or create a default list for this swimlane
- const swimlaneId = updates.swimlaneId || card.swimlaneId;
- let defaultList = lists.find(list =>
- list.swimlaneId === swimlaneId && list.title === 'Default'
- );
- if (!defaultList) {
- // Create a default list for this swimlane
- const newListId = Lists.insert({
- title: 'Default',
- boardId: boardId,
- swimlaneId: swimlaneId,
- sort: 0,
- archived: false,
- createdAt: new Date(),
- modifiedAt: new Date(),
- type: 'list'
- });
- defaultList = { _id: newListId };
- }
- updates.listId = defaultList._id;
- needsUpdate = true;
- }
- if (needsUpdate) {
- Cards.update(card._id, {
- $set: {
- ...updates,
- modifiedAt: new Date()
- }
- });
- cardsFixed++;
- }
- // Update progress
- if (progressCallback && (i % 10 === 0 || i === totalCards - 1)) {
- const progress = Math.round(((i + 1) / totalCards) * 100);
- progressCallback(progress, `Processing card ${i + 1} of ${totalCards}...`);
- }
- }
- return { cardsFixed };
- }
- /**
- * Step 3: Convert shared lists to per-swimlane lists
- */
- async convertSharedListsToPerSwimlane(boardId, progressCallback = null) {
- const cards = ReactiveCache.getCards({ boardId });
- const lists = ReactiveCache.getLists({ boardId });
- const swimlanes = ReactiveCache.getSwimlanes({ boardId });
- let listsProcessed = 0;
- let listsCreated = 0;
- // Group cards by swimlaneId
- const cardsBySwimlane = new Map();
- cards.forEach(card => {
- if (!cardsBySwimlane.has(card.swimlaneId)) {
- cardsBySwimlane.set(card.swimlaneId, []);
- }
- cardsBySwimlane.get(card.swimlaneId).push(card);
- });
- const swimlaneEntries = Array.from(cardsBySwimlane.entries());
- const totalSwimlanes = swimlaneEntries.length;
- // Process each swimlane
- for (let i = 0; i < swimlaneEntries.length; i++) {
- const [swimlaneId, swimlaneCards] = swimlaneEntries[i];
- if (!swimlaneId) continue;
- if (progressCallback) {
- const progress = Math.round(((i + 1) / totalSwimlanes) * 100);
- progressCallback(progress, `Processing swimlane ${i + 1} of ${totalSwimlanes}...`);
- }
- // Get existing lists for this swimlane
- const existingLists = lists.filter(list => list.swimlaneId === swimlaneId);
- const existingListTitles = new Set(existingLists.map(list => list.title));
- // Group cards by their current listId
- const cardsByListId = new Map();
- swimlaneCards.forEach(card => {
- if (!cardsByListId.has(card.listId)) {
- cardsByListId.set(card.listId, []);
- }
- cardsByListId.get(card.listId).push(card);
- });
- // For each listId used by cards in this swimlane
- for (const [listId, cardsInList] of cardsByListId) {
- const originalList = lists.find(l => l._id === listId);
- if (!originalList) continue;
- // Check if this list's swimlaneId matches the card's swimlaneId
- if (originalList.swimlaneId === swimlaneId) {
- // List is already correctly assigned to this swimlane
- listsProcessed++;
- continue;
- }
- // Check if we already have a list with the same title in this swimlane
- let targetList = existingLists.find(list => list.title === originalList.title);
-
- if (!targetList) {
- // Create a new list for this swimlane
- const newListData = {
- title: originalList.title,
- boardId: boardId,
- swimlaneId: swimlaneId,
- sort: originalList.sort || 0,
- archived: originalList.archived || false,
- createdAt: new Date(),
- modifiedAt: new Date(),
- type: originalList.type || 'list'
- };
- // Copy other properties if they exist
- if (originalList.color) newListData.color = originalList.color;
- if (originalList.wipLimit) newListData.wipLimit = originalList.wipLimit;
- if (originalList.wipLimitEnabled) newListData.wipLimitEnabled = originalList.wipLimitEnabled;
- if (originalList.wipLimitSoft) newListData.wipLimitSoft = originalList.wipLimitSoft;
- if (originalList.starred) newListData.starred = originalList.starred;
- if (originalList.collapsed) newListData.collapsed = originalList.collapsed;
- // Insert the new list
- const newListId = Lists.insert(newListData);
- targetList = { _id: newListId, ...newListData };
- listsCreated++;
- }
- // Update all cards in this group to use the correct listId
- for (const card of cardsInList) {
- Cards.update(card._id, {
- $set: {
- listId: targetList._id,
- modifiedAt: new Date()
- }
- });
- }
- listsProcessed++;
- }
- }
- return { listsProcessed, listsCreated };
- }
- /**
- * Step 4: Ensure all lists are per-swimlane
- */
- async ensurePerSwimlaneLists(boardId) {
- const lists = ReactiveCache.getLists({ boardId });
- const swimlanes = ReactiveCache.getSwimlanes({ boardId });
- const defaultSwimlane = swimlanes.find(s => s.title === 'Default') || swimlanes[0];
- let listsProcessed = 0;
- for (const list of lists) {
- if (!list.swimlaneId || list.swimlaneId === '') {
- // Assign to default swimlane
- Lists.update(list._id, {
- $set: {
- swimlaneId: defaultSwimlane._id,
- modifiedAt: new Date()
- }
- });
- listsProcessed++;
- }
- }
- return { listsProcessed };
- }
- /**
- * Step 5: Cleanup empty lists (lists with no cards)
- */
- async cleanupEmptyLists(boardId) {
- const lists = ReactiveCache.getLists({ boardId });
- const cards = ReactiveCache.getCards({ boardId });
- let listsRemoved = 0;
- for (const list of lists) {
- const listCards = cards.filter(card => card.listId === list._id);
-
- if (listCards.length === 0) {
- // Remove empty list
- Lists.remove(list._id);
- listsRemoved++;
-
- if (process.env.DEBUG === 'true') {
- console.log(`Removed empty list: ${list.title} (${list._id})`);
- }
- }
- }
- return { listsRemoved };
- }
- /**
- * Step 6: Validate migration
- */
- async validateMigration(boardId) {
- const issues = this.detectMigrationIssues(boardId);
- const cards = ReactiveCache.getCards({ boardId });
- const lists = ReactiveCache.getLists({ boardId });
- // Check that all cards have valid swimlaneId and listId
- const validCards = cards.filter(card => card.swimlaneId && card.listId);
- const invalidCards = cards.length - validCards.length;
- // Check that all lists have swimlaneId
- const validLists = lists.filter(list => list.swimlaneId && list.swimlaneId !== '');
- const invalidLists = lists.length - validLists.length;
- return {
- issuesRemaining: issues.length,
- totalCards: cards.length,
- validCards,
- invalidCards,
- totalLists: lists.length,
- validLists,
- invalidLists,
- migrationSuccessful: issues.length === 0 && invalidCards === 0 && invalidLists === 0
- };
- }
- /**
- * Step 7: Fix avatar URLs (remove problematic auth parameters and fix URL formats)
- */
- async fixAvatarUrls(boardId) {
- const users = ReactiveCache.getUsers({});
- let avatarsFixed = 0;
- for (const user of users) {
- if (user.profile && user.profile.avatarUrl) {
- const avatarUrl = user.profile.avatarUrl;
- let needsUpdate = false;
- let cleanUrl = avatarUrl;
-
- // Check if URL has problematic parameters
- if (avatarUrl.includes('auth=false') || avatarUrl.includes('brokenIsFine=true')) {
- // Remove problematic parameters
- cleanUrl = cleanUrl.replace(/[?&]auth=false/g, '');
- cleanUrl = cleanUrl.replace(/[?&]brokenIsFine=true/g, '');
- cleanUrl = cleanUrl.replace(/\?&/g, '?');
- cleanUrl = cleanUrl.replace(/\?$/g, '');
- needsUpdate = true;
- }
-
- // Check if URL is using old CollectionFS format
- if (avatarUrl.includes('/cfs/files/avatars/')) {
- cleanUrl = cleanUrl.replace('/cfs/files/avatars/', '/cdn/storage/avatars/');
- needsUpdate = true;
- }
-
- // Check if URL is missing the /cdn/storage/avatars/ prefix
- if (avatarUrl.includes('avatars/') && !avatarUrl.includes('/cdn/storage/avatars/') && !avatarUrl.includes('/cfs/files/avatars/')) {
- // This might be a relative URL, make it absolute
- if (!avatarUrl.startsWith('http') && !avatarUrl.startsWith('/')) {
- cleanUrl = `/cdn/storage/avatars/${avatarUrl}`;
- needsUpdate = true;
- }
- }
-
- if (needsUpdate) {
- // Update user's avatar URL
- Users.update(user._id, {
- $set: {
- 'profile.avatarUrl': cleanUrl,
- modifiedAt: new Date()
- }
- });
-
- avatarsFixed++;
- }
- }
- }
- return { avatarsFixed };
- }
- /**
- * Step 8: Fix attachment URLs (remove problematic auth parameters and fix URL formats)
- */
- async fixAttachmentUrls(boardId) {
- const attachments = ReactiveCache.getAttachments({});
- let attachmentsFixed = 0;
- for (const attachment of attachments) {
- // Check if attachment has URL field that needs fixing
- if (attachment.url) {
- const attachmentUrl = attachment.url;
- let needsUpdate = false;
- let cleanUrl = attachmentUrl;
-
- // Check if URL has problematic parameters
- if (attachmentUrl.includes('auth=false') || attachmentUrl.includes('brokenIsFine=true')) {
- // Remove problematic parameters
- cleanUrl = cleanUrl.replace(/[?&]auth=false/g, '');
- cleanUrl = cleanUrl.replace(/[?&]brokenIsFine=true/g, '');
- cleanUrl = cleanUrl.replace(/\?&/g, '?');
- cleanUrl = cleanUrl.replace(/\?$/g, '');
- needsUpdate = true;
- }
-
- // Check if URL is using old CollectionFS format
- if (attachmentUrl.includes('/cfs/files/attachments/')) {
- cleanUrl = cleanUrl.replace('/cfs/files/attachments/', '/cdn/storage/attachments/');
- needsUpdate = true;
- }
-
- // Check if URL has /original/ path that should be removed
- if (attachmentUrl.includes('/original/')) {
- cleanUrl = cleanUrl.replace(/\/original\/[^\/\?#]+/, '');
- needsUpdate = true;
- }
-
- // If we have a file ID, generate a universal URL
- const fileId = attachment._id;
- if (fileId && !isUniversalFileUrl(cleanUrl, 'attachment')) {
- cleanUrl = generateUniversalAttachmentUrl(fileId);
- needsUpdate = true;
- }
-
- if (needsUpdate) {
- // Update attachment URL
- Attachments.update(attachment._id, {
- $set: {
- url: cleanUrl,
- modifiedAt: new Date()
- }
- });
-
- attachmentsFixed++;
- }
- }
- }
- return { attachmentsFixed };
- }
- /**
- * Get migration status for a board
- */
- getMigrationStatus(boardId) {
- try {
- const board = ReactiveCache.getBoard(boardId);
- if (!board) {
- return { status: 'board_not_found' };
- }
- if (board.comprehensiveMigrationCompleted) {
- return {
- status: 'completed',
- completedAt: board.comprehensiveMigrationCompletedAt,
- results: board.comprehensiveMigrationResults
- };
- }
- const needsMigration = this.needsMigration(boardId);
- const issues = this.detectMigrationIssues(boardId);
-
- return {
- status: needsMigration ? 'needed' : 'not_needed',
- issues,
- issueCount: issues.length
- };
- } catch (error) {
- console.error('Error getting migration status:', error);
- return { status: 'error', error: error.message };
- }
- }
- }
- // Export singleton instance
- export const comprehensiveBoardMigration = new ComprehensiveBoardMigration();
- // Meteor methods
- Meteor.methods({
- 'comprehensiveBoardMigration.check'(boardId) {
- check(boardId, String);
-
- if (!this.userId) {
- throw new Meteor.Error('not-authorized');
- }
-
- return comprehensiveBoardMigration.getMigrationStatus(boardId);
- },
- 'comprehensiveBoardMigration.execute'(boardId) {
- check(boardId, String);
-
- if (!this.userId) {
- throw new Meteor.Error('not-authorized');
- }
-
- return comprehensiveBoardMigration.executeMigration(boardId);
- },
- 'comprehensiveBoardMigration.needsMigration'(boardId) {
- check(boardId, String);
-
- if (!this.userId) {
- throw new Meteor.Error('not-authorized');
- }
-
- return comprehensiveBoardMigration.needsMigration(boardId);
- },
- 'comprehensiveBoardMigration.detectIssues'(boardId) {
- check(boardId, String);
-
- if (!this.userId) {
- throw new Meteor.Error('not-authorized');
- }
-
- return comprehensiveBoardMigration.detectMigrationIssues(boardId);
- },
- 'comprehensiveBoardMigration.fixAvatarUrls'(boardId) {
- check(boardId, String);
-
- if (!this.userId) {
- throw new Meteor.Error('not-authorized');
- }
-
- return comprehensiveBoardMigration.fixAvatarUrls(boardId);
- }
- });
|