Fix async/await in copy/move card operations

The mapCustomFieldsToBoard() method is async but was being called without
await in copy() and move() methods. This caused a Promise to be assigned
to customFields instead of the actual array, failing MongoDB schema
validation on cross-board operations.

Changes:
- Make copy() method async and await mapCustomFieldsToBoard()
- Add await in move() for mapCustomFieldsToBoard()
- Make copyCard() server method async and await card.copy()
- Add null check in mapCustomFieldsToBoard() for cards without custom fields
- Update client to use Meteor.callAsync for server-only copyCard method

Fixes #6105
This commit is contained in:
Harry Adel 2026-01-31 19:45:43 +02:00
parent 88b35a6415
commit 14de981ac3
2 changed files with 11 additions and 7 deletions

View file

@ -1050,7 +1050,7 @@ Template.editCardAssignerForm.events({
const title = textarea.val().trim();
if (title) {
const newCardId = Meteor.call('copyCard', card._id, options.boardId, options.swimlaneId, options.listId, true, {title: title});
const newCardId = await Meteor.callAsync('copyCard', card._id, options.boardId, options.swimlaneId, options.listId, true, {title: title});
// Position the copied card
if (newCardId) {
@ -1145,7 +1145,7 @@ Template.editCardAssignerForm.events({
if (title) {
const titleList = JSON.parse(title);
for (const obj of titleList) {
const newCardId = Meteor.call('copyCard', card._id, options.boardId, options.swimlaneId, options.listId, false, {title: obj.title, description: obj.description});
const newCardId = await Meteor.callAsync('copyCard', card._id, options.boardId, options.swimlaneId, options.listId, false, {title: obj.title, description: obj.description});
// Position the copied card
if (newCardId) {

View file

@ -573,6 +573,10 @@ Cards.helpers({
},
async mapCustomFieldsToBoard(boardId) {
// Guard against undefined/null customFields
if (!this.customFields || !Array.isArray(this.customFields)) {
return [];
}
// Map custom fields to new board
const result = [];
for (const cf of this.customFields) {
@ -603,7 +607,7 @@ Cards.helpers({
},
copy(boardId, swimlaneId, listId) {
async copy(boardId, swimlaneId, listId) {
const oldId = this._id;
const oldCard = ReactiveCache.getCard(oldId);
@ -633,7 +637,7 @@ Cards.helpers({
delete this.labelIds;
this.labelIds = newCardLabels;
this.customFields = this.mapCustomFieldsToBoard(newBoard._id);
this.customFields = await this.mapCustomFieldsToBoard(newBoard._id);
}
delete this._id;
@ -2097,7 +2101,7 @@ Cards.helpers({
cardNumber: newCardNumber
});
mutatedFields.customFields = this.mapCustomFieldsToBoard(newBoard._id);
mutatedFields.customFields = await this.mapCustomFieldsToBoard(newBoard._id);
}
await Cards.updateAsync(this._id, { $set: mutatedFields });
@ -3077,7 +3081,7 @@ if (Meteor.isServer) {
* @param mergeCardValues this values into the copied card
* @return the new card id
*/
copyCard(cardId, boardId, swimlaneId, listId, insertAtTop, mergeCardValues) {
async copyCard(cardId, boardId, swimlaneId, listId, insertAtTop, mergeCardValues) {
check(cardId, String);
check(boardId, String);
check(swimlaneId, String);
@ -3096,7 +3100,7 @@ if (Meteor.isServer) {
card.sort = sort + 1;
}
const ret = card.copy(boardId, swimlaneId, listId);
const ret = await card.copy(boardId, swimlaneId, listId);
return ret;
},
});