diff --git a/client/00-startup.js b/client/00-startup.js index 0a98d76ef..a6f049322 100644 --- a/client/00-startup.js +++ b/client/00-startup.js @@ -6,12 +6,12 @@ if ('serviceWorker' in navigator) { } // Import board converter for on-demand conversion -import '/imports/lib/boardConverter'; -import '/imports/components/boardConversionProgress'; +import '/client/lib/boardConverter'; +import '/client/components/boardConversionProgress'; // Import migration manager and progress UI -import '/imports/lib/migrationManager'; -import '/imports/components/migrationProgress'; +import '/client/lib/migrationManager'; +import '/client/components/migrationProgress'; // Import cron settings -import '/imports/components/settings/cronSettings'; +import '/client/components/settings/cronSettings'; diff --git a/client/components/boardConversionProgress.js b/client/components/boardConversionProgress.js index e928ac3d2..454df5006 100644 --- a/client/components/boardConversionProgress.js +++ b/client/components/boardConversionProgress.js @@ -1,31 +1,37 @@ import { Template } from 'meteor/templating'; import { ReactiveVar } from 'meteor/reactive-var'; -import { boardConverter } from '/imports/lib/boardConverter'; +import { + boardConverter, + isConverting, + conversionProgress, + conversionStatus, + conversionEstimatedTime +} from '/client/lib/boardConverter'; Template.boardConversionProgress.helpers({ isConverting() { - return boardConverter.isConverting.get(); + return isConverting.get(); }, conversionProgress() { - return boardConverter.conversionProgress.get(); + return conversionProgress.get(); }, conversionStatus() { - return boardConverter.conversionStatus.get(); + return conversionStatus.get(); }, conversionEstimatedTime() { - return boardConverter.conversionEstimatedTime.get(); + return conversionEstimatedTime.get(); } }); Template.boardConversionProgress.onCreated(function() { // Subscribe to conversion state changes this.autorun(() => { - boardConverter.isConverting.get(); - boardConverter.conversionProgress.get(); - boardConverter.conversionStatus.get(); - boardConverter.conversionEstimatedTime.get(); + isConverting.get(); + conversionProgress.get(); + conversionStatus.get(); + conversionEstimatedTime.get(); }); }); diff --git a/client/components/boards/boardBody.jade b/client/components/boards/boardBody.jade index 5dfd4373b..7e5530ffe 100644 --- a/client/components/boards/boardBody.jade +++ b/client/components/boards/boardBody.jade @@ -1,7 +1,8 @@ template(name="board") - if isMigrating + + if isMigrating.get +migrationProgress - else if isConverting + else if isConverting.get +boardConversionProgress else if isBoardReady.get if currentBoard @@ -46,7 +47,12 @@ template(name="boardBody") else if isViewCalendar +calendarView else - +listsGroup(currentBoard) + // Default view - show swimlanes if they exist, otherwise show lists + if hasSwimlanes + each currentBoard.swimlanes + +swimlane(this) + else + +listsGroup(currentBoard) +sidebar template(name="calendarView") diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js index 52854c20c..5ebb23bec 100644 --- a/client/components/boards/boardBody.js +++ b/client/components/boards/boardBody.js @@ -1,8 +1,11 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; import dragscroll from '@wekanteam/dragscroll'; -import { boardConverter } from '/imports/lib/boardConverter'; -import { migrationManager } from '/imports/lib/migrationManager'; +import { boardConverter } from '/client/lib/boardConverter'; +import { migrationManager } from '/client/lib/migrationManager'; +import { attachmentMigrationManager } from '/client/lib/attachmentMigrationManager'; +import { Swimlanes } from '/models/swimlanes'; +import { Lists } from '/models/lists'; const subManager = new SubsManager(); const { calculateIndex } = Utils; @@ -13,6 +16,7 @@ BlazeComponent.extendComponent({ this.isBoardReady = new ReactiveVar(false); this.isConverting = new ReactiveVar(false); this.isMigrating = new ReactiveVar(false); + this._swimlaneCreated = new Set(); // Track boards where we've created swimlanes // The pattern we use to manually handle data loading is described here: // https://kadira.io/academy/meteor-routing-guide/content/subscriptions-and-data-management/using-subs-manager @@ -21,10 +25,14 @@ BlazeComponent.extendComponent({ this.autorun(() => { const currentBoardId = Session.get('currentBoard'); if (!currentBoardId) return; + const handle = subManager.subscribe('board', currentBoardId, false); + Tracker.nonreactive(() => { Tracker.autorun(() => { if (handle.ready()) { + // Ensure default swimlane exists (only once per board) + this.ensureDefaultSwimlane(currentBoardId); // Check if board needs conversion this.checkAndConvertBoard(currentBoardId); } else { @@ -35,17 +43,54 @@ BlazeComponent.extendComponent({ }); }, + ensureDefaultSwimlane(boardId) { + // Only create swimlane once per board + if (this._swimlaneCreated.has(boardId)) { + return; + } + + try { + const board = ReactiveCache.getBoard(boardId); + if (!board) return; + + const swimlanes = board.swimlanes(); + + if (swimlanes.length === 0) { + const swimlaneId = Swimlanes.insert({ + title: 'Default', + boardId: boardId, + }); + this._swimlaneCreated.add(boardId); + } else { + this._swimlaneCreated.add(boardId); + } + } catch (error) { + console.error('Error creating default swimlane:', error); + } + }, + async checkAndConvertBoard(boardId) { try { - // First check if migrations need to be run - if (migrationManager.needsMigration()) { + const board = ReactiveCache.getBoard(boardId); + if (!board) { + this.isBoardReady.set(true); + return; + } + + // Check if board needs migration based on migration version + const needsMigration = !board.migrationVersion || board.migrationVersion < 1; + + if (needsMigration) { + // Start background migration for old boards this.isMigrating.set(true); - await migrationManager.startMigration(); + await this.startBackgroundMigration(boardId); this.isMigrating.set(false); } - // Then check if board needs conversion - if (boardConverter.needsConversion(boardId)) { + // Check if board needs conversion (for old structure) + const needsConversion = boardConverter.needsConversion(boardId); + + if (needsConversion) { this.isConverting.set(true); const success = await boardConverter.convertBoard(boardId); this.isConverting.set(false); @@ -53,12 +98,15 @@ BlazeComponent.extendComponent({ if (success) { this.isBoardReady.set(true); } else { - console.error('Board conversion failed'); + console.error('Board conversion failed, setting ready to true anyway'); this.isBoardReady.set(true); // Still show board even if conversion failed } } else { this.isBoardReady.set(true); } + + // Start attachment migration in background if needed + this.startAttachmentMigrationIfNeeded(boardId); } catch (error) { console.error('Error during board conversion check:', error); this.isConverting.set(false); @@ -67,8 +115,39 @@ BlazeComponent.extendComponent({ } }, + async startBackgroundMigration(boardId) { + try { + // Start background migration using the cron system + Meteor.call('boardMigration.startBoardMigration', boardId, (error, result) => { + if (error) { + console.error('Failed to start background migration:', error); + } else { + console.log('Background migration started for board:', boardId); + } + }); + } catch (error) { + console.error('Error starting background migration:', error); + } + }, + + async startAttachmentMigrationIfNeeded(boardId) { + try { + // Check if there are unconverted attachments + const unconvertedAttachments = attachmentMigrationManager.getUnconvertedAttachments(boardId); + + if (unconvertedAttachments.length > 0) { + console.log(`Starting attachment migration for ${unconvertedAttachments.length} attachments in board ${boardId}`); + await attachmentMigrationManager.startAttachmentMigration(boardId); + } + } catch (error) { + console.error('Error starting attachment migration:', error); + } + }, + onlyShowCurrentCard() { - return Utils.isMiniScreen() && Utils.getCurrentCardId(true); + const isMiniScreen = Utils.isMiniScreen(); + const currentCardId = Utils.getCurrentCardId(true); + return isMiniScreen && currentCardId; }, goHome() { @@ -82,6 +161,14 @@ BlazeComponent.extendComponent({ isMigrating() { return this.isMigrating.get(); }, + + isBoardReady() { + return this.isBoardReady.get(); + }, + + currentBoard() { + return Utils.getCurrentBoard(); + }, }).register('board'); BlazeComponent.extendComponent({ @@ -95,33 +182,37 @@ BlazeComponent.extendComponent({ // fix swimlanes sort field if there are null values const currentBoardData = Utils.getCurrentBoard(); - const nullSortSwimlanes = currentBoardData.nullSortSwimlanes(); - if (nullSortSwimlanes.length > 0) { - const swimlanes = currentBoardData.swimlanes(); - let count = 0; - swimlanes.forEach(s => { - Swimlanes.update(s._id, { - $set: { - sort: count, - }, + if (currentBoardData && Swimlanes) { + const nullSortSwimlanes = currentBoardData.nullSortSwimlanes(); + if (nullSortSwimlanes.length > 0) { + const swimlanes = currentBoardData.swimlanes(); + let count = 0; + swimlanes.forEach(s => { + Swimlanes.update(s._id, { + $set: { + sort: count, + }, + }); + count += 1; }); - count += 1; - }); + } } // fix lists sort field if there are null values - const nullSortLists = currentBoardData.nullSortLists(); - if (nullSortLists.length > 0) { - const lists = currentBoardData.lists(); - let count = 0; - lists.forEach(l => { - Lists.update(l._id, { - $set: { - sort: count, - }, + if (currentBoardData && Lists) { + const nullSortLists = currentBoardData.nullSortLists(); + if (nullSortLists.length > 0) { + const lists = currentBoardData.lists(); + let count = 0; + lists.forEach(l => { + Lists.update(l._id, { + $set: { + sort: count, + }, + }); + count += 1; }); - count += 1; - }); + } } }, onRendered() { @@ -461,51 +552,100 @@ BlazeComponent.extendComponent({ notDisplayThisBoard() { let allowPrivateVisibilityOnly = TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly'); let currentBoard = Utils.getCurrentBoard(); - if (allowPrivateVisibilityOnly !== undefined && allowPrivateVisibilityOnly.booleanValue && currentBoard.permission == 'public') { - return true; - } - - return false; + return allowPrivateVisibilityOnly !== undefined && allowPrivateVisibilityOnly.booleanValue && currentBoard && currentBoard.permission == 'public'; }, isViewSwimlanes() { const currentUser = ReactiveCache.getCurrentUser(); + let boardView; + if (currentUser) { - return (currentUser.profile || {}).boardView === 'board-view-swimlanes'; + boardView = (currentUser.profile || {}).boardView; } else { - return ( - window.localStorage.getItem('boardView') === 'board-view-swimlanes' - ); + boardView = window.localStorage.getItem('boardView'); } - }, - - hasSwimlanes() { - return Utils.getCurrentBoard().swimlanes().length > 0; + + // If no board view is set, default to swimlanes + if (!boardView) { + boardView = 'board-view-swimlanes'; + } + + return boardView === 'board-view-swimlanes'; }, isViewLists() { const currentUser = ReactiveCache.getCurrentUser(); + let boardView; + if (currentUser) { - return (currentUser.profile || {}).boardView === 'board-view-lists'; + boardView = (currentUser.profile || {}).boardView; } else { - return window.localStorage.getItem('boardView') === 'board-view-lists'; + boardView = window.localStorage.getItem('boardView'); } + + return boardView === 'board-view-lists'; }, isViewCalendar() { const currentUser = ReactiveCache.getCurrentUser(); + let boardView; + if (currentUser) { - return (currentUser.profile || {}).boardView === 'board-view-cal'; + boardView = (currentUser.profile || {}).boardView; } else { - return window.localStorage.getItem('boardView') === 'board-view-cal'; + boardView = window.localStorage.getItem('boardView'); } + + return boardView === 'board-view-cal'; }, + hasSwimlanes() { + const currentBoard = Utils.getCurrentBoard(); + if (!currentBoard) return false; + + const swimlanes = currentBoard.swimlanes(); + return swimlanes.length > 0; + }, + + isVerticalScrollbars() { const user = ReactiveCache.getCurrentUser(); return user && user.isVerticalScrollbars(); }, + boardView() { + return Utils.boardView(); + }, + + debugBoardState() { + const currentBoard = Utils.getCurrentBoard(); + const currentBoardId = Session.get('currentBoard'); + const isBoardReady = this.isBoardReady.get(); + const isConverting = this.isConverting.get(); + const isMigrating = this.isMigrating.get(); + const boardView = Utils.boardView(); + + console.log('=== BOARD DEBUG STATE ==='); + console.log('currentBoardId:', currentBoardId); + console.log('currentBoard:', !!currentBoard, currentBoard ? currentBoard.title : 'none'); + console.log('isBoardReady:', isBoardReady); + console.log('isConverting:', isConverting); + console.log('isMigrating:', isMigrating); + console.log('boardView:', boardView); + console.log('========================'); + + return { + currentBoardId, + hasCurrentBoard: !!currentBoard, + currentBoardTitle: currentBoard ? currentBoard.title : 'none', + isBoardReady, + isConverting, + isMigrating, + boardView + }; + }, + + openNewListForm() { if (this.isViewSwimlanes()) { // The form had been removed in 416b17062e57f215206e93a85b02ef9eb1ab4902 diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js index 5d54c2dd3..ffba91259 100644 --- a/client/components/boards/boardHeader.js +++ b/client/components/boards/boardHeader.js @@ -81,11 +81,20 @@ BlazeComponent.extendComponent({ Modal.open('archivedBoards'); }, 'click .js-toggle-board-view': Popup.open('boardChangeView'), - 'click .js-toggle-sidebar'() { - Sidebar.toggle(); - }, + // Sidebar toggle is handled by the sidebar component itself + // 'click .js-toggle-sidebar'() { + // if (Sidebar) { + // Sidebar.toggle(); + // } else { + // console.warn('Sidebar not available for toggle'); + // } + // }, 'click .js-open-filter-view'() { - Sidebar.setView('filter'); + if (Sidebar) { + Sidebar.setView('filter'); + } else { + console.warn('Sidebar not available for setView'); + } }, 'click .js-sort-cards': Popup.open('cardsSort'), /* @@ -102,14 +111,22 @@ BlazeComponent.extendComponent({ */ 'click .js-filter-reset'(event) { event.stopPropagation(); - Sidebar.setView(); + if (Sidebar) { + Sidebar.setView(); + } else { + console.warn('Sidebar not available for setView'); + } Filter.reset(); }, 'click .js-sort-reset'() { Session.set('sortBy', ''); }, 'click .js-open-search-view'() { - Sidebar.setView('search'); + if (Sidebar) { + Sidebar.setView('search'); + } else { + console.warn('Sidebar not available for setView'); + } }, 'click .js-multiselection-activate'() { const currentCard = Utils.getCurrentCardId(); @@ -203,6 +220,7 @@ const CreateBoard = BlazeComponent.extendComponent({ title: title, permission: 'private', type: 'template-container', + migrationVersion: 1, // Latest version - no migration needed }), ); @@ -246,6 +264,7 @@ const CreateBoard = BlazeComponent.extendComponent({ Boards.insert({ title, permission: visibility, + migrationVersion: 1, // Latest version - no migration needed }), ); diff --git a/client/components/cards/attachments.css b/client/components/cards/attachments.css index 64a0c8735..becb29160 100644 --- a/client/components/cards/attachments.css +++ b/client/components/cards/attachments.css @@ -336,3 +336,36 @@ margin-top: 10px; } } + +/* Attachment migration styles */ +.attachment-item.migrating { + position: relative; + opacity: 0.7; +} + +.attachment-migration-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.9); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + z-index: 10; + border-radius: 4px; +} + +.migration-spinner { + font-size: 24px; + color: #007cba; + margin-bottom: 8px; +} + +.migration-text { + font-size: 12px; + color: #666; + text-align: center; +} diff --git a/client/components/cards/attachments.jade b/client/components/cards/attachments.jade index 5e1d41da1..da52688f2 100644 --- a/client/components/cards/attachments.jade +++ b/client/components/cards/attachments.jade @@ -57,7 +57,7 @@ template(name="attachmentGallery") each attachments - .attachment-item + .attachment-item(class="{{#if isAttachmentMigrating _id}}migrating{{/if}}") .attachment-thumbnail-container.open-preview(data-attachment-id="{{_id}}" data-card-id="{{ meta.cardId }}") if link if(isImage) @@ -97,6 +97,12 @@ template(name="attachmentGallery") i.fa.fa-trash.icon(title="{{_ 'delete'}}") a.fa.fa-navicon.icon.js-open-attachment-menu(data-attachment-link="{{link}}" title="{{_ 'attachmentActionsPopup-title'}}") + // Migration spinner overlay + if isAttachmentMigrating _id + .attachment-migration-overlay + .migration-spinner + i.fa.fa-cog.fa-spin + .migration-text {{_ 'migrating-attachment'}} template(name="attachmentActionsPopup") ul.pop-over-list diff --git a/client/components/cards/attachments.js b/client/components/cards/attachments.js index 51304cf92..18788f22d 100644 --- a/client/components/cards/attachments.js +++ b/client/components/cards/attachments.js @@ -3,6 +3,7 @@ import { ObjectID } from 'bson'; import DOMPurify from 'dompurify'; import { sanitizeHTML, sanitizeText } from '/imports/lib/secureDOMPurify'; import uploadProgressManager from '../../lib/uploadProgressManager'; +import { attachmentMigrationManager } from '/client/lib/attachmentMigrationManager'; const filesize = require('filesize'); const prettyMilliseconds = require('pretty-ms'); @@ -576,3 +577,20 @@ BlazeComponent.extendComponent({ ] } }).register('attachmentRenamePopup'); + +// Template helpers for attachment migration status +Template.registerHelper('attachmentMigrationStatus', function(attachmentId) { + return attachmentMigrationManager.getAttachmentMigrationStatus(attachmentId); +}); + +Template.registerHelper('isAttachmentMigrating', function(attachmentId) { + return attachmentMigrationManager.isAttachmentBeingMigrated(attachmentId); +}); + +Template.registerHelper('attachmentMigrationProgress', function() { + return attachmentMigrationManager.attachmentMigrationProgress.get(); +}); + +Template.registerHelper('attachmentMigrationStatusText', function() { + return attachmentMigrationManager.attachmentMigrationStatus.get(); +}); diff --git a/client/components/cards/minicard.js b/client/components/cards/minicard.js index 54898e4b5..2cecccd7e 100644 --- a/client/components/cards/minicard.js +++ b/client/components/cards/minicard.js @@ -206,7 +206,9 @@ Template.minicard.helpers({ // Show list name if either: // 1. Board-wide setting is enabled, OR // 2. This specific card has the setting enabled - return this.currentBoard.allowsShowListsOnMinicard || this.showListOnMinicard; + const currentBoard = this.currentBoard; + if (!currentBoard) return false; + return currentBoard.allowsShowListsOnMinicard || this.showListOnMinicard; } }); diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index 9ab4fcdc7..529c70fc8 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -472,6 +472,14 @@ BlazeComponent.extendComponent({ if (!this.selectedBoardId.get()) { return []; } + const board = ReactiveCache.getBoard(this.selectedBoardId.get()); + if (!board) { + return []; + } + + // Ensure default swimlane exists + board.getDefaultSwimline(); + const swimlanes = ReactiveCache.getSwimlanes( { boardId: this.selectedBoardId.get() diff --git a/client/components/migrationProgress.js b/client/components/migrationProgress.js index d36c264f3..83a05ea36 100644 --- a/client/components/migrationProgress.js +++ b/client/components/migrationProgress.js @@ -1,30 +1,38 @@ import { Template } from 'meteor/templating'; -import { migrationManager } from '/imports/lib/migrationManager'; +import { + migrationManager, + isMigrating, + migrationProgress, + migrationStatus, + migrationCurrentStep, + migrationEstimatedTime, + migrationSteps +} from '/client/lib/migrationManager'; Template.migrationProgress.helpers({ isMigrating() { - return migrationManager.isMigrating.get(); + return isMigrating.get(); }, migrationProgress() { - return migrationManager.migrationProgress.get(); + return migrationProgress.get(); }, migrationStatus() { - return migrationManager.migrationStatus.get(); + return migrationStatus.get(); }, migrationCurrentStep() { - return migrationManager.migrationCurrentStep.get(); + return migrationCurrentStep.get(); }, migrationEstimatedTime() { - return migrationManager.migrationEstimatedTime.get(); + return migrationEstimatedTime.get(); }, migrationSteps() { - const steps = migrationManager.migrationSteps.get(); - const currentStep = migrationManager.migrationCurrentStep.get(); + const steps = migrationSteps.get(); + const currentStep = migrationCurrentStep.get(); return steps.map(step => ({ ...step, @@ -36,11 +44,11 @@ Template.migrationProgress.helpers({ Template.migrationProgress.onCreated(function() { // Subscribe to migration state changes this.autorun(() => { - migrationManager.isMigrating.get(); - migrationManager.migrationProgress.get(); - migrationManager.migrationStatus.get(); - migrationManager.migrationCurrentStep.get(); - migrationManager.migrationEstimatedTime.get(); - migrationManager.migrationSteps.get(); + isMigrating.get(); + migrationProgress.get(); + migrationStatus.get(); + migrationCurrentStep.get(); + migrationEstimatedTime.get(); + migrationSteps.get(); }); }); diff --git a/client/components/settings/adminReports.js b/client/components/settings/adminReports.js index 61d327298..23a438347 100644 --- a/client/components/settings/adminReports.js +++ b/client/components/settings/adminReports.js @@ -112,7 +112,7 @@ class AdminReport extends BlazeComponent { } resultsCount() { - return this.collection.find().countDocuments(); + return this.collection.find().count(); } fileSize(size) { diff --git a/client/components/settings/cronSettings.js b/client/components/settings/cronSettings.js index 4920a4469..c56de5ed5 100644 --- a/client/components/settings/cronSettings.js +++ b/client/components/settings/cronSettings.js @@ -30,28 +30,33 @@ Template.cronSettings.onCreated(function() { this.boardMigrationStats = new ReactiveVar({}); // Load initial data - this.loadCronData(); + loadCronData(this); }); Template.cronSettings.helpers({ loading() { - return Template.instance().loading.get(); + const instance = Template.instance(); + return instance && instance.loading ? instance.loading.get() : true; }, showMigrations() { - return Template.instance().showMigrations.get(); + const instance = Template.instance(); + return instance && instance.showMigrations ? instance.showMigrations.get() : true; }, showBoardOperations() { - return Template.instance().showBoardOperations.get(); + const instance = Template.instance(); + return instance && instance.showBoardOperations ? instance.showBoardOperations.get() : false; }, showJobs() { - return Template.instance().showJobs.get(); + const instance = Template.instance(); + return instance && instance.showJobs ? instance.showJobs.get() : false; }, showAddJob() { - return Template.instance().showAddJob.get(); + const instance = Template.instance(); + return instance && instance.showAddJob ? instance.showAddJob.get() : false; }, migrationProgress() { @@ -86,27 +91,33 @@ Template.cronSettings.helpers({ }, boardOperations() { - return Template.instance().boardOperations.get(); + const instance = Template.instance(); + return instance && instance.boardOperations ? instance.boardOperations.get() : []; }, operationStats() { - return Template.instance().operationStats.get(); + const instance = Template.instance(); + return instance && instance.operationStats ? instance.operationStats.get() : {}; }, pagination() { - return Template.instance().pagination.get(); + const instance = Template.instance(); + return instance && instance.pagination ? instance.pagination.get() : {}; }, queueStats() { - return Template.instance().queueStats.get(); + const instance = Template.instance(); + return instance && instance.queueStats ? instance.queueStats.get() : {}; }, systemResources() { - return Template.instance().systemResources.get(); + const instance = Template.instance(); + return instance && instance.systemResources ? instance.systemResources.get() : {}; }, boardMigrationStats() { - return Template.instance().boardMigrationStats.get(); + const instance = Template.instance(); + return instance && instance.boardMigrationStats ? instance.boardMigrationStats.get() : {}; }, formatDateTime(date) { @@ -146,7 +157,7 @@ Template.cronSettings.events({ instance.showBoardOperations.set(true); instance.showJobs.set(false); instance.showAddJob.set(false); - instance.loadBoardOperations(); + loadBoardOperations(instance); }, 'click .js-cron-jobs'(event) { @@ -156,7 +167,7 @@ Template.cronSettings.events({ instance.showBoardOperations.set(false); instance.showJobs.set(true); instance.showAddJob.set(false); - instance.loadCronJobs(); + loadCronJobs(instance); }, 'click .js-cron-add'(event) { @@ -174,8 +185,8 @@ Template.cronSettings.events({ console.error('Failed to start migrations:', error); alert('Failed to start migrations: ' + error.message); } else { - console.log('Migrations started successfully'); - Template.instance().pollMigrationProgress(); + // Migrations started successfully + pollMigrationProgress(Template.instance()); } }); }, @@ -204,7 +215,7 @@ Template.cronSettings.events({ 'click .js-refresh-jobs'(event) { event.preventDefault(); - Template.instance().loadCronJobs(); + loadCronJobs(Template.instance()); }, 'click .js-start-job'(event) { @@ -216,7 +227,7 @@ Template.cronSettings.events({ alert('Failed to start job: ' + error.message); } else { console.log('Job started successfully'); - Template.instance().loadCronJobs(); + loadCronJobs(Template.instance()); } }); }, @@ -230,7 +241,7 @@ Template.cronSettings.events({ alert('Failed to pause job: ' + error.message); } else { console.log('Job paused successfully'); - Template.instance().loadCronJobs(); + loadCronJobs(Template.instance()); } }); }, @@ -244,7 +255,7 @@ Template.cronSettings.events({ alert('Failed to stop job: ' + error.message); } else { console.log('Job stopped successfully'); - Template.instance().loadCronJobs(); + loadCronJobs(Template.instance()); } }); }, @@ -259,7 +270,7 @@ Template.cronSettings.events({ alert('Failed to remove job: ' + error.message); } else { console.log('Job removed successfully'); - Template.instance().loadCronJobs(); + loadCronJobs(Template.instance()); } }); } @@ -286,7 +297,7 @@ Template.cronSettings.events({ form.reset(); Template.instance().showJobs.set(true); Template.instance().showAddJob.set(false); - Template.instance().loadCronJobs(); + loadCronJobs(Template.instance()); } }); }, @@ -300,7 +311,7 @@ Template.cronSettings.events({ 'click .js-refresh-board-operations'(event) { event.preventDefault(); - Template.instance().loadBoardOperations(); + loadBoardOperations(Template.instance()); }, 'click .js-start-test-operation'(event) { @@ -318,7 +329,7 @@ Template.cronSettings.events({ alert('Failed to start test operation: ' + error.message); } else { console.log('Test operation started:', result); - Template.instance().loadBoardOperations(); + loadBoardOperations(Template.instance()); } }); }, @@ -328,7 +339,7 @@ Template.cronSettings.events({ const instance = Template.instance(); instance.searchTerm.set(searchTerm); instance.currentPage.set(1); - instance.loadBoardOperations(); + loadBoardOperations(instance); }, 'click .js-prev-page'(event) { @@ -337,7 +348,7 @@ Template.cronSettings.events({ const currentPage = instance.currentPage.get(); if (currentPage > 1) { instance.currentPage.set(currentPage - 1); - instance.loadBoardOperations(); + loadBoardOperations(instance); } }, @@ -348,7 +359,7 @@ Template.cronSettings.events({ const pagination = instance.pagination.get(); if (currentPage < pagination.totalPages) { instance.currentPage.set(currentPage + 1); - instance.loadBoardOperations(); + loadBoardOperations(instance); } }, @@ -389,16 +400,17 @@ Template.cronSettings.events({ console.error('Failed to force board scan:', error); alert('Failed to force board scan: ' + error.message); } else { - console.log('Board scan started successfully'); + // Board scan started successfully // Refresh the data - Template.instance().loadBoardOperations(); + loadBoardOperations(Template.instance()); } }); } }); -Template.cronSettings.prototype.loadCronData = function() { - this.loading.set(true); +// Helper functions for cron settings +function loadCronData(instance) { + instance.loading.set(true); // Load migration progress Meteor.call('cron.getMigrationProgress', (error, result) => { @@ -412,21 +424,20 @@ Template.cronSettings.prototype.loadCronData = function() { }); // Load cron jobs - this.loadCronJobs(); + loadCronJobs(instance); - this.loading.set(false); -}; + instance.loading.set(false); +} -Template.cronSettings.prototype.loadCronJobs = function() { +function loadCronJobs(instance) { Meteor.call('cron.getJobs', (error, result) => { if (result) { cronJobs.set(result); } }); -}; +} -Template.cronSettings.prototype.loadBoardOperations = function() { - const instance = this; +function loadBoardOperations(instance) { const page = instance.currentPage.get(); const limit = instance.pageSize.get(); const searchTerm = instance.searchTerm.get(); @@ -474,9 +485,9 @@ Template.cronSettings.prototype.loadBoardOperations = function() { instance.boardMigrationStats.set(result); } }); -}; +} -Template.cronSettings.prototype.pollMigrationProgress = function() { +function pollMigrationProgress(instance) { const pollInterval = setInterval(() => { Meteor.call('cron.getMigrationProgress', (error, result) => { if (result) { @@ -493,4 +504,4 @@ Template.cronSettings.prototype.pollMigrationProgress = function() { } }); }, 1000); -}; +} diff --git a/client/lib/attachmentMigrationManager.js b/client/lib/attachmentMigrationManager.js new file mode 100644 index 000000000..2fca04644 --- /dev/null +++ b/client/lib/attachmentMigrationManager.js @@ -0,0 +1,169 @@ +/** + * Attachment Migration Manager + * Handles migration of attachments from old structure to new structure + * with UI feedback and spinners for unconverted attachments + */ + +import { ReactiveVar } from 'meteor/reactive-var'; +import { ReactiveCache } from '/imports/reactiveCache'; + +// Reactive variables for attachment migration progress +export const attachmentMigrationProgress = new ReactiveVar(0); +export const attachmentMigrationStatus = new ReactiveVar(''); +export const isMigratingAttachments = new ReactiveVar(false); +export const unconvertedAttachments = new ReactiveVar([]); + +class AttachmentMigrationManager { + constructor() { + this.migrationCache = new Map(); // Cache migrated attachment IDs + } + + /** + * Check if an attachment needs migration + * @param {string} attachmentId - The attachment ID to check + * @returns {boolean} - True if attachment needs migration + */ + needsMigration(attachmentId) { + if (this.migrationCache.has(attachmentId)) { + return false; // Already migrated + } + + try { + const attachment = ReactiveCache.getAttachment(attachmentId); + if (!attachment) return false; + + // Check if attachment has old structure (no meta field or missing required fields) + return !attachment.meta || + !attachment.meta.cardId || + !attachment.meta.boardId || + !attachment.meta.listId; + } catch (error) { + console.error('Error checking if attachment needs migration:', error); + return false; + } + } + + /** + * Get all unconverted attachments for a board + * @param {string} boardId - The board ID + * @returns {Array} - Array of unconverted attachments + */ + getUnconvertedAttachments(boardId) { + try { + const attachments = ReactiveCache.getAttachments({ + 'meta.boardId': boardId + }); + + return attachments.filter(attachment => this.needsMigration(attachment._id)); + } catch (error) { + console.error('Error getting unconverted attachments:', error); + return []; + } + } + + /** + * Start migration for attachments in a board + * @param {string} boardId - The board ID + */ + async startAttachmentMigration(boardId) { + if (isMigratingAttachments.get()) { + return; // Already migrating + } + + isMigratingAttachments.set(true); + attachmentMigrationStatus.set('Starting attachment migration...'); + attachmentMigrationProgress.set(0); + + try { + const unconverted = this.getUnconvertedAttachments(boardId); + unconvertedAttachments.set(unconverted); + + if (unconverted.length === 0) { + attachmentMigrationStatus.set('All attachments are already migrated'); + attachmentMigrationProgress.set(100); + isMigratingAttachments.set(false); + return; + } + + // Start server-side migration + Meteor.call('attachmentMigration.migrateBoardAttachments', boardId, (error, result) => { + if (error) { + console.error('Failed to start attachment migration:', error); + attachmentMigrationStatus.set(`Migration failed: ${error.message}`); + isMigratingAttachments.set(false); + } else { + console.log('Attachment migration started for board:', boardId); + this.pollAttachmentMigrationProgress(boardId); + } + }); + + } catch (error) { + console.error('Error starting attachment migration:', error); + attachmentMigrationStatus.set(`Migration failed: ${error.message}`); + isMigratingAttachments.set(false); + } + } + + /** + * Poll for attachment migration progress + * @param {string} boardId - The board ID + */ + pollAttachmentMigrationProgress(boardId) { + const pollInterval = setInterval(() => { + Meteor.call('attachmentMigration.getProgress', boardId, (error, result) => { + if (error) { + console.error('Error getting migration progress:', error); + clearInterval(pollInterval); + isMigratingAttachments.set(false); + return; + } + + if (result) { + attachmentMigrationProgress.set(result.progress); + attachmentMigrationStatus.set(result.status); + unconvertedAttachments.set(result.unconvertedAttachments || []); + + // Stop polling if migration is complete + if (result.progress >= 100 || result.status === 'completed') { + clearInterval(pollInterval); + isMigratingAttachments.set(false); + this.migrationCache.clear(); // Clear cache to refresh data + } + } + }); + }, 1000); + } + + /** + * Check if an attachment is currently being migrated + * @param {string} attachmentId - The attachment ID + * @returns {boolean} - True if attachment is being migrated + */ + isAttachmentBeingMigrated(attachmentId) { + const unconverted = unconvertedAttachments.get(); + return unconverted.some(attachment => attachment._id === attachmentId); + } + + /** + * Get migration status for an attachment + * @param {string} attachmentId - The attachment ID + * @returns {string} - Migration status ('migrated', 'migrating', 'unmigrated') + */ + getAttachmentMigrationStatus(attachmentId) { + if (this.migrationCache.has(attachmentId)) { + return 'migrated'; + } + + if (this.isAttachmentBeingMigrated(attachmentId)) { + return 'migrating'; + } + + return this.needsMigration(attachmentId) ? 'unmigrated' : 'migrated'; + } +} + +export const attachmentMigrationManager = new AttachmentMigrationManager(); + + + + diff --git a/client/lib/boardConverter.js b/client/lib/boardConverter.js index 99337419a..0f19d31fb 100644 --- a/client/lib/boardConverter.js +++ b/client/lib/boardConverter.js @@ -5,7 +5,7 @@ */ import { ReactiveVar } from 'meteor/reactive-var'; -import { ReactiveCache } from '/imports/lib/reactiveCache'; +import { ReactiveCache } from '/imports/reactiveCache'; // Reactive variables for conversion progress export const conversionProgress = new ReactiveVar(0); diff --git a/client/lib/migrationManager.js b/client/lib/migrationManager.js index 11a4b9f5b..c178b69e7 100644 --- a/client/lib/migrationManager.js +++ b/client/lib/migrationManager.js @@ -5,7 +5,7 @@ */ import { ReactiveVar } from 'meteor/reactive-var'; -import { ReactiveCache } from '/imports/lib/reactiveCache'; +import { ReactiveCache } from '/imports/reactiveCache'; // Reactive variables for migration progress export const migrationProgress = new ReactiveVar(0); @@ -600,10 +600,16 @@ class MigrationManager { } /** - * Check if any migrations need to be run + * Check if any migrations need to be run for a specific board */ - needsMigration() { - // Check if any migration step is not completed + needsMigration(boardId = null) { + if (boardId) { + // Check if specific board needs migration based on version + const board = ReactiveCache.getBoard(boardId); + return !board || !board.migrationVersion || board.migrationVersion < 1; + } + + // Check if any migration step is not completed (global migrations) return this.steps.some(step => !step.completed); } @@ -623,6 +629,23 @@ class MigrationManager { }, 0); } + /** + * Mark a board as migrated + */ + markBoardAsMigrated(boardId) { + try { + Meteor.call('boardMigration.markAsMigrated', boardId, 'full_board_migration', (error, result) => { + if (error) { + console.error('Failed to mark board as migrated:', error); + } else { + console.log('Board marked as migrated:', boardId); + } + }); + } catch (error) { + console.error('Error marking board as migrated:', error); + } + } + /** * Start migration process using cron system */ @@ -711,7 +734,7 @@ class MigrationManager { // In a real implementation, this would call the actual migration // For now, we'll simulate the migration - console.log(`Running migration: ${step.name}`); + // Running migration step } /** diff --git a/models/attachments.js b/models/attachments.js index 96de9f79e..31b7c0c02 100644 --- a/models/attachments.js +++ b/models/attachments.js @@ -315,10 +315,10 @@ if (Meteor.isServer) { fs.mkdirSync(storagePath, { recursive: true }); } }); - - // Add backward compatibility methods - Attachments.getAttachmentWithBackwardCompatibility = getAttachmentWithBackwardCompatibility; - Attachments.getAttachmentsWithBackwardCompatibility = getAttachmentsWithBackwardCompatibility; } +// Add backward compatibility methods - available on both client and server +Attachments.getAttachmentWithBackwardCompatibility = getAttachmentWithBackwardCompatibility; +Attachments.getAttachmentsWithBackwardCompatibility = getAttachmentsWithBackwardCompatibility; + export default Attachments; diff --git a/models/boards.js b/models/boards.js index 2dc3c62de..2ce9d018a 100644 --- a/models/boards.js +++ b/models/boards.js @@ -331,6 +331,19 @@ Boards.attachSchema( optional: true, defaultValue: null, }, + migrationVersion: { + /** + * The migration version of the board structure. + * New boards are created with the latest version and don't need migration. + */ + type: Number, + // eslint-disable-next-line consistent-return + autoValue() { + if (this.isInsert && !this.isSet) { + return 1; // Latest migration version for new boards + } + }, + }, subtasksDefaultListId: { /** @@ -2196,6 +2209,7 @@ if (Meteor.isServer) { ], permission: req.body.permission || 'private', color: req.body.color || 'belize', + migrationVersion: 1, // Latest version - no migration needed }); const swimlaneId = Swimlanes.insert({ title: TAPi18n.__('default'), diff --git a/models/lib/meteorMongoIntegration.js b/models/lib/meteorMongoIntegration.js index 16e62d53a..43a6af389 100644 --- a/models/lib/meteorMongoIntegration.js +++ b/models/lib/meteorMongoIntegration.js @@ -50,7 +50,7 @@ class MeteorMongoIntegration { this.overrideMongoCollection(); this.isInitialized = true; - console.log('Meteor MongoDB Integration initialized successfully'); + // Meteor MongoDB Integration initialized successfully (status available in Admin Panel) } /** @@ -296,11 +296,8 @@ export { meteorMongoIntegration, MeteorMongoIntegration }; // Auto-initialize if MONGO_URL is available if (Meteor.isServer && process.env.MONGO_URL) { - console.log('Auto-initializing Meteor MongoDB Integration with MONGO_URL'); + // Auto-initializing Meteor MongoDB Integration with MONGO_URL (status available in Admin Panel) meteorMongoIntegration.initialize(process.env.MONGO_URL); } -// Log initialization -if (Meteor.isServer) { - console.log('Meteor MongoDB Integration module loaded'); -} +// Meteor MongoDB Integration module loaded (status available in Admin Panel) diff --git a/models/lib/mongodbConnectionManager.js b/models/lib/mongodbConnectionManager.js index a10701e8f..2c37ac513 100644 --- a/models/lib/mongodbConnectionManager.js +++ b/models/lib/mongodbConnectionManager.js @@ -288,7 +288,4 @@ const mongodbConnectionManager = new MongoDBConnectionManager(); // Export for use in other modules export { mongodbConnectionManager, MongoDBConnectionManager }; -// Log initialization -if (Meteor.isServer) { - console.log('MongoDB Connection Manager initialized'); -} +// MongoDB Connection Manager initialized (status available in Admin Panel) diff --git a/models/lib/mongodbDriverManager.js b/models/lib/mongodbDriverManager.js index 24f38d9b0..19d71329a 100644 --- a/models/lib/mongodbDriverManager.js +++ b/models/lib/mongodbDriverManager.js @@ -270,8 +270,4 @@ const mongodbDriverManager = new MongoDBDriverManager(); // Export for use in other modules export { mongodbDriverManager, MongoDBDriverManager }; -// Log initialization -if (Meteor.isServer) { - console.log('MongoDB Driver Manager initialized'); - console.log(`Supported MongoDB versions: ${mongodbDriverManager.getSupportedVersions().join(', ')}`); -} +// MongoDB Driver Manager initialized (status available in Admin Panel) diff --git a/models/users.js b/models/users.js index 0e2ff1db7..69f14a6b3 100644 --- a/models/users.js +++ b/models/users.js @@ -1850,7 +1850,7 @@ if (Meteor.isServer) { }, }); Accounts.onCreateUser((options, user) => { - const userCount = ReactiveCache.getUsers({}, {}, true).countDocuments(); + const userCount = ReactiveCache.getUsers({}, {}, true).count(); user.isAdmin = userCount === 0; if (user.services.oidc) { diff --git a/package-lock.json b/package-lock.json index 7de61c7a7..2f9ca93bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,564 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "optional": true, + "requires": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "optional": true, + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + } + } + }, + "@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "optional": true, + "requires": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "optional": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "optional": true, + "requires": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "optional": true, + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + } + } + }, + "@aws-sdk/client-cognito-identity": { + "version": "3.908.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.908.0.tgz", + "integrity": "sha512-XEva6l07dtF+6QNzTZHB+PEOX03sdcOudh+XVa4UjlUPlqrl/LAk/MzRxbw6pHScG5DPMx6Iyeicm7pJBOTdYg==", + "optional": true, + "requires": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.908.0", + "@aws-sdk/credential-provider-node": "3.908.0", + "@aws-sdk/middleware-host-header": "3.901.0", + "@aws-sdk/middleware-logger": "3.901.0", + "@aws-sdk/middleware-recursion-detection": "3.901.0", + "@aws-sdk/middleware-user-agent": "3.908.0", + "@aws-sdk/region-config-resolver": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@aws-sdk/util-endpoints": "3.901.0", + "@aws-sdk/util-user-agent-browser": "3.907.0", + "@aws-sdk/util-user-agent-node": "3.908.0", + "@smithy/config-resolver": "^4.3.0", + "@smithy/core": "^3.15.0", + "@smithy/fetch-http-handler": "^5.3.1", + "@smithy/hash-node": "^4.2.0", + "@smithy/invalid-dependency": "^4.2.0", + "@smithy/middleware-content-length": "^4.2.0", + "@smithy/middleware-endpoint": "^4.3.1", + "@smithy/middleware-retry": "^4.4.1", + "@smithy/middleware-serde": "^4.2.0", + "@smithy/middleware-stack": "^4.2.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/node-http-handler": "^4.3.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/smithy-client": "^4.7.1", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.0", + "@smithy/util-defaults-mode-node": "^4.2.1", + "@smithy/util-endpoints": "^3.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-retry": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/client-sso": { + "version": "3.908.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.908.0.tgz", + "integrity": "sha512-PseFMWvtac+Q+zaY9DMISE+2+glNh0ROJ1yR4gMzeafNHSwkdYu4qcgxLWIOnIodGydBv/tQ6nzHPzExXnUUgw==", + "optional": true, + "requires": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.908.0", + "@aws-sdk/middleware-host-header": "3.901.0", + "@aws-sdk/middleware-logger": "3.901.0", + "@aws-sdk/middleware-recursion-detection": "3.901.0", + "@aws-sdk/middleware-user-agent": "3.908.0", + "@aws-sdk/region-config-resolver": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@aws-sdk/util-endpoints": "3.901.0", + "@aws-sdk/util-user-agent-browser": "3.907.0", + "@aws-sdk/util-user-agent-node": "3.908.0", + "@smithy/config-resolver": "^4.3.0", + "@smithy/core": "^3.15.0", + "@smithy/fetch-http-handler": "^5.3.1", + "@smithy/hash-node": "^4.2.0", + "@smithy/invalid-dependency": "^4.2.0", + "@smithy/middleware-content-length": "^4.2.0", + "@smithy/middleware-endpoint": "^4.3.1", + "@smithy/middleware-retry": "^4.4.1", + "@smithy/middleware-serde": "^4.2.0", + "@smithy/middleware-stack": "^4.2.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/node-http-handler": "^4.3.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/smithy-client": "^4.7.1", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.0", + "@smithy/util-defaults-mode-node": "^4.2.1", + "@smithy/util-endpoints": "^3.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-retry": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/core": { + "version": "3.908.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.908.0.tgz", + "integrity": "sha512-okl6FC2cQT1Oidvmnmvyp/IEvqENBagKO0ww4YV5UtBkf0VlhAymCWkZqhovtklsqgq0otag2VRPAgnrMt6nVQ==", + "optional": true, + "requires": { + "@aws-sdk/types": "3.901.0", + "@aws-sdk/xml-builder": "3.901.0", + "@smithy/core": "^3.15.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/signature-v4": "^5.3.0", + "@smithy/smithy-client": "^4.7.1", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/credential-provider-cognito-identity": { + "version": "3.908.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.908.0.tgz", + "integrity": "sha512-Lmb5GatPNDY5cODfwH3XGWpKZqg/lh7ghCn4Y9BTMi5W9Z/E2Jq5YF7yaLobOjOpx+lVoxS+EsVrtSm6p3ffuw==", + "optional": true, + "requires": { + "@aws-sdk/client-cognito-identity": "3.908.0", + "@aws-sdk/types": "3.901.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/credential-provider-env": { + "version": "3.908.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.908.0.tgz", + "integrity": "sha512-FK2YuxoI5CxUflPOIMbVAwDbi6Xvu+2sXopXLmrHc2PfI39M3vmjEoQwYCP8WuQSRb+TbAP3xAkxHjFSBFR35w==", + "optional": true, + "requires": { + "@aws-sdk/core": "3.908.0", + "@aws-sdk/types": "3.901.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/credential-provider-http": { + "version": "3.908.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.908.0.tgz", + "integrity": "sha512-eLbz0geVW9EykujQNnYfR35Of8MreI6pau5K6XDFDUSWO9GF8wqH7CQwbXpXHBlCTHtq4QSLxzorD8U5CROhUw==", + "optional": true, + "requires": { + "@aws-sdk/core": "3.908.0", + "@aws-sdk/types": "3.901.0", + "@smithy/fetch-http-handler": "^5.3.1", + "@smithy/node-http-handler": "^4.3.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/smithy-client": "^4.7.1", + "@smithy/types": "^4.6.0", + "@smithy/util-stream": "^4.5.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/credential-provider-ini": { + "version": "3.908.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.908.0.tgz", + "integrity": "sha512-7Cgnv5wabgFtsgr+Uc/76EfPNGyxmbG8aICn3g3D3iJlcO4uuOZI8a77i0afoDdchZrTC6TG6UusS/NAW6zEoQ==", + "optional": true, + "requires": { + "@aws-sdk/core": "3.908.0", + "@aws-sdk/credential-provider-env": "3.908.0", + "@aws-sdk/credential-provider-http": "3.908.0", + "@aws-sdk/credential-provider-process": "3.908.0", + "@aws-sdk/credential-provider-sso": "3.908.0", + "@aws-sdk/credential-provider-web-identity": "3.908.0", + "@aws-sdk/nested-clients": "3.908.0", + "@aws-sdk/types": "3.901.0", + "@smithy/credential-provider-imds": "^4.2.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/credential-provider-node": { + "version": "3.908.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.908.0.tgz", + "integrity": "sha512-8OKbykpGw5bdfF/pLTf8YfUi1Kl8o1CTjBqWQTsLOkE3Ho3hsp1eQx8Cz4ttrpv0919kb+lox62DgmAOEmTr1w==", + "optional": true, + "requires": { + "@aws-sdk/credential-provider-env": "3.908.0", + "@aws-sdk/credential-provider-http": "3.908.0", + "@aws-sdk/credential-provider-ini": "3.908.0", + "@aws-sdk/credential-provider-process": "3.908.0", + "@aws-sdk/credential-provider-sso": "3.908.0", + "@aws-sdk/credential-provider-web-identity": "3.908.0", + "@aws-sdk/types": "3.901.0", + "@smithy/credential-provider-imds": "^4.2.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/credential-provider-process": { + "version": "3.908.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.908.0.tgz", + "integrity": "sha512-sWnbkGjDPBi6sODUzrAh5BCDpnPw0wpK8UC/hWI13Q8KGfyatAmCBfr+9OeO3+xBHa8N5AskMncr7C4qS846yQ==", + "optional": true, + "requires": { + "@aws-sdk/core": "3.908.0", + "@aws-sdk/types": "3.901.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/credential-provider-sso": { + "version": "3.908.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.908.0.tgz", + "integrity": "sha512-WV/aOzuS6ZZhrkPty6TJ3ZG24iS8NXP0m3GuTVuZ5tKi9Guss31/PJ1CrKPRCYGm15CsIjf+mrUxVnNYv9ap5g==", + "optional": true, + "requires": { + "@aws-sdk/client-sso": "3.908.0", + "@aws-sdk/core": "3.908.0", + "@aws-sdk/token-providers": "3.908.0", + "@aws-sdk/types": "3.901.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/credential-provider-web-identity": { + "version": "3.908.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.908.0.tgz", + "integrity": "sha512-9xWrFn6nWlF5KlV4XYW+7E6F33S3wUUEGRZ/+pgDhkIZd527ycT2nPG2dZ3fWUZMlRmzijP20QIJDqEbbGWe1Q==", + "optional": true, + "requires": { + "@aws-sdk/core": "3.908.0", + "@aws-sdk/nested-clients": "3.908.0", + "@aws-sdk/types": "3.901.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/credential-providers": { + "version": "3.908.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.908.0.tgz", + "integrity": "sha512-WuAttxDemeKyMAgNEwBdgz11/W8IOosDxAU9GOk8ZbNJI/Cp0SiCp5p9bu0hlroq2V4HxECIRIMNeJZLh7fIgg==", + "optional": true, + "requires": { + "@aws-sdk/client-cognito-identity": "3.908.0", + "@aws-sdk/core": "3.908.0", + "@aws-sdk/credential-provider-cognito-identity": "3.908.0", + "@aws-sdk/credential-provider-env": "3.908.0", + "@aws-sdk/credential-provider-http": "3.908.0", + "@aws-sdk/credential-provider-ini": "3.908.0", + "@aws-sdk/credential-provider-node": "3.908.0", + "@aws-sdk/credential-provider-process": "3.908.0", + "@aws-sdk/credential-provider-sso": "3.908.0", + "@aws-sdk/credential-provider-web-identity": "3.908.0", + "@aws-sdk/nested-clients": "3.908.0", + "@aws-sdk/types": "3.901.0", + "@smithy/config-resolver": "^4.3.0", + "@smithy/core": "^3.15.0", + "@smithy/credential-provider-imds": "^4.2.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/middleware-host-header": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.901.0.tgz", + "integrity": "sha512-yWX7GvRmqBtbNnUW7qbre3GvZmyYwU0WHefpZzDTYDoNgatuYq6LgUIQ+z5C04/kCRoFkAFrHag8a3BXqFzq5A==", + "optional": true, + "requires": { + "@aws-sdk/types": "3.901.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/middleware-logger": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.901.0.tgz", + "integrity": "sha512-UoHebjE7el/tfRo8/CQTj91oNUm+5Heus5/a4ECdmWaSCHCS/hXTsU3PTTHAY67oAQR8wBLFPfp3mMvXjB+L2A==", + "optional": true, + "requires": { + "@aws-sdk/types": "3.901.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/middleware-recursion-detection": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.901.0.tgz", + "integrity": "sha512-Wd2t8qa/4OL0v/oDpCHHYkgsXJr8/ttCxrvCKAt0H1zZe2LlRhY9gpDVKqdertfHrHDj786fOvEQA28G1L75Dg==", + "optional": true, + "requires": { + "@aws-sdk/types": "3.901.0", + "@aws/lambda-invoke-store": "^0.0.1", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/middleware-user-agent": { + "version": "3.908.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.908.0.tgz", + "integrity": "sha512-R0ePEOku72EvyJWy/D0Z5f/Ifpfxa0U9gySO3stpNhOox87XhsILpcIsCHPy0OHz1a7cMoZsF6rMKSzDeCnogQ==", + "optional": true, + "requires": { + "@aws-sdk/core": "3.908.0", + "@aws-sdk/types": "3.901.0", + "@aws-sdk/util-endpoints": "3.901.0", + "@smithy/core": "^3.15.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/nested-clients": { + "version": "3.908.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.908.0.tgz", + "integrity": "sha512-ZxDYrfxOKXNFHLyvJtT96TJ0p4brZOhwRE4csRXrezEVUN+pNgxuem95YvMALPVhlVqON2CTzr8BX+CcBKvX9Q==", + "optional": true, + "requires": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.908.0", + "@aws-sdk/middleware-host-header": "3.901.0", + "@aws-sdk/middleware-logger": "3.901.0", + "@aws-sdk/middleware-recursion-detection": "3.901.0", + "@aws-sdk/middleware-user-agent": "3.908.0", + "@aws-sdk/region-config-resolver": "3.901.0", + "@aws-sdk/types": "3.901.0", + "@aws-sdk/util-endpoints": "3.901.0", + "@aws-sdk/util-user-agent-browser": "3.907.0", + "@aws-sdk/util-user-agent-node": "3.908.0", + "@smithy/config-resolver": "^4.3.0", + "@smithy/core": "^3.15.0", + "@smithy/fetch-http-handler": "^5.3.1", + "@smithy/hash-node": "^4.2.0", + "@smithy/invalid-dependency": "^4.2.0", + "@smithy/middleware-content-length": "^4.2.0", + "@smithy/middleware-endpoint": "^4.3.1", + "@smithy/middleware-retry": "^4.4.1", + "@smithy/middleware-serde": "^4.2.0", + "@smithy/middleware-stack": "^4.2.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/node-http-handler": "^4.3.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/smithy-client": "^4.7.1", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.0", + "@smithy/util-defaults-mode-node": "^4.2.1", + "@smithy/util-endpoints": "^3.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-retry": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/region-config-resolver": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.901.0.tgz", + "integrity": "sha512-7F0N888qVLHo4CSQOsnkZ4QAp8uHLKJ4v3u09Ly5k4AEStrSlFpckTPyUx6elwGL+fxGjNE2aakK8vEgzzCV0A==", + "optional": true, + "requires": { + "@aws-sdk/types": "3.901.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/token-providers": { + "version": "3.908.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.908.0.tgz", + "integrity": "sha512-4SosHWRQ8hj1X2yDenCYHParcCjHcd7S+Mdb/lelwF0JBFCNC+dNCI9ws3cP/dFdZO/AIhJQGUBzEQtieloixw==", + "optional": true, + "requires": { + "@aws-sdk/core": "3.908.0", + "@aws-sdk/nested-clients": "3.908.0", + "@aws-sdk/types": "3.901.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/types": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.901.0.tgz", + "integrity": "sha512-FfEM25hLEs4LoXsLXQ/q6X6L4JmKkKkbVFpKD4mwfVHtRVQG6QxJiCPcrkcPISquiy6esbwK2eh64TWbiD60cg==", + "optional": true, + "requires": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/util-endpoints": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.901.0.tgz", + "integrity": "sha512-5nZP3hGA8FHEtKvEQf4Aww5QZOkjLW1Z+NixSd+0XKfHvA39Ah5sZboScjLx0C9kti/K3OGW1RCx5K9Zc3bZqg==", + "optional": true, + "requires": { + "@aws-sdk/types": "3.901.0", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "@smithy/util-endpoints": "^3.2.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/util-locate-window": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz", + "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==", + "optional": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@aws-sdk/util-user-agent-browser": { + "version": "3.907.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.907.0.tgz", + "integrity": "sha512-Hus/2YCQmtCEfr4Ls88d07Q99Ex59uvtktiPTV963Q7w7LHuIT/JBjrbwNxtSm2KlJR9PHNdqxwN+fSuNsMGMQ==", + "optional": true, + "requires": { + "@aws-sdk/types": "3.901.0", + "@smithy/types": "^4.6.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/util-user-agent-node": { + "version": "3.908.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.908.0.tgz", + "integrity": "sha512-l6AEaKUAYarcEy8T8NZ+dNZ00VGLs3fW2Cqu1AuPENaSad0/ahEU+VU7MpXS8FhMRGPgplxKVgCTLyTY0Lbssw==", + "optional": true, + "requires": { + "@aws-sdk/middleware-user-agent": "3.908.0", + "@aws-sdk/types": "3.901.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/xml-builder": { + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.901.0.tgz", + "integrity": "sha512-pxFCkuAP7Q94wMTNPAwi6hEtNrp/BdFf+HOrIEeFQsk4EoOmpKY3I6S+u6A9Wg295J80Kh74LqDWM22ux3z6Aw==", + "optional": true, + "requires": { + "@smithy/types": "^4.6.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "dependencies": { + "fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "optional": true, + "requires": { + "strnum": "^2.1.0" + } + }, + "strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "optional": true + } + } + }, + "@aws/lambda-invoke-store": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.0.1.tgz", + "integrity": "sha512-ORHRQ2tmvnBXc8t/X9Z8IcSbBA4xTLKuN873FopzklHMeqBst7YG0d+AX97inkvDX+NChYtSr+qGfcqGFaI8Zw==", + "optional": true + }, "@babel/runtime": { "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", @@ -57,6 +615,14 @@ "tar": "^6.1.11" } }, + "@mongodb-js/saslprep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.1.tgz", + "integrity": "sha512-6nZrq5kfAz0POWyhljnbWQQJQ5uT8oE2ddX303q1uY0tWsivWKgBDXBBvuFPwOqRRalXJuVO9EjOdVtuhLX0zg==", + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, "@rwap/jquery-ui-touch-punch": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@rwap/jquery-ui-touch-punch/-/jquery-ui-touch-punch-1.0.11.tgz", @@ -100,6 +666,560 @@ "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", "dev": true }, + "@smithy/abort-controller": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.0.tgz", + "integrity": "sha512-PLUYa+SUKOEZtXFURBu/CNxlsxfaFGxSBPcStL13KpVeVWIfdezWyDqkz7iDLmwnxojXD0s5KzuB5HGHvt4Aeg==", + "optional": true, + "requires": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@smithy/config-resolver": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.3.0.tgz", + "integrity": "sha512-9oH+n8AVNiLPK/iK/agOsoWfrKZ3FGP3502tkksd6SRsKMYiu7AFX0YXo6YBADdsAj7C+G/aLKdsafIJHxuCkQ==", + "optional": true, + "requires": { + "@smithy/node-config-provider": "^4.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/core": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.15.0.tgz", + "integrity": "sha512-VJWncXgt+ExNn0U2+Y7UywuATtRYaodGQKFo9mDyh70q+fJGedfrqi2XuKU1BhiLeXgg6RZrW7VEKfeqFhHAJA==", + "optional": true, + "requires": { + "@smithy/middleware-serde": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-stream": "^4.5.0", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/credential-provider-imds": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.0.tgz", + "integrity": "sha512-SOhFVvFH4D5HJZytb0bLKxCrSnwcqPiNlrw+S4ZXjMnsC+o9JcUQzbZOEQcA8yv9wJFNhfsUiIUKiEnYL68Big==", + "optional": true, + "requires": { + "@smithy/node-config-provider": "^4.3.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/fetch-http-handler": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.1.tgz", + "integrity": "sha512-3AvYYbB+Dv5EPLqnJIAgYw/9+WzeBiUYS8B+rU0pHq5NMQMvrZmevUROS4V2GAt0jEOn9viBzPLrZE+riTNd5Q==", + "optional": true, + "requires": { + "@smithy/protocol-http": "^5.3.0", + "@smithy/querystring-builder": "^4.2.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/hash-node": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.0.tgz", + "integrity": "sha512-ugv93gOhZGysTctZh9qdgng8B+xO0cj+zN0qAZ+Sgh7qTQGPOJbMdIuyP89KNfUyfAqFSNh5tMvC+h2uCpmTtA==", + "optional": true, + "requires": { + "@smithy/types": "^4.6.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "optional": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "optional": true, + "requires": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + } + } + } + }, + "@smithy/invalid-dependency": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.0.tgz", + "integrity": "sha512-ZmK5X5fUPAbtvRcUPtk28aqIClVhbfcmfoS4M7UQBTnDdrNxhsrxYVv0ZEl5NaPSyExsPWqL4GsPlRvtlwg+2A==", + "optional": true, + "requires": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "optional": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-content-length": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.0.tgz", + "integrity": "sha512-6ZAnwrXFecrA4kIDOcz6aLBhU5ih2is2NdcZtobBDSdSHtE9a+MThB5uqyK4XXesdOCvOcbCm2IGB95birTSOQ==", + "optional": true, + "requires": { + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-endpoint": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.1.tgz", + "integrity": "sha512-JtM4SjEgImLEJVXdsbvWHYiJ9dtuKE8bqLlvkvGi96LbejDL6qnVpVxEFUximFodoQbg0Gnkyff9EKUhFhVJFw==", + "optional": true, + "requires": { + "@smithy/core": "^3.15.0", + "@smithy/middleware-serde": "^4.2.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "@smithy/url-parser": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-retry": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.1.tgz", + "integrity": "sha512-wXxS4ex8cJJteL0PPQmWYkNi9QKDWZIpsndr0wZI2EL+pSSvA/qqxXU60gBOJoIc2YgtZSWY/PE86qhKCCKP1w==", + "optional": true, + "requires": { + "@smithy/node-config-provider": "^4.3.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/service-error-classification": "^4.2.0", + "@smithy/smithy-client": "^4.7.1", + "@smithy/types": "^4.6.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-retry": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-serde": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.0.tgz", + "integrity": "sha512-rpTQ7D65/EAbC6VydXlxjvbifTf4IH+sADKg6JmAvhkflJO2NvDeyU9qsWUNBelJiQFcXKejUHWRSdmpJmEmiw==", + "optional": true, + "requires": { + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-stack": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.0.tgz", + "integrity": "sha512-G5CJ//eqRd9OARrQu9MK1H8fNm2sMtqFh6j8/rPozhEL+Dokpvi1Og+aCixTuwDAGZUkJPk6hJT5jchbk/WCyg==", + "optional": true, + "requires": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@smithy/node-config-provider": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.0.tgz", + "integrity": "sha512-5QgHNuWdT9j9GwMPPJCKxy2KDxZ3E5l4M3/5TatSZrqYVoEiqQrDfAq8I6KWZw7RZOHtVtCzEPdYz7rHZixwcA==", + "optional": true, + "requires": { + "@smithy/property-provider": "^4.2.0", + "@smithy/shared-ini-file-loader": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@smithy/node-http-handler": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.3.0.tgz", + "integrity": "sha512-RHZ/uWCmSNZ8cneoWEVsVwMZBKy/8123hEpm57vgGXA3Irf/Ja4v9TVshHK2ML5/IqzAZn0WhINHOP9xl+Qy6Q==", + "optional": true, + "requires": { + "@smithy/abort-controller": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/querystring-builder": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@smithy/property-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.0.tgz", + "integrity": "sha512-rV6wFre0BU6n/tx2Ztn5LdvEdNZ2FasQbPQmDOPfV9QQyDmsCkOAB0osQjotRCQg+nSKFmINhyda0D3AnjSBJw==", + "optional": true, + "requires": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.0.tgz", + "integrity": "sha512-6POSYlmDnsLKb7r1D3SVm7RaYW6H1vcNcTWGWrF7s9+2noNYvUsm7E4tz5ZQ9HXPmKn6Hb67pBDRIjrT4w/d7Q==", + "optional": true, + "requires": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-builder": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.0.tgz", + "integrity": "sha512-Q4oFD0ZmI8yJkiPPeGUITZj++4HHYCW3pYBYfIobUCkYpI6mbkzmG1MAQQ3lJYYWj3iNqfzOenUZu+jqdPQ16A==", + "optional": true, + "requires": { + "@smithy/types": "^4.6.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.0.tgz", + "integrity": "sha512-BjATSNNyvVbQxOOlKse0b0pSezTWGMvA87SvoFoFlkRsKXVsN3bEtjCxvsNXJXfnAzlWFPaT9DmhWy1vn0sNEA==", + "optional": true, + "requires": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@smithy/service-error-classification": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.0.tgz", + "integrity": "sha512-Ylv1ttUeKatpR0wEOMnHf1hXMktPUMObDClSWl2TpCVT4DwtJhCeighLzSLbgH3jr5pBNM0LDXT5yYxUvZ9WpA==", + "optional": true, + "requires": { + "@smithy/types": "^4.6.0" + } + }, + "@smithy/shared-ini-file-loader": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.0.tgz", + "integrity": "sha512-VCUPPtNs+rKWlqqntX0CbVvWyjhmX30JCtzO+s5dlzzxrvSfRh5SY0yxnkirvc1c80vdKQttahL71a9EsdolSQ==", + "optional": true, + "requires": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@smithy/signature-v4": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.0.tgz", + "integrity": "sha512-MKNyhXEs99xAZaFhm88h+3/V+tCRDQ+PrDzRqL0xdDpq4gjxcMmf5rBA3YXgqZqMZ/XwemZEurCBQMfxZOWq/g==", + "optional": true, + "requires": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.0", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "optional": true, + "requires": { + "tslib": "^2.6.2" + } + } + } + }, + "@smithy/smithy-client": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.7.1.tgz", + "integrity": "sha512-WXVbiyNf/WOS/RHUoFMkJ6leEVpln5ojCjNBnzoZeMsnCg3A0BRhLK3WYc4V7PmYcYPZh9IYzzAg9XcNSzYxYQ==", + "optional": true, + "requires": { + "@smithy/core": "^3.15.0", + "@smithy/middleware-endpoint": "^4.3.1", + "@smithy/middleware-stack": "^4.2.0", + "@smithy/protocol-http": "^5.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-stream": "^4.5.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz", + "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==", + "optional": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/url-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.0.tgz", + "integrity": "sha512-AlBmD6Idav2ugmoAL6UtR6ItS7jU5h5RNqLMZC7QrLCoITA9NzIN3nx9GWi8g4z1pfWh2r9r96SX/jHiNwPJ9A==", + "optional": true, + "requires": { + "@smithy/querystring-parser": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "optional": true, + "requires": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "optional": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "optional": true, + "requires": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + } + } + } + }, + "@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "optional": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "optional": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "optional": true, + "requires": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "optional": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-defaults-mode-browser": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.0.tgz", + "integrity": "sha512-H4MAj8j8Yp19Mr7vVtGgi7noJjvjJbsKQJkvNnLlrIFduRFT5jq5Eri1k838YW7rN2g5FTnXpz5ktKVr1KVgPQ==", + "optional": true, + "requires": { + "@smithy/property-provider": "^4.2.0", + "@smithy/smithy-client": "^4.7.1", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-defaults-mode-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.1.tgz", + "integrity": "sha512-PuDcgx7/qKEMzV1QFHJ7E4/MMeEjaA7+zS5UNcHCLPvvn59AeZQ0DSDGMpqC2xecfa/1cNGm4l8Ec/VxCuY7Ug==", + "optional": true, + "requires": { + "@smithy/config-resolver": "^4.3.0", + "@smithy/credential-provider-imds": "^4.2.0", + "@smithy/node-config-provider": "^4.3.0", + "@smithy/property-provider": "^4.2.0", + "@smithy/smithy-client": "^4.7.1", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-endpoints": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.0.tgz", + "integrity": "sha512-TXeCn22D56vvWr/5xPqALc9oO+LN+QpFjrSM7peG/ckqEPoI3zaKZFp+bFwfmiHhn5MGWPaLCqDOJPPIixk9Wg==", + "optional": true, + "requires": { + "@smithy/node-config-provider": "^4.3.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "optional": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.0.tgz", + "integrity": "sha512-u9OOfDa43MjagtJZ8AapJcmimP+K2Z7szXn8xbty4aza+7P1wjFmy2ewjSbhEiYQoW1unTlOAIV165weYAaowA==", + "optional": true, + "requires": { + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-retry": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.0.tgz", + "integrity": "sha512-BWSiuGbwRnEE2SFfaAZEX0TqaxtvtSYPM/J73PFVm+A29Fg1HTPiYFb8TmX1DXp4hgcdyJcNQmprfd5foeORsg==", + "optional": true, + "requires": { + "@smithy/service-error-classification": "^4.2.0", + "@smithy/types": "^4.6.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-stream": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.0.tgz", + "integrity": "sha512-0TD5M5HCGu5diEvZ/O/WquSjhJPasqv7trjoqHyWjNh/FBeBl7a0ztl9uFMOsauYtRfd8jvpzIAQhDHbx+nvZw==", + "optional": true, + "requires": { + "@smithy/fetch-http-handler": "^5.3.1", + "@smithy/node-http-handler": "^4.3.0", + "@smithy/types": "^4.6.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "optional": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "optional": true, + "requires": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + } + } + } + }, + "@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "optional": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "optional": true, + "requires": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "optional": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "optional": true, + "requires": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + } + } + } + }, + "@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "optional": true, + "requires": { + "tslib": "^2.6.2" + } + }, "@tokenizer/token": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", @@ -116,6 +1236,20 @@ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "optional": true }, + "@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + }, + "@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "requires": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, "@wekanteam/dragscroll": { "version": "git+https://github.com/wekan/dragscroll.git#6ea215c8cdbde9362ecba8ffb72ce9f9fde842d2", "from": "git+https://github.com/wekan/dragscroll.git" @@ -379,6 +1513,12 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, + "bowser": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.1.tgz", + "integrity": "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==", + "optional": true + }, "brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -697,6 +1837,11 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" }, + "denque": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", + "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==" + }, "detect-libc": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", @@ -1198,6 +2343,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==" + }, "ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", @@ -1581,6 +2731,11 @@ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" }, + "memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" + }, "mensch": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/mensch/-/mensch-0.3.4.tgz", @@ -2609,6 +3764,302 @@ "resolved": "https://registry.npmjs.org/mongo-object/-/mongo-object-3.0.1.tgz", "integrity": "sha512-EbiwWHvKOF9xhIzuwaqknwPISdkHMipjMs6DiJFicupgBBLEhUs0OOro9MuPkFogB17DZlsV4KJhhxfqZ7ZRMQ==" }, + "mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "requires": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + }, + "dependencies": { + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "requires": { + "punycode": "^2.1.1" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + } + } + }, + "mongodb3legacy": { + "version": "npm:mongodb@3.7.4", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.4.tgz", + "integrity": "sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==", + "requires": { + "bl": "^2.2.1", + "bson": "^1.1.4", + "denque": "^1.4.1", + "optional-require": "^1.1.8", + "safe-buffer": "^5.1.2", + "saslprep": "^1.0.0" + }, + "dependencies": { + "bl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "bson": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", + "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==" + }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + } + } + }, + "mongodb4legacy": { + "version": "npm:mongodb@4.17.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.17.2.tgz", + "integrity": "sha512-mLV7SEiov2LHleRJPMPrK2PMyhXFZt2UQLC4VD4pnth3jMjYKHhtqfwwkkvS/NXuo/Fp3vbhaNcXrIDaLRb9Tg==", + "requires": { + "@aws-sdk/credential-providers": "^3.186.0", + "@mongodb-js/saslprep": "^1.1.0", + "bson": "^4.7.2", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + } + }, + "mongodb5legacy": { + "version": "npm:mongodb@5.9.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz", + "integrity": "sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==", + "requires": { + "@mongodb-js/saslprep": "^1.1.0", + "bson": "^5.5.0", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + }, + "dependencies": { + "bson": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz", + "integrity": "sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==" + } + } + }, + "mongodb6legacy": { + "version": "npm:mongodb@6.3.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.3.0.tgz", + "integrity": "sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA==", + "requires": { + "@mongodb-js/saslprep": "^1.1.0", + "bson": "^6.2.0", + "mongodb-connection-string-url": "^3.0.0" + }, + "dependencies": { + "@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "requires": { + "@types/webidl-conversions": "*" + } + }, + "bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==" + }, + "mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "requires": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "requires": { + "punycode": "^2.3.1" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "requires": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + } + } + } + }, + "mongodb7legacy": { + "version": "npm:mongodb@6.20.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", + "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==", + "requires": { + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.2" + }, + "dependencies": { + "@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "requires": { + "@types/webidl-conversions": "*" + } + }, + "bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==" + }, + "mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "requires": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "requires": { + "punycode": "^2.3.1" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "requires": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + } + } + } + }, + "mongodb8legacy": { + "version": "npm:mongodb@6.20.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", + "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==", + "requires": { + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.2" + }, + "dependencies": { + "@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "requires": { + "@types/webidl-conversions": "*" + } + }, + "bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==" + }, + "mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "requires": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "requires": { + "punycode": "^2.3.1" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "requires": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + } + } + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2731,6 +4182,14 @@ "wrappy": "1" } }, + "optional-require": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.10.tgz", + "integrity": "sha512-0r3OB9EIQsP+a5HVATHq2ExIy2q/Vaffoo4IAikW1spCYswhLxqWQS0i3GwS3AdY/OIP4SWZHLGz8CMU558PGw==", + "requires": { + "require-at": "^1.0.6" + } + }, "os": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/os/-/os-0.1.2.tgz", @@ -2893,6 +4352,11 @@ } } }, + "require-at": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", + "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==" + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -2921,6 +4385,15 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, "sax": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", @@ -3110,6 +4583,20 @@ "resolved": "https://registry.npmjs.org/slick/-/slick-1.12.2.tgz", "integrity": "sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==" }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" + }, + "socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "requires": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -3124,6 +4611,14 @@ "source-map": "^0.6.0" } }, + "sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "requires": { + "memory-pager": "^1.0.2" + } + }, "speech-rule-engine": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz", diff --git a/sandstorm.js b/sandstorm.js index aa94fb508..b50922794 100644 --- a/sandstorm.js +++ b/sandstorm.js @@ -359,9 +359,12 @@ if (isSandstorm && Meteor.isServer) { // Meteor application. We need to enforce “public” visibility as the sharing // is now handled by Sandstorm. // See https://github.com/wekan/wekan/issues/346 + // Migration disabled - using backward compatibility approach + /* Migrations.add('enforce-public-visibility-for-sandstorm', () => { Boards.update('sandstorm', { $set: { permission: 'public' } }); }); + */ // Monkey patch to work around the problem described in // https://github.com/sandstorm-io/meteor-accounts-sandstorm/pull/31 diff --git a/server/00checkStartup.js b/server/00checkStartup.js index 320a5242e..d4d152a1e 100644 --- a/server/00checkStartup.js +++ b/server/00checkStartup.js @@ -1,6 +1,18 @@ const fs = require('fs'); const os = require('os'); +// Configure SyncedCron to suppress console logging +// This must be done before any SyncedCron operations +if (Meteor.isServer) { + const { SyncedCron } = require('meteor/percolate:synced-cron'); + SyncedCron.config({ + log: false, // Disable console logging + collectionName: 'cronJobs', // Use custom collection name + utc: false, // Use local time + collectionTTL: 172800 // 2 days TTL + }); +} + let errors = []; if (!process.env.WRITABLE_PATH) { errors.push("WRITABLE_PATH environment variable missing and/or unset, please configure !"); @@ -25,9 +37,6 @@ if (errors.length > 0) { process.exit(1); } -// Import migration runner for on-demand migrations -import './migrationRunner'; - // Import cron job storage for persistent job tracking import './cronJobStorage'; diff --git a/server/attachmentMigration.js b/server/attachmentMigration.js index ed1c75010..2d2d80bc7 100644 --- a/server/attachmentMigration.js +++ b/server/attachmentMigration.js @@ -1,572 +1,204 @@ +/** + * Server-side Attachment Migration System + * Handles migration of attachments from old structure to new structure + */ + import { Meteor } from 'meteor/meteor'; +import { ReactiveVar } from 'meteor/reactive-var'; import { ReactiveCache } from '/imports/reactiveCache'; -import { Attachments, fileStoreStrategyFactory } from '/models/attachments'; -import { moveToStorage } from '/models/lib/fileStoreStrategy'; -import os from 'os'; -import { createHash } from 'crypto'; -// Migration state management -const migrationState = { - isRunning: false, - isPaused: false, - targetStorage: null, - batchSize: 10, - delayMs: 1000, - cpuThreshold: 70, - progress: 0, - totalAttachments: 0, - migratedAttachments: 0, - currentBatch: [], - migrationQueue: [], - log: [], - startTime: null, - lastCpuCheck: 0 -}; +// Reactive variables for tracking migration progress +const migrationProgress = new ReactiveVar(0); +const migrationStatus = new ReactiveVar(''); +const unconvertedAttachments = new ReactiveVar([]); -// CPU monitoring -function getCpuUsage() { - const cpus = os.cpus(); - let totalIdle = 0; - let totalTick = 0; - - cpus.forEach(cpu => { - for (const type in cpu.times) { - totalTick += cpu.times[type]; - } - totalIdle += cpu.times.idle; - }); - - const idle = totalIdle / cpus.length; - const total = totalTick / cpus.length; - const usage = 100 - Math.floor(100 * idle / total); - - return usage; -} - -// Logging function -function addToLog(message) { - const timestamp = new Date().toISOString(); - const logEntry = `[${timestamp}] ${message}`; - migrationState.log.unshift(logEntry); - - // Keep only last 100 log entries - if (migrationState.log.length > 100) { - migrationState.log = migrationState.log.slice(0, 100); - } - - console.log(logEntry); -} - -// Get migration status -function getMigrationStatus() { - return { - isRunning: migrationState.isRunning, - isPaused: migrationState.isPaused, - targetStorage: migrationState.targetStorage, - progress: migrationState.progress, - totalAttachments: migrationState.totalAttachments, - migratedAttachments: migrationState.migratedAttachments, - remainingAttachments: migrationState.totalAttachments - migrationState.migratedAttachments, - status: migrationState.isRunning ? (migrationState.isPaused ? 'paused' : 'running') : 'idle', - log: migrationState.log.slice(0, 10).join('\n'), // Return last 10 log entries - startTime: migrationState.startTime, - estimatedTimeRemaining: calculateEstimatedTimeRemaining() - }; -} - -// Calculate estimated time remaining -function calculateEstimatedTimeRemaining() { - if (!migrationState.isRunning || migrationState.migratedAttachments === 0) { - return null; - } - - const elapsed = Date.now() - migrationState.startTime; - const rate = migrationState.migratedAttachments / elapsed; // attachments per ms - const remaining = migrationState.totalAttachments - migrationState.migratedAttachments; - - return Math.round(remaining / rate); -} - -// Process a single attachment migration -function migrateAttachment(attachmentId) { - try { - const attachment = ReactiveCache.getAttachment(attachmentId); - if (!attachment) { - addToLog(`Warning: Attachment ${attachmentId} not found`); - return false; - } - - // Check if already in target storage - const currentStorage = fileStoreStrategyFactory.getFileStrategy(attachment, 'original').getStorageName(); - if (currentStorage === migrationState.targetStorage) { - addToLog(`Attachment ${attachmentId} already in target storage ${migrationState.targetStorage}`); - return true; - } - - // Perform migration - moveToStorage(attachment, migrationState.targetStorage, fileStoreStrategyFactory); - addToLog(`Migrated attachment ${attachmentId} from ${currentStorage} to ${migrationState.targetStorage}`); - - return true; - } catch (error) { - addToLog(`Error migrating attachment ${attachmentId}: ${error.message}`); - return false; - } -} - -// Process a batch of attachments -function processBatch() { - if (!migrationState.isRunning || migrationState.isPaused) { - return; +class AttachmentMigrationService { + constructor() { + this.migrationCache = new Map(); } - const batch = migrationState.migrationQueue.splice(0, migrationState.batchSize); - if (batch.length === 0) { - // Migration complete - migrationState.isRunning = false; - migrationState.progress = 100; - addToLog(`Migration completed. Migrated ${migrationState.migratedAttachments} attachments.`); - return; - } - - let successCount = 0; - batch.forEach(attachmentId => { - if (migrateAttachment(attachmentId)) { - successCount++; - migrationState.migratedAttachments++; - } - }); - - // Update progress - migrationState.progress = Math.round((migrationState.migratedAttachments / migrationState.totalAttachments) * 100); - - addToLog(`Processed batch: ${successCount}/${batch.length} successful. Progress: ${migrationState.progress}%`); - - // Check CPU usage - const currentTime = Date.now(); - if (currentTime - migrationState.lastCpuCheck > 5000) { // Check every 5 seconds - const cpuUsage = getCpuUsage(); - migrationState.lastCpuCheck = currentTime; - - if (cpuUsage > migrationState.cpuThreshold) { - addToLog(`CPU usage ${cpuUsage}% exceeds threshold ${migrationState.cpuThreshold}%. Pausing migration.`); - migrationState.isPaused = true; - return; - } - } - - // Schedule next batch - if (migrationState.isRunning && !migrationState.isPaused) { - Meteor.setTimeout(() => { - processBatch(); - }, migrationState.delayMs); - } -} - -// Initialize migration queue -function initializeMigrationQueue() { - const allAttachments = ReactiveCache.getAttachments(); - migrationState.totalAttachments = allAttachments.length; - migrationState.migrationQueue = allAttachments.map(attachment => attachment._id); - migrationState.migratedAttachments = 0; - migrationState.progress = 0; - - addToLog(`Initialized migration queue with ${migrationState.totalAttachments} attachments`); -} - -// Start migration -function startMigration(targetStorage, batchSize, delayMs, cpuThreshold) { - if (migrationState.isRunning) { - throw new Meteor.Error('migration-already-running', 'Migration is already running'); - } - - migrationState.isRunning = true; - migrationState.isPaused = false; - migrationState.targetStorage = targetStorage; - migrationState.batchSize = batchSize; - migrationState.delayMs = delayMs; - migrationState.cpuThreshold = cpuThreshold; - migrationState.startTime = Date.now(); - migrationState.lastCpuCheck = 0; - - initializeMigrationQueue(); - addToLog(`Started migration to ${targetStorage} with batch size ${batchSize}, delay ${delayMs}ms, CPU threshold ${cpuThreshold}%`); - - // Start processing - processBatch(); -} - -// Pause migration -function pauseMigration() { - if (!migrationState.isRunning) { - throw new Meteor.Error('migration-not-running', 'No migration is currently running'); - } - - migrationState.isPaused = true; - addToLog('Migration paused'); -} - -// Resume migration -function resumeMigration() { - if (!migrationState.isRunning) { - throw new Meteor.Error('migration-not-running', 'No migration is currently running'); - } - - if (!migrationState.isPaused) { - throw new Meteor.Error('migration-not-paused', 'Migration is not paused'); - } - - migrationState.isPaused = false; - addToLog('Migration resumed'); - - // Continue processing - processBatch(); -} - -// Stop migration -function stopMigration() { - if (!migrationState.isRunning) { - throw new Meteor.Error('migration-not-running', 'No migration is currently running'); - } - - migrationState.isRunning = false; - migrationState.isPaused = false; - migrationState.migrationQueue = []; - addToLog('Migration stopped'); -} - -// Get attachment storage configuration -function getAttachmentStorageConfiguration() { - const config = { - filesystemPath: process.env.WRITABLE_PATH || '/data', - attachmentsPath: `${process.env.WRITABLE_PATH || '/data'}/attachments`, - avatarsPath: `${process.env.WRITABLE_PATH || '/data'}/avatars`, - gridfsEnabled: true, // Always available - s3Enabled: false, - s3Endpoint: '', - s3Bucket: '', - s3Region: '', - s3SslEnabled: false, - s3Port: 443 - }; - - // Check S3 configuration - if (process.env.S3) { + /** + * Migrate all attachments for a board + * @param {string} boardId - The board ID + */ + async migrateBoardAttachments(boardId) { try { - const s3Config = JSON.parse(process.env.S3).s3; - if (s3Config && s3Config.key && s3Config.secret && s3Config.bucket) { - config.s3Enabled = true; - config.s3Endpoint = s3Config.endPoint || ''; - config.s3Bucket = s3Config.bucket || ''; - config.s3Region = s3Config.region || ''; - config.s3SslEnabled = s3Config.sslEnabled || false; - config.s3Port = s3Config.port || 443; - } - } catch (error) { - console.error('Error parsing S3 configuration:', error); - } - } - - return config; -} - -// Get attachment monitoring data -function getAttachmentMonitoringData() { - const attachments = ReactiveCache.getAttachments(); - const stats = { - totalAttachments: attachments.length, - filesystemAttachments: 0, - gridfsAttachments: 0, - s3Attachments: 0, - totalSize: 0, - filesystemSize: 0, - gridfsSize: 0, - s3Size: 0 - }; - - attachments.forEach(attachment => { - const storage = fileStoreStrategyFactory.getFileStrategy(attachment, 'original').getStorageName(); - const size = attachment.size || 0; - - stats.totalSize += size; - - switch (storage) { - case 'fs': - stats.filesystemAttachments++; - stats.filesystemSize += size; - break; - case 'gridfs': - stats.gridfsAttachments++; - stats.gridfsSize += size; - break; - case 's3': - stats.s3Attachments++; - stats.s3Size += size; - break; - } - }); - - return stats; -} - -// Test S3 connection -function testS3Connection(s3Config) { - // This would implement actual S3 connection testing - // For now, we'll just validate the configuration - if (!s3Config.secretKey) { - throw new Meteor.Error('s3-secret-key-required', 'S3 secret key is required'); - } - - // In a real implementation, you would test the connection here - // For now, we'll just return success - return { success: true, message: 'S3 connection test successful' }; -} - -// Save S3 settings -function saveS3Settings(s3Config) { - if (!s3Config.secretKey) { - throw new Meteor.Error('s3-secret-key-required', 'S3 secret key is required'); - } - - // In a real implementation, you would save the S3 configuration - // For now, we'll just return success - return { success: true, message: 'S3 settings saved successfully' }; -} - -// Meteor methods -if (Meteor.isServer) { - Meteor.methods({ - // Migration methods - 'startAttachmentMigration'(config) { - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - - const user = ReactiveCache.getUser(this.userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - startMigration(config.targetStorage, config.batchSize, config.delayMs, config.cpuThreshold); - return { success: true, message: 'Migration started' }; - }, - - 'pauseAttachmentMigration'() { - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - - const user = ReactiveCache.getUser(this.userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - pauseMigration(); - return { success: true, message: 'Migration paused' }; - }, - - 'resumeAttachmentMigration'() { - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - - const user = ReactiveCache.getUser(this.userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - resumeMigration(); - return { success: true, message: 'Migration resumed' }; - }, - - 'stopAttachmentMigration'() { - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - - const user = ReactiveCache.getUser(this.userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - stopMigration(); - return { success: true, message: 'Migration stopped' }; - }, - - 'getAttachmentMigrationSettings'() { - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - - const user = ReactiveCache.getUser(this.userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - return { - batchSize: migrationState.batchSize, - delayMs: migrationState.delayMs, - cpuThreshold: migrationState.cpuThreshold, - status: migrationState.isRunning ? (migrationState.isPaused ? 'paused' : 'running') : 'idle', - progress: migrationState.progress - }; - }, - - // Configuration methods - 'getAttachmentStorageConfiguration'() { - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - - const user = ReactiveCache.getUser(this.userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - return getAttachmentStorageConfiguration(); - }, - - 'testS3Connection'(s3Config) { - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - - const user = ReactiveCache.getUser(this.userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - return testS3Connection(s3Config); - }, - - 'saveS3Settings'(s3Config) { - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - - const user = ReactiveCache.getUser(this.userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - return saveS3Settings(s3Config); - }, - - // Monitoring methods - 'getAttachmentMonitoringData'() { - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - - const user = ReactiveCache.getUser(this.userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - return getAttachmentMonitoringData(); - }, - - 'refreshAttachmentMonitoringData'() { - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - - const user = ReactiveCache.getUser(this.userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - return getAttachmentMonitoringData(); - }, - - 'exportAttachmentMonitoringData'() { - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - - const user = ReactiveCache.getUser(this.userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - const monitoringData = getAttachmentMonitoringData(); - const migrationStatus = getMigrationStatus(); + console.log(`Starting attachment migration for board: ${boardId}`); - return { - timestamp: new Date().toISOString(), - monitoring: monitoringData, - migration: migrationStatus, - system: { - cpuUsage: getCpuUsage(), - memoryUsage: process.memoryUsage(), - uptime: process.uptime() + // Get all attachments for the board + const attachments = Attachments.find({ + 'meta.boardId': boardId + }).fetch(); + + const totalAttachments = attachments.length; + let migratedCount = 0; + + migrationStatus.set(`Migrating ${totalAttachments} attachments...`); + migrationProgress.set(0); + + for (const attachment of attachments) { + try { + // Check if attachment needs migration + if (this.needsMigration(attachment)) { + await this.migrateAttachment(attachment); + this.migrationCache.set(attachment._id, true); + } + + migratedCount++; + const progress = Math.round((migratedCount / totalAttachments) * 100); + migrationProgress.set(progress); + migrationStatus.set(`Migrated ${migratedCount}/${totalAttachments} attachments...`); + + } catch (error) { + console.error(`Error migrating attachment ${attachment._id}:`, error); + } + } + + // Update unconverted attachments list + const remainingUnconverted = this.getUnconvertedAttachments(boardId); + unconvertedAttachments.set(remainingUnconverted); + + migrationStatus.set('Attachment migration completed'); + migrationProgress.set(100); + + console.log(`Attachment migration completed for board: ${boardId}`); + + } catch (error) { + console.error(`Error migrating attachments for board ${boardId}:`, error); + migrationStatus.set(`Migration failed: ${error.message}`); + throw error; + } + } + + /** + * Check if an attachment needs migration + * @param {Object} attachment - The attachment object + * @returns {boolean} - True if attachment needs migration + */ + needsMigration(attachment) { + if (this.migrationCache.has(attachment._id)) { + return false; // Already migrated + } + + // Check if attachment has old structure + return !attachment.meta || + !attachment.meta.cardId || + !attachment.meta.boardId || + !attachment.meta.listId; + } + + /** + * Migrate a single attachment + * @param {Object} attachment - The attachment object + */ + async migrateAttachment(attachment) { + try { + // Get the card to find board and list information + const card = ReactiveCache.getCard(attachment.cardId); + if (!card) { + console.warn(`Card not found for attachment ${attachment._id}`); + return; + } + + const list = ReactiveCache.getList(card.listId); + if (!list) { + console.warn(`List not found for attachment ${attachment._id}`); + return; + } + + // Update attachment with new structure + const updateData = { + meta: { + cardId: attachment.cardId, + boardId: list.boardId, + listId: card.listId, + userId: attachment.userId, + createdAt: attachment.createdAt || new Date(), + migratedAt: new Date() } }; - } - }); - // Publications - Meteor.publish('attachmentMigrationStatus', function() { - if (!this.userId) { - return this.ready(); - } - - const user = ReactiveCache.getUser(this.userId); - if (!user || !user.isAdmin) { - return this.ready(); - } - - const self = this; - let handle; - - function updateStatus() { - const status = getMigrationStatus(); - self.changed('attachmentMigrationStatus', 'status', status); - } - - self.added('attachmentMigrationStatus', 'status', getMigrationStatus()); - - // Update every 2 seconds - handle = Meteor.setInterval(updateStatus, 2000); - - self.ready(); - - self.onStop(() => { - if (handle) { - Meteor.clearInterval(handle); + // Preserve existing meta data if it exists + if (attachment.meta) { + updateData.meta = { + ...attachment.meta, + ...updateData.meta + }; } - }); - }); - Meteor.publish('attachmentMonitoringData', function() { - if (!this.userId) { - return this.ready(); + Attachments.update(attachment._id, { $set: updateData }); + + console.log(`Migrated attachment ${attachment._id}`); + + } catch (error) { + console.error(`Error migrating attachment ${attachment._id}:`, error); + throw error; } + } - const user = ReactiveCache.getUser(this.userId); - if (!user || !user.isAdmin) { - return this.ready(); + /** + * Get unconverted attachments for a board + * @param {string} boardId - The board ID + * @returns {Array} - Array of unconverted attachments + */ + getUnconvertedAttachments(boardId) { + try { + const attachments = Attachments.find({ + 'meta.boardId': boardId + }).fetch(); + + return attachments.filter(attachment => this.needsMigration(attachment)); + } catch (error) { + console.error('Error getting unconverted attachments:', error); + return []; } + } - const self = this; - let handle; + /** + * Get migration progress + * @param {string} boardId - The board ID + * @returns {Object} - Migration progress data + */ + getMigrationProgress(boardId) { + const progress = migrationProgress.get(); + const status = migrationStatus.get(); + const unconverted = this.getUnconvertedAttachments(boardId); - function updateMonitoring() { - const data = getAttachmentMonitoringData(); - self.changed('attachmentMonitoringData', 'data', data); - } - - self.added('attachmentMonitoringData', 'data', getAttachmentMonitoringData()); - - // Update every 10 seconds - handle = Meteor.setInterval(updateMonitoring, 10000); - - self.ready(); - - self.onStop(() => { - if (handle) { - Meteor.clearInterval(handle); - } - }); - }); + return { + progress, + status, + unconvertedAttachments: unconverted + }; + } } + +const attachmentMigrationService = new AttachmentMigrationService(); + +// Meteor methods +Meteor.methods({ + 'attachmentMigration.migrateBoardAttachments'(boardId) { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); + } + + return attachmentMigrationService.migrateBoardAttachments(boardId); + }, + + 'attachmentMigration.getProgress'(boardId) { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); + } + + return attachmentMigrationService.getMigrationProgress(boardId); + }, + + 'attachmentMigration.getUnconvertedAttachments'(boardId) { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); + } + + return attachmentMigrationService.getUnconvertedAttachments(boardId); + } +}); + +export { attachmentMigrationService }; \ No newline at end of file diff --git a/server/boardMigrationDetector.js b/server/boardMigrationDetector.js index 352bc9d84..cd553ac47 100644 --- a/server/boardMigrationDetector.js +++ b/server/boardMigrationDetector.js @@ -5,7 +5,9 @@ import { Meteor } from 'meteor/meteor'; import { ReactiveVar } from 'meteor/reactive-var'; +import { check, Match } from 'meteor/check'; import { cronJobStorage } from './cronJobStorage'; +import Boards from '/models/boards'; // Reactive variables for board migration tracking export const unmigratedBoards = new ReactiveVar([]); @@ -38,7 +40,7 @@ class BoardMigrationDetector { this.scanUnmigratedBoards(); }, this.scanInterval); - console.log('Board migration detector started'); + // Board migration detector started } /** @@ -73,7 +75,7 @@ class BoardMigrationDetector { } // Check if memory usage is reasonable - if (resources.memoryUsage > 70) { + if (resources.memoryUsage > 85) { return false; } @@ -117,7 +119,7 @@ class BoardMigrationDetector { migrationScanInProgress.set(true); try { - console.log('Scanning for unmigrated boards...'); + // Scanning for unmigrated boards // Get all boards from the database const boards = this.getAllBoards(); @@ -132,7 +134,7 @@ class BoardMigrationDetector { unmigratedBoards.set(unmigrated); lastMigrationScan.set(new Date()); - console.log(`Found ${unmigrated.length} unmigrated boards`); + // Found unmigrated boards } catch (error) { console.error('Error scanning for unmigrated boards:', error); @@ -213,10 +215,19 @@ class BoardMigrationDetector { /** * Start migration for a specific board */ - async startBoardMigration(board) { + async startBoardMigration(boardId) { try { - console.log(`Starting migration for board: ${board.title || board._id}`); - + const board = Boards.findOne(boardId); + if (!board) { + throw new Error(`Board ${boardId} not found`); + } + + // Check if board already has latest migration version + if (board.migrationVersion && board.migrationVersion >= 1) { + console.log(`Board ${boardId} already has latest migration version`); + return null; + } + // Create migration job for this board const jobId = `board_migration_${board._id}_${Date.now()}`; @@ -246,7 +257,7 @@ class BoardMigrationDetector { return jobId; } catch (error) { - console.error(`Error starting migration for board ${board._id}:`, error); + console.error(`Error starting migration for board ${boardId}:`, error); throw error; } } @@ -271,7 +282,7 @@ class BoardMigrationDetector { * Force a full scan of all boards */ async forceScan() { - console.log('Forcing full board migration scan...'); + // Forcing full board migration scan await this.scanUnmigratedBoards(); } @@ -315,7 +326,7 @@ class BoardMigrationDetector { const updatedUnmigrated = currentUnmigrated.filter(b => b._id !== boardId); unmigratedBoards.set(updatedUnmigrated); - console.log(`Marked board ${boardId} as migrated for ${migrationType}`); + // Marked board as migrated } catch (error) { console.error(`Error marking board ${boardId} as migrated:`, error); @@ -353,6 +364,8 @@ Meteor.methods({ }, 'boardMigration.getBoardStatus'(boardId) { + check(boardId, String); + if (!this.userId) { throw new Meteor.Error('not-authorized'); } @@ -361,10 +374,23 @@ Meteor.methods({ }, 'boardMigration.markAsMigrated'(boardId, migrationType) { + check(boardId, String); + check(migrationType, String); + if (!this.userId) { throw new Meteor.Error('not-authorized'); } return boardMigrationDetector.markBoardAsMigrated(boardId, migrationType); + }, + + 'boardMigration.startBoardMigration'(boardId) { + check(boardId, String); + + if (!this.userId) { + throw new Meteor.Error('not-authorized'); + } + + return boardMigrationDetector.startBoardMigration(boardId); } }); diff --git a/server/cronJobStorage.js b/server/cronJobStorage.js index 41644519f..76b659e1b 100644 --- a/server/cronJobStorage.js +++ b/server/cronJobStorage.js @@ -36,7 +36,7 @@ class CronJobStorage { constructor() { this.maxConcurrentJobs = this.getMaxConcurrentJobs(); this.cpuThreshold = 80; // CPU usage threshold percentage - this.memoryThreshold = 90; // Memory usage threshold percentage + this.memoryThreshold = 95; // Memory usage threshold percentage (increased for better job processing) } /** @@ -379,12 +379,12 @@ Meteor.startup(() => { // Resume incomplete jobs const resumedJobs = cronJobStorage.resumeIncompleteJobs(); if (resumedJobs.length > 0) { - console.log(`Resumed ${resumedJobs.length} incomplete cron jobs:`, resumedJobs); + // Resumed incomplete cron jobs } // Cleanup old jobs const cleanup = cronJobStorage.cleanupOldJobs(); if (cleanup.removedQueue > 0 || cleanup.removedStatus > 0 || cleanup.removedSteps > 0) { - console.log('Cleaned up old cron jobs:', cleanup); + // Cleaned up old cron jobs } }); diff --git a/server/cronMigrationManager.js b/server/cronMigrationManager.js index dc30677b8..8386466ed 100644 --- a/server/cronMigrationManager.js +++ b/server/cronMigrationManager.js @@ -247,8 +247,10 @@ class CronMigrationManager { // Start job processor this.startJobProcessor(); - // Update cron jobs list - this.updateCronJobsList(); + // Update cron jobs list after a short delay to allow SyncedCron to initialize + Meteor.setTimeout(() => { + this.updateCronJobsList(); + }, 1000); } /** @@ -263,7 +265,7 @@ class CronMigrationManager { this.processJobQueue(); }, 5000); // Check every 5 seconds - console.log('Cron job processor started with CPU throttling'); + // Cron job processor started with CPU throttling } /** @@ -469,7 +471,7 @@ class CronMigrationManager { const { boardId, boardTitle, migrationType } = jobData; try { - console.log(`Starting board migration for ${boardTitle || boardId}`); + // Starting board migration // Create migration steps for this board const steps = this.createBoardMigrationSteps(boardId, migrationType); @@ -503,7 +505,7 @@ class CronMigrationManager { // Mark board as migrated this.markBoardAsMigrated(boardId, migrationType); - console.log(`Completed board migration for ${boardTitle || boardId}`); + // Completed board migration } catch (error) { console.error(`Board migration failed for ${boardId}:`, error); @@ -633,7 +635,7 @@ class CronMigrationManager { */ async runMigrationStep(step) { try { - console.log(`Starting migration: ${step.name}`); + // Starting migration step cronMigrationCurrentStep.set(step.name); cronMigrationStatus.set(`Running: ${step.description}`); @@ -654,7 +656,7 @@ class CronMigrationManager { step.progress = 100; step.status = 'completed'; - console.log(`Completed migration: ${step.name}`); + // Completed migration step // Update progress this.updateProgress(); @@ -873,6 +875,13 @@ class CronMigrationManager { * Update cron jobs list */ updateCronJobsList() { + // Check if SyncedCron is available and has jobs + if (!SyncedCron || !SyncedCron.jobs || !Array.isArray(SyncedCron.jobs)) { + // SyncedCron not available or no jobs yet + cronJobs.set([]); + return; + } + const jobs = SyncedCron.jobs.map(job => { const step = this.migrationSteps.find(s => s.cronName === job.name); return { diff --git a/server/migrationRunner.js b/server/migrationRunner.js deleted file mode 100644 index 47f2773f2..000000000 --- a/server/migrationRunner.js +++ /dev/null @@ -1,404 +0,0 @@ -/** - * 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() - }; - } -}); diff --git a/server/migrations.js b/server/migrations.js deleted file mode 100644 index 8367f2562..000000000 --- a/server/migrations.js +++ /dev/null @@ -1,1581 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { TAPi18n } from '/imports/i18n'; -import AccountSettings from '../models/accountSettings'; -import TableVisibilityModeSettings from '../models/tableVisibilityModeSettings'; -import Actions from '../models/actions'; -import Activities from '../models/activities'; -import Announcements from '../models/announcements'; -import Attachments from '../models/attachments'; -//import AttachmentsOld from '../models/attachments_old'; -import Avatars from '../models/avatars'; -//import AvatarsOld from '../models/avatars_old'; -import Boards from '../models/boards'; -import CardComments from '../models/cardComments'; -import Cards from '../models/cards'; -import ChecklistItems from '../models/checklistItems'; -import Checklists from '../models/checklists'; -import CustomFields from '../models/customFields'; -import Integrations from '../models/integrations'; -import InvitationCodes from '../models/invitationCodes'; -import Lists from '../models/lists'; -import Rules from '../models/rules'; -import Settings from '../models/settings'; -import Swimlanes from '../models/swimlanes'; -import Triggers from '../models/triggers'; -import UnsavedEdits from '../models/unsavedEdits'; -import Users from '../models/users'; - -// 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. -// 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. - -// In the context of migration functions we don't want to validate database -// mutation queries against the current (ie, latest) collection schema. Doing -// that would work at the time we write the migration but would break in the -// future when we'll update again the concerned collection schema. -// -// To prevent this bug we always have to disable the schema validation and -// argument transformations. We generally use the shorthandlers defined below. -const noValidate = { - validate: false, - filter: false, - autoConvert: false, - removeEmptyStrings: false, - getAutoValues: false, -}; -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( - { - background: { - $exists: false, - }, - }, - { - $set: { - background: { - type: 'color', - color: defaultColor, - }, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-cardcounterlist-allowed', () => { - Boards.update( - { - allowsCardCounterList: { - $exists: false, - }, - }, - { - $set: { - allowsCardCounterList: true, - }, - }, - noValidateMulti, - ); -}); - -/* -Migrations.add('add-boardmemberlist-allowed', () => { - Boards.update( - { - allowsBoardMemberList: { - $exists: false, - }, - }, - { - $set: { - allowsBoardMemberList: true, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('lowercase-board-permission', () => { - ['Public', 'Private'].forEach(permission => { - Boards.update( - { permission }, - { $set: { permission: permission.toLowerCase() } }, - noValidateMulti, - ); - }); -}); - -/* -// Security migration: see https://github.com/wekan/wekan/issues/99 -Migrations.add('change-attachments-type-for-non-images', () => { - const newTypeForNonImage = 'application/octet-stream'; - Attachments.find().forEach(file => { - if (!file.isImage()) { - Attachments.update( - file._id, - { - $set: { - 'original.type': newTypeForNonImage, - 'copies.attachments.type': newTypeForNonImage, - }, - }, - noValidate, - ); - } - }); -}); - -Migrations.add('card-covers', () => { - Cards.find().forEach(card => { - const cover = Attachments.findOne({ cardId: card._id, cover: true }); - if (cover) { - Cards.update(card._id, { $set: { coverId: cover._id } }, noValidate); - } - }); - Attachments.update({}, { $unset: { cover: '' } }, noValidateMulti); -}); - -Migrations.add('use-css-class-for-boards-colors', () => { - const associationTable = { - '#27AE60': 'nephritis', - '#C0392B': 'pomegranate', - '#2980B9': 'belize', - '#8E44AD': 'wisteria', - '#2C3E50': 'midnight', - '#E67E22': 'pumpkin', - '#CD5A91': 'moderatepink', - '#00AECC': 'strongcyan', - '#4BBF6B': 'limegreen', - '#2C3E51': 'dark', - '#27AE61': 'relax', - '#568BA2': 'corteza', - '#499BEA': 'clearblue', - '#596557': 'natural', - '#2A80B8': 'modern', - '#2a2a2a': 'moderndark', - '#222222': 'exodark', - '#0A0A14': 'cleandark', - '#FFFFFF': 'cleanlight', - }; - Boards.find().forEach(board => { - const oldBoardColor = board.background.color; - const newBoardColor = associationTable[oldBoardColor]; - Boards.update( - board._id, - { - $set: { color: newBoardColor }, - $unset: { background: '' }, - }, - noValidate, - ); - }); -}); - -Migrations.add('denormalize-star-number-per-board', () => { - Boards.find().forEach(board => { - const nStars = Users.find({ 'profile.starredBoards': board._id }).countDocuments(); - Boards.update(board._id, { $set: { stars: nStars } }, noValidate); - }); -}); - -// We want to keep a trace of former members so we can efficiently publish their -// infos in the general board publication. -Migrations.add('add-member-isactive-field', () => { - Boards.find({}, { fields: { members: 1 } }).forEach(board => { - const allUsersWithSomeActivity = _.chain( - Activities.find( - { boardId: board._id }, - { fields: { userId: 1 } }, - ).fetch(), - ) - .pluck('userId') - .uniq() - .value(); - const currentUsers = _.pluck(board.members, 'userId'); - const formerUsers = _.difference(allUsersWithSomeActivity, currentUsers); - - const newMemberSet = []; - board.members.forEach(member => { - member.isActive = true; - newMemberSet.push(member); - }); - formerUsers.forEach(userId => { - newMemberSet.push({ - userId, - isAdmin: false, - isActive: false, - }); - }); - Boards.update(board._id, { $set: { members: newMemberSet } }, noValidate); - }); -}); - -Migrations.add('add-sort-checklists', () => { - Checklists.find().forEach((checklist, index) => { - if (!checklist.hasOwnProperty('sort')) { - Checklists.direct.update( - checklist._id, - { $set: { sort: index } }, - noValidate, - ); - } - checklist.items.forEach((item, index) => { - if (!item.hasOwnProperty('sort')) { - Checklists.direct.update( - { _id: checklist._id, 'items._id': item._id }, - { $set: { 'items.$.sort': index } }, - noValidate, - ); - } - }); - }); -}); - -Migrations.add('add-swimlanes', () => { - Boards.find().forEach(board => { - const swimlaneId = board.getDefaultSwimline()._id; - Cards.find({ boardId: board._id }).forEach(card => { - if (!card.hasOwnProperty('swimlaneId')) { - Cards.direct.update( - { _id: card._id }, - { $set: { swimlaneId } }, - noValidate, - ); - } - }); - }); -}); - -Migrations.add('add-views', () => { - Boards.find().forEach(board => { - if (!board.hasOwnProperty('view')) { - Boards.direct.update( - { _id: board._id }, - { $set: { view: 'board-view-swimlanes' } }, - noValidate, - ); - } - }); -}); - -Migrations.add('add-checklist-items', () => { - Checklists.find().forEach(checklist => { - // Create new items - _.sortBy(checklist.items, 'sort').forEach((item, index) => { - ChecklistItems.direct.insert({ - title: item.title ? item.title : 'Checklist', - sort: index, - isFinished: item.isFinished, - checklistId: checklist._id, - cardId: checklist.cardId, - }); - }); - - // Delete old ones - Checklists.direct.update( - { _id: checklist._id }, - { $unset: { items: 1 } }, - noValidate, - ); - }); -}); - -Migrations.add('add-card-types', () => { - Cards.find().forEach(card => { - Cards.direct.update( - { _id: card._id }, - { - $set: { - type: 'cardType-card', - linkedId: null, - }, - }, - noValidate, - ); - }); -}); - -Migrations.add('add-custom-fields-to-cards', () => { - Cards.update( - { - customFields: { - $exists: false, - }, - }, - { - $set: { - customFields: [], - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-requester-field', () => { - Cards.update( - { - requestedBy: { - $exists: false, - }, - }, - { - $set: { - requestedBy: '', - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-assigner-field', () => { - Cards.update( - { - assignedBy: { - $exists: false, - }, - }, - { - $set: { - assignedBy: '', - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-parent-field-to-cards', () => { - Cards.update( - { - parentId: { - $exists: false, - }, - }, - { - $set: { - parentId: '', - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-subtasks-boards', () => { - Boards.update( - { - subtasksDefaultBoardId: { - $exists: false, - }, - }, - { - $set: { - subtasksDefaultBoardId: null, - subtasksDefaultListId: null, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-subtasks-sort', () => { - Boards.update( - { - subtaskSort: { - $exists: false, - }, - }, - { - $set: { - subtaskSort: -1, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-subtasks-allowed', () => { - Boards.update( - { - allowsSubtasks: { - $exists: false, - }, - }, - { - $set: { - allowsSubtasks: true, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-subtasks-allowed', () => { - Boards.update( - { - presentParentTask: { - $exists: false, - }, - }, - { - $set: { - presentParentTask: 'no-parent', - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-authenticationMethod', () => { - Users.update( - { - authenticationMethod: { - $exists: false, - }, - }, - { - $set: { - authenticationMethod: 'password', - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('remove-tag', () => { - Users.update( - {}, - { - $unset: { - 'profile.tags': 1, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('remove-customFields-references-broken', () => { - Cards.update( - { 'customFields.$value': null }, - { - $pull: { - customFields: { value: null }, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-product-name', () => { - Settings.update( - { - productName: { - $exists: false, - }, - }, - { - $set: { - productName: '', - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-hide-logo', () => { - Settings.update( - { - hideLogo: { - $exists: false, - }, - }, - { - $set: { - hideLogo: false, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-hide-card-counter-list', () => { - Settings.update( - { - hideCardCounterList: { - $exists: false, - }, - }, - { - $set: { - hideCardCounterList: false, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-hide-board-member-list', () => { - Settings.update( - { - hideBoardMemberList: { - $exists: false, - }, - }, - { - $set: { - hideBoardMemberList: false, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-displayAuthenticationMethod', () => { - Settings.update( - { - displayAuthenticationMethod: { - $exists: false, - }, - }, - { - $set: { - displayAuthenticationMethod: true, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-defaultAuthenticationMethod', () => { - Settings.update( - { - defaultAuthenticationMethod: { - $exists: false, - }, - }, - { - $set: { - defaultAuthenticationMethod: 'password', - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-templates', () => { - Boards.update( - { - type: { - $exists: false, - }, - }, - { - $set: { - type: 'board', - }, - }, - noValidateMulti, - ); - Swimlanes.update( - { - type: { - $exists: false, - }, - }, - { - $set: { - type: 'swimlane', - }, - }, - noValidateMulti, - ); - Lists.update( - { - type: { - $exists: false, - }, - swimlaneId: { - $exists: false, - }, - }, - { - $set: { - type: 'list', - swimlaneId: '', - }, - }, - noValidateMulti, - ); - Users.find({ - 'profile.templatesBoardId': { - $exists: false, - }, - }).forEach(user => { - // Create board and swimlanes - Boards.insert( - { - title: TAPi18n.__('templates'), - permission: 'private', - type: 'template-container', - members: [ - { - userId: user._id, - isAdmin: true, - isActive: true, - isNoComments: false, - isCommentOnly: false, - }, - ], - }, - (err, boardId) => { - // Insert the reference to our templates board - Users.update(user._id, { - $set: { 'profile.templatesBoardId': boardId }, - }); - - // Insert the card templates swimlane - Swimlanes.insert( - { - title: TAPi18n.__('card-templates-swimlane'), - boardId, - sort: 1, - type: 'template-container', - }, - (err, swimlaneId) => { - // Insert the reference to out card templates swimlane - Users.update(user._id, { - $set: { 'profile.cardTemplatesSwimlaneId': swimlaneId }, - }); - }, - ); - - // Insert the list templates swimlane - Swimlanes.insert( - { - title: TAPi18n.__('list-templates-swimlane'), - boardId, - sort: 2, - type: 'template-container', - }, - (err, swimlaneId) => { - // Insert the reference to out list templates swimlane - Users.update(user._id, { - $set: { 'profile.listTemplatesSwimlaneId': swimlaneId }, - }); - }, - ); - - // Insert the board templates swimlane - Swimlanes.insert( - { - title: TAPi18n.__('board-templates-swimlane'), - boardId, - sort: 3, - type: 'template-container', - }, - (err, swimlaneId) => { - // Insert the reference to out board templates swimlane - Users.update(user._id, { - $set: { 'profile.boardTemplatesSwimlaneId': swimlaneId }, - }); - }, - ); - }, - ); - }); -}); - -Migrations.add('fix-circular-reference_', () => { - Cards.find().forEach(card => { - if (card.parentId === card._id) { - Cards.update(card._id, { $set: { parentId: '' } }, noValidateMulti); - } - }); -}); - -Migrations.add('mutate-boardIds-in-customfields', () => { - CustomFields.find().forEach(cf => { - CustomFields.update( - cf, - { - $set: { - boardIds: [cf.boardId], - }, - $unset: { - boardId: '', - }, - }, - noValidateMulti, - ); - }); -}); - -const modifiedAtTables = [ - AccountSettings, - TableVisibilityModeSettings, - Actions, - Activities, - Announcements, - Boards, - CardComments, - Cards, - ChecklistItems, - Checklists, - CustomFields, - Integrations, - InvitationCodes, - Lists, - Rules, - Settings, - Swimlanes, - Triggers, - UnsavedEdits, - Users, -]; - -Migrations.add('add-missing-created-and-modified', () => { - Promise.all( - modifiedAtTables.map(db => - db - .rawCollection() - .updateMany( - { modifiedAt: { $exists: false } }, - { $set: { modifiedAt: new Date() } }, - { multi: true }, - ) - .then(() => - db - .rawCollection() - .updateMany( - { createdAt: { $exists: false } }, - { $set: { createdAt: new Date() } }, - { multi: true }, - ), - ), - ), - ) - .then(() => { - // eslint-disable-next-line no-console - console.info('Successfully added createdAt and updatedAt to all tables'); - }) - .catch(e => { - // eslint-disable-next-line no-console - console.error(e); - }); -}); - -Migrations.add('fix-incorrect-dates', () => { - const tables = [ - AccountSettings, - TableVisibilityModeSettings, - Actions, - Activities, - Announcements, - Boards, - CardComments, - Cards, - ChecklistItems, - Checklists, - CustomFields, - Integrations, - InvitationCodes, - Lists, - Rules, - Settings, - Swimlanes, - Triggers, - UnsavedEdits, - ]; - - // Dates were previously created with Date.now() which is a number, not a date - tables.forEach(t => - t - .rawCollection() - .find({ $or: [{ createdAt: { $type: 1 } }, { updatedAt: { $type: 1 } }] }) - .forEach(({ _id, createdAt, updatedAt }) => { - t.rawCollection().updateMany( - { _id }, - { - $set: { - createdAt: new Date(createdAt), - updatedAt: new Date(updatedAt), - }, - }, - ); - }), - ); -}); - -Migrations.add('add-assignee', () => { - Cards.update( - { - assignees: { - $exists: false, - }, - }, - { - $set: { - assignees: [], - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-profile-showDesktopDragHandles', () => { - Users.update( - { - 'profile.showDesktopDragHandles': { - $exists: false, - }, - }, - { - $set: { - 'profile.showDesktopDragHandles': false, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-profile-hiddenMinicardLabelText', () => { - Users.update( - { - 'profile.hiddenMinicardLabelText': { - $exists: false, - }, - }, - { - $set: { - 'profile.hiddenMinicardLabelText': false, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-receiveddate-allowed', () => { - Boards.update( - { - allowsReceivedDate: { - $exists: false, - }, - }, - { - $set: { - allowsReceivedDate: true, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-startdate-allowed', () => { - Boards.update( - { - allowsStartDate: { - $exists: false, - }, - }, - { - $set: { - allowsStartDate: true, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-duedate-allowed', () => { - Boards.update( - { - allowsDueDate: { - $exists: false, - }, - }, - { - $set: { - allowsDueDate: true, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-enddate-allowed', () => { - Boards.update( - { - allowsEndDate: { - $exists: false, - }, - }, - { - $set: { - allowsEndDate: true, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-members-allowed', () => { - Boards.update( - { - allowsMembers: { - $exists: false, - }, - }, - { - $set: { - allowsMembers: true, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-assignee-allowed', () => { - Boards.update( - { - allowsAssignee: { - $exists: false, - }, - }, - { - $set: { - allowsAssignee: true, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-labels-allowed', () => { - Boards.update( - { - allowsLabels: { - $exists: false, - }, - }, - { - $set: { - allowsLabels: true, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-checklists-allowed', () => { - Boards.update( - { - allowsChecklists: { - $exists: false, - }, - }, - { - $set: { - allowsChecklists: true, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-attachments-allowed', () => { - Boards.update( - { - allowsAttachments: { - $exists: false, - }, - }, - { - $set: { - allowsAttachments: true, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-comments-allowed', () => { - Boards.update( - { - allowsComments: { - $exists: false, - }, - }, - { - $set: { - allowsComments: true, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-assigned-by-allowed', () => { - Boards.update( - { - allowsAssignedBy: { - $exists: false, - }, - }, - { - $set: { - allowsAssignedBy: true, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-requested-by-allowed', () => { - Boards.update( - { - allowsRequestedBy: { - $exists: false, - }, - }, - { - $set: { - allowsRequestedBy: true, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-activities-allowed', () => { - Boards.update( - { - allowsActivities: { - $exists: false, - }, - }, - { - $set: { - allowsActivities: true, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-description-title-allowed', () => { - Boards.update( - { - allowsDescriptionTitle: { - $exists: false, - }, - }, - { - $set: { - allowsDescriptionTitle: true, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-description-text-allowed', () => { - Boards.update( - { - allowsDescriptionText: { - $exists: false, - }, - }, - { - $set: { - allowsDescriptionText: true, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-description-text-allowed-on-minicard', () => { - Boards.update( - { - allowsDescriptionTextOnMinicard: { - $exists: false, - }, - }, - { - $set: { - allowsDescriptionTextOnMinicard: true, - }, - }, - noValidateMulti, - ); -}); - - -Migrations.add('add-sort-field-to-boards', () => { - Boards.find().forEach((board, index) => { - if (!board.hasOwnProperty('sort')) { - Boards.direct.update(board._id, { $set: { sort: index } }, noValidate); - } - }); -}); - -Migrations.add('add-default-profile-view', () => { - Users.find().forEach(user => { - if (!user.hasOwnProperty('profile.boardView')) { - // Set default view - Users.direct.update( - { _id: user._id }, - { $set: { 'profile.boardView': 'board-view-swimlanes' } }, - noValidate, - ); - } - }); -}); - -Migrations.add('add-hide-logo-by-default', () => { - Settings.update( - { - hideLogo: { - hideLogo: false, - }, - }, - { - $set: { - hideLogo: true, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-hide-card-counter-list-by-default', () => { - Settings.update( - { - hideCardCounterList: { - hideCardCounterList: false, - }, - }, - { - $set: { - hideCardCounterList: true, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-hide-board-member-list-by-default', () => { - Settings.update( - { - hideBoardMemberList: { - hideBoardMember: false, - }, - }, - { - $set: { - hideBoardMemberList: true, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('add-card-number-allowed', () => { - Boards.update( - { - allowsCardNumber: { - $exists: false, - }, - }, - { - $set: { - allowsCardNumber: false, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('assign-boardwise-card-numbers', () => { - Boards.find().forEach(board => { - let nextCardNumber = board.getNextCardNumber(); - Cards.find( - { - boardId: board._id, - cardNumber: { - $exists: false - } - }, - { - sort: { createdAt: 1 } - } - ).forEach(card => { - Cards.update( - card._id, - { $set: { cardNumber: nextCardNumber } }, - noValidate); - nextCardNumber++; - }); - }) -}); - -Migrations.add('add-card-details-show-lists', () => { - Boards.update( - { - allowsShowLists: { - $exists: false, - }, - }, - { - $set: { - allowsShowLists: true, - }, - }, - noValidateMulti, - ); -}); - -/* -Migrations.add('migrate-attachments-collectionFS-to-ostrioFiles', () => { - Meteor.settings.public.ostrioFilesMigrationInProgress = "true"; - AttachmentsOld.find().forEach(function(fileObj) { - const newFileName = fileObj.name(); - const storagePath = Attachments.storagePath({}); - // OLD: - // const filePath = path.join(storagePath, `${fileObj._id}-${newFileName}`); - // NEW: Save file only with filename of ObjectID, not including filename. - // Fixes https://github.com/wekan/wekan/issues/4416#issuecomment-1510517168 - //const filePath = path.join(storagePath, `${fileObj._id}`); - const filePath = path.join(storagePath, `${fileObj._id}-${newFileName}`); - - // This is "example" variable, change it to the userId that you might be using. - const userId = fileObj.userId; - - const fileType = fileObj.type(); - const fileSize = fileObj.size(); - const fileId = fileObj._id; - - const readStream = fileObj.createReadStream('attachments'); - const writeStream = fs.createWriteStream(filePath); - - writeStream.on('error', function(err) { - console.log('Writing error: ', err, filePath); - }); - - // Once we have a file, then upload it to our new data storage - readStream.on('end', () => { - console.log('Ended: ', filePath); - // UserFiles is the new Meteor-Files/FilesCollection collection instance - - Attachments.addFile( - filePath, - { - fileName: newFileName, - type: fileType, - meta: { - boardId: fileObj.boardId, - cardId: fileObj.cardId, - listId: fileObj.listId, - swimlaneId: fileObj.swimlaneId, - uploadedBeforeMigration: fileObj.uploadedAt, - migrationTime: new Date(), - copies: fileObj.copies, - source: 'import' - }, - userId, - size: fileSize, - fileId, - }, - (err, fileRef) => { - if (err) { - console.log(err); - } else { - console.log('File Inserted: ', fileRef._id); - // Set the userId again - Attachments.update({ _id: fileRef._id }, { $set: { userId } }); - fileObj.remove(); - } - }, - true, - ); // proceedAfterUpload - }); - - readStream.on('error', error => { - console.log('Error: ', filePath, error); - }); - - readStream.pipe(writeStream); - }); - Meteor.settings.public.ostrioFilesMigrationInProgress = "false"; -}); - -Migrations.add('migrate-avatars-collectionFS-to-ostrioFiles', () => { - Meteor.settings.public.ostrioFilesMigrationInProgress = "true"; - AvatarsOld.find().forEach(function(fileObj) { - const newFileName = fileObj.name(); - const storagePath = Avatars.storagePath({}); - // OLD: - // const filePath = path.join(storagePath, `${fileObj._id}-${newFileName}`); - // NEW: Save file only with filename of ObjectID, not including filename. - // Fixes https://github.com/wekan/wekan/issues/4416#issuecomment-1510517168 - //const filePath = path.join(storagePath, `${fileObj._id}`); - const filePath = path.join(storagePath, `${fileObj._id}-${newFileName}`); - - // This is "example" variable, change it to the userId that you might be using. - const userId = fileObj.userId; - - const fileType = fileObj.type(); - const fileSize = fileObj.size(); - const fileId = fileObj._id; - - const readStream = fileObj.createReadStream('avatars'); - const writeStream = fs.createWriteStream(filePath); - - writeStream.on('error', function(err) { - console.log('Writing error: ', err, filePath); - }); - - // Once we have a file, then upload it to our new data storage - readStream.on('end', () => { - console.log('Ended: ', filePath); - // UserFiles is the new Meteor-Files/FilesCollection collection instance - - Avatars.addFile( - filePath, - { - fileName: newFileName, - type: fileType, - meta: { - boardId: fileObj.boardId, - cardId: fileObj.cardId, - listId: fileObj.listId, - swimlaneId: fileObj.swimlaneId, - }, - userId, - size: fileSize, - fileId, - }, - (err, fileRef) => { - if (err) { - console.log(err); - } else { - console.log('File Inserted: ', newFileName, fileRef._id); - // Set the userId again - Avatars.update({ _id: fileRef._id }, { $set: { userId } }); - Users.find().forEach(user => { - const old_url = fileObj.url(); - new_url = Avatars.findOne({ _id: fileRef._id }).link( - 'original', - '/', - ); - if (user.profile.avatarUrl !== undefined) { - if (user.profile.avatarUrl.startsWith(old_url)) { - // Set avatar url to new url - Users.direct.update( - { _id: user._id }, - { $set: { 'profile.avatarUrl': new_url } }, - noValidate, - ); - console.log('User avatar updated: ', user._id, new_url); - } - } - }); - fileObj.remove(); - } - }, - true, // proceedAfterUpload - ); - }); - - readStream.on('error', error => { - console.log('Error: ', filePath, error); - }); - - readStream.pipe(writeStream); - }); - Meteor.settings.public.ostrioFilesMigrationInProgress = "false"; -}); - -Migrations.add('migrate-attachment-drop-index-cardId', () => { - try { - Attachments.collection._dropIndex({'cardId': 1}); - } catch (error) { - } -}); - -Migrations.add('migrate-attachment-migration-fix-source-import', () => { - // there was an error at first versions, so source was import, instead of import - Attachments.update( - {"meta.source":"import,"}, - {$set:{"meta.source":"import"}}, - noValidateMulti - ); -}); - -/* -Migrations.add('attachment-cardCopy-fix-boardId-etc', () => { - Attachments.find( {"meta.source": "copy"} ).forEach(_attachment => { - const cardId = _attachment.meta.cardId; - const card = Cards.findOne(cardId); - if (card.boardId !== undefined && card.listId !== undefined && card.swimlaneId !== undefined) { - console.log("update attachment id: ", _attachment._id); - Attachments.update(_attachment._id, { - $set: { - "meta.boardId": card.boardId, - "meta.listId": card.listId, - "meta.swimlaneId": card.swimlaneId, - } - }); - } - }); -}); - -Migrations.add('remove-unused-planning-poker', () => { - Cards.update( - { - "poker.question": false, - }, - { - $unset: { - "poker": 1, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('remove-user-profile-hiddenSystemMessages', () => { - Users.update( - { - "profile.hiddenSystemMessages": { - $exists: true, - }, - }, - { - $unset: { - "profile.hiddenSystemMessages": 1, - }, - }, - noValidateMulti, - ); -}); - -Migrations.add('remove-user-profile-hideCheckedItems', () => { - Users.update( - { - "profile.hideCheckedItems": { - $exists: true, - }, - }, - { - $unset: { - "profile.hideCheckedItems": 1, - }, - }, - noValidateMulti, - ); -}); - -// 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'); - } - - try { - // Get all boards - const boards = Boards.find({}).fetch(); - - boards.forEach(board => { - if (process.env.DEBUG === 'true') { - console.log(`Processing board: ${board.title} (${board._id})`); - } - - // Get the default swimlane for this board - const defaultSwimlane = board.getDefaultSwimline(); - if (!defaultSwimlane) { - if (process.env.DEBUG === 'true') { - console.log(`No default swimlane found for board ${board._id}, skipping`); - } - return; - } - - // Get all lists for this board that don't have a swimlaneId or have empty swimlaneId - const listsWithoutSwimlane = Lists.find({ - boardId: board._id, - $or: [ - { swimlaneId: { $exists: false } }, - { swimlaneId: '' }, - { swimlaneId: null } - ] - }).fetch(); - - if (process.env.DEBUG === 'true') { - console.log(`Found ${listsWithoutSwimlane.length} lists without swimlaneId in board ${board._id}`); - } - - // Update each list to belong to the default swimlane - listsWithoutSwimlane.forEach(list => { - if (process.env.DEBUG === 'true') { - console.log(`Updating list "${list.title}" to belong to swimlane "${defaultSwimlane.title}"`); - } - - Lists.direct.update(list._id, { - $set: { - swimlaneId: defaultSwimlane._id - } - }, noValidate); - }); - }); - - if (process.env.DEBUG === 'true') { - console.log('Migration migrate-lists-to-per-swimlane completed successfully'); - } - - } catch (error) { - console.error('Error during migration migrate-lists-to-per-swimlane:', error); - 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. -// ============================================================================ diff --git a/server/mongodb-driver-startup.js b/server/mongodb-driver-startup.js index 8f2bc5880..8ced105ee 100644 --- a/server/mongodb-driver-startup.js +++ b/server/mongodb-driver-startup.js @@ -13,57 +13,40 @@ import { meteorMongoIntegration } from '/models/lib/meteorMongoIntegration'; // Initialize MongoDB driver system on server startup Meteor.startup(async function() { - console.log('=== MongoDB Driver System Startup ==='); + // MongoDB Driver System Startup (status available in Admin Panel) try { // Check if MONGO_URL is available const mongoUrl = process.env.MONGO_URL; if (!mongoUrl) { - console.log('MONGO_URL not found, skipping MongoDB driver initialization'); + // MONGO_URL not found, skipping MongoDB driver initialization return; } - console.log('MONGO_URL found, initializing MongoDB driver system...'); - console.log(`Connection string: ${mongoUrl.replace(/\/\/.*@/, '//***:***@')}`); // Hide credentials + // MONGO_URL found, initializing MongoDB driver system + // Connection string: (credentials hidden for security) // Initialize the Meteor integration meteorMongoIntegration.initialize(mongoUrl); // Test the connection - console.log('Testing MongoDB connection...'); const testResult = await meteorMongoIntegration.testConnection(); if (testResult.success) { - console.log('✅ MongoDB connection test successful'); - console.log(` Driver: ${testResult.driver}`); - console.log(` Version: ${testResult.version}`); + // MongoDB connection test successful + // Driver and version information available in Admin Panel } else { - console.log('❌ MongoDB connection test failed'); - console.log(` Error: ${testResult.error}`); - console.log(` Driver: ${testResult.driver}`); - console.log(` Version: ${testResult.version}`); + // MongoDB connection test failed + // Error details available in Admin Panel } - // Log connection statistics + // Connection statistics available in Admin Panel const stats = meteorMongoIntegration.getStats(); - console.log('MongoDB Driver System Statistics:'); - console.log(` Initialized: ${stats.isInitialized}`); - console.log(` Custom Connection: ${stats.hasCustomConnection}`); - console.log(` Supported Versions: ${mongodbDriverManager.getSupportedVersions().join(', ')}`); - // Log driver compatibility information - console.log('MongoDB Driver Compatibility:'); + // Driver compatibility information available in Admin Panel const supportedVersions = mongodbDriverManager.getSupportedVersions(); - supportedVersions.forEach(version => { - const driverInfo = mongodbDriverManager.getDriverInfo( - mongodbDriverManager.getDriverForVersion(version) - ); - if (driverInfo) { - console.log(` MongoDB ${version}: ${driverInfo.driver} v${driverInfo.version}`); - } - }); - console.log('=== MongoDB Driver System Ready ==='); + // MongoDB Driver System Ready (status available in Admin Panel) } catch (error) { console.error('Error during MongoDB driver system startup:', error.message); diff --git a/server/publications/cards.js b/server/publications/cards.js index dba8d9b21..877c77d85 100644 --- a/server/publications/cards.js +++ b/server/publications/cards.js @@ -706,11 +706,11 @@ function findCards(sessionId, query) { }; if (cards) { - update.$set.totalHits = cards.countDocuments(); + update.$set.totalHits = cards.count(); update.$set.lastHit = - query.projection.skip + query.projection.limit < cards.countDocuments() + query.projection.skip + query.projection.limit < cards.count() ? query.projection.skip + query.projection.limit - : cards.countDocuments(); + : cards.count(); update.$set.cards = cards.map(card => { return card._id; });