• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rwjdk / TrelloDotNet / 12705056480

10 Jan 2025 07:18AM UTC coverage: 58.22% (-0.2%) from 58.422%
12705056480

push

github

rwjdk
Merge branch 'main' of https://github.com/rwjdk/TrelloDotNet

900 of 1884 branches covered (47.77%)

Branch coverage included in aggregate %.

2344 of 3688 relevant lines covered (63.56%)

47.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

26.18
/src/TrelloDotNet/TrelloClient.Cards.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Linq;
4
using System.Text.Json;
5
using System.Threading;
6
using System.Threading.Tasks;
7
using TrelloDotNet.Control;
8
using TrelloDotNet.Model;
9
using TrelloDotNet.Model.Options;
10
using TrelloDotNet.Model.Options.GetCardOptions;
11
using TrelloDotNet.Model.Options.MoveCardToBoardOptions;
12
using TrelloDotNet.Model.Options.MoveCardToListOptions;
13

14
// ReSharper disable UnusedMember.Global
15

16
namespace TrelloDotNet
17
{
18
    public partial class TrelloClient
19
    {
20
        /// <summary>
21
        /// Add a Card
22
        /// </summary>
23
        /// <param name="card">The Card to Add</param>
24
        /// <param name="cancellationToken">Cancellation Token</param>
25
        /// <returns>The Added Card</returns>
26
        public async Task<Card> AddCardAsync(Card card, CancellationToken cancellationToken = default)
27
        {
28
            QueryParameter[] parameters = _queryParametersBuilder.GetViaQueryParameterAttributes(card);
59✔
29
            _queryParametersBuilder.AdjustForNamedPosition(parameters, card.NamedPosition);
59✔
30
            var result = await _apiRequestController.Post<Card>($"{UrlPaths.Cards}", cancellationToken, parameters);
59✔
31
            if (card.Cover != null)
59✔
32
            {
33
                return await AddCoverToCardAsync(result.Id, card.Cover, cancellationToken);
1✔
34
            }
35

36
            return result;
58✔
37
        }
59✔
38

39
        /// <summary>
40
        /// Archive (Close) a Card
41
        /// </summary>
42
        /// <param name="cardId">The id of card that should be archived</param>
43
        /// <param name="cancellationToken">Cancellation Token</param>
44
        /// <returns>The Archived Card</returns>
45
        public async Task<Card> ArchiveCardAsync(string cardId, CancellationToken cancellationToken = default)
46
        {
47
            return await _apiRequestController.Put<Card>($"{UrlPaths.Cards}/{cardId}", cancellationToken, new QueryParameter("closed", true));
1✔
48
        }
1✔
49

50
        /// <summary>
51
        /// ReOpen (Send back to board) a Card
52
        /// </summary>
53
        /// <param name="cardId">The id of card that should be reopened</param>
54
        /// <param name="cancellationToken">Cancellation Token</param>
55
        /// <returns>The ReOpened Card</returns>
56
        public async Task<Card> ReOpenCardAsync(string cardId, CancellationToken cancellationToken = default)
57
        {
58
            return await _apiRequestController.Put<Card>($"{UrlPaths.Cards}/{cardId}", cancellationToken, new QueryParameter("closed", false));
1✔
59
        }
1✔
60

61
        /// <summary>
62
        /// Update a Card
63
        /// </summary>
64
        /// <param name="cardWithChanges">The card with the changes</param>
65
        /// <param name="cancellationToken">Cancellation Token</param>
66
        /// <returns>The Updated Card</returns>
67
        public async Task<Card> UpdateCardAsync(Card cardWithChanges, CancellationToken cancellationToken = default)
68
        {
69
            var parameters = _queryParametersBuilder.GetViaQueryParameterAttributes(cardWithChanges).ToList();
9✔
70
            CardCover cardCover = cardWithChanges.Cover;
9✔
71
            _queryParametersBuilder.AdjustForNamedPosition(parameters, cardWithChanges.NamedPosition);
9✔
72
            var payload = GeneratePayloadForCoverUpdate(cardCover, parameters);
9✔
73
            return await _apiRequestController.PutWithJsonPayload<Card>($"{UrlPaths.Cards}/{cardWithChanges.Id}", cancellationToken, payload, parameters.ToArray());
9✔
74
        }
9✔
75

76
        /// <summary>
77
        /// Archive all cards on in a List
78
        /// </summary>
79
        /// <param name="listId">The id of the List that should have its cards archived</param>
80
        /// <param name="cancellationToken">Cancellation Token</param>
81
        public async Task ArchiveAllCardsInListAsync(string listId, CancellationToken cancellationToken = default)
82
        {
83
            await _apiRequestController.Post<List>($"{UrlPaths.Lists}/{listId}/archiveAllCards", cancellationToken);
1✔
84
        }
1✔
85

86
        /// <summary>
87
        /// Move all cards of a list to another list
88
        /// </summary>
89
        /// <param name="currentListId">The id of the List that should have its cards moved</param>
90
        /// <param name="newListId">The id of the new List that should receive the cards</param>
91
        /// <param name="cancellationToken">Cancellation Token</param>
92
        public async Task MoveAllCardsInListAsync(string currentListId, string newListId, CancellationToken cancellationToken = default)
93
        {
94
            var newList = await GetListAsync(newListId, cancellationToken); //Get the new list's BoardId so the user do not need to provide it.
1✔
95
            await _apiRequestController.Post($"{UrlPaths.Lists}/{currentListId}/moveAllCards", cancellationToken,
1✔
96
                0,
1✔
97
                new QueryParameter("idBoard", newList.BoardId),
1✔
98
                new QueryParameter("idList", newListId)
1✔
99
            );
1✔
100
        }
1✔
101

102
        /// <summary>
103
        /// Delete a Card (WARNING: THERE IS NO WAY GOING BACK!!!). Alternative use ArchiveCardAsync() for non-permanency
104
        /// </summary>
105
        /// <param name="cardId">The id of the Card to Delete</param>
106
        /// <param name="cancellationToken">Cancellation Token</param>
107
        public async Task DeleteCardAsync(string cardId, CancellationToken cancellationToken = default)
108
        {
109
            await _apiRequestController.Delete($"{UrlPaths.Cards}/{cardId}", cancellationToken, 0);
1✔
110
        }
1✔
111

112
        /// <summary>
113
        /// Get a Card by its Id
114
        /// </summary>
115
        /// <param name="cardId">Id of the Card</param>
116
        /// <param name="cancellationToken">Cancellation Token</param>
117
        /// <returns>The Card</returns>
118
        public async Task<Card> GetCardAsync(string cardId, CancellationToken cancellationToken = default)
119
        {
120
            return await _apiRequestController.Get<Card>(GetUrlBuilder.GetCard(cardId), cancellationToken,
49✔
121
#pragma warning disable CS0618 // Type or member is obsolete
49✔
122
                new QueryParameter("customFieldItems", Options.IncludeCustomFieldsInCardGetMethods),
49✔
123
                new QueryParameter("attachments", Options.IncludeAttachmentsInCardGetMethods)
49✔
124
#pragma warning restore CS0618 // Type or member is obsolete
49✔
125
            );
49✔
126
        }
49✔
127

128
        /// <summary>
129
        /// Get a Card by its Id
130
        /// </summary>
131
        /// <param name="cardId">Id of the Card</param>
132
        /// <param name="options">Options on how and what should be included on the cards (Example only a few fields to increase performance or more nested data to avoid more API calls)</param>
133
        /// <param name="cancellationToken">Cancellation Token</param>
134
        /// <returns>The Card</returns>
135
        public async Task<Card> GetCardAsync(string cardId, GetCardOptions options, CancellationToken cancellationToken = default)
136
        {
137
            return await _apiRequestController.Get<Card>(GetUrlBuilder.GetCard(cardId), cancellationToken, options.GetParameters(false));
60✔
138
        }
60✔
139

140
        /// <summary>
141
        /// Get all open cards on un-archived lists on a board
142
        /// </summary>
143
        /// <param name="boardId">Id of the Board (in its long or short version)</param>
144
        /// <param name="cancellationToken">CancellationToken</param>
145
        /// <returns>List of Cards</returns>
146
        public async Task<List<Card>> GetCardsOnBoardAsync(string boardId, CancellationToken cancellationToken = default)
147
        {
148
            return await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsOnBoard(boardId), cancellationToken,
6✔
149
#pragma warning disable CS0618 // Type or member is obsolete
6✔
150
                new QueryParameter("customFieldItems", Options.IncludeCustomFieldsInCardGetMethods),
6✔
151
                new QueryParameter("attachments", Options.IncludeAttachmentsInCardGetMethods)
6✔
152
#pragma warning restore CS0618 // Type or member is obsolete
6✔
153
            );
6✔
154
        }
6✔
155

156
        /// <summary>
157
        /// Get all open cards on un-archived lists on a board
158
        /// </summary>
159
        /// <param name="boardId">Id of the Board (in its long or short version)</param>
160
        /// <param name="options">Options on how and what should be included on the cards (Example only a few fields to increase performance or more nested data to avoid more API calls)</param>
161
        /// <param name="cancellationToken">CancellationToken</param>
162
        /// <returns>List of Cards</returns>
163
        public async Task<List<Card>> GetCardsOnBoardAsync(string boardId, GetCardOptions options, CancellationToken cancellationToken = default)
164
        {
165
            if (options.IncludeList)
×
166
            {
167
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idList"))
×
168
                {
169
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
170
                    {
×
171
                        "idList"
×
172
                    }).ToArray();
×
173
                }
174
            }
175

176
            if (options.IncludeBoard)
×
177
            {
178
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idBoard"))
×
179
                {
180
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
181
                    {
×
182
                        "idBoard"
×
183
                    }).ToArray();
×
184
                }
185
            }
186

187
            var cards = await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsOnBoard(boardId), cancellationToken, options.GetParameters(true));
×
188
            if (options.IncludeList)
×
189
            {
190
                var lists = await GetListsOnBoardAsync(boardId, cancellationToken);
×
191
                foreach (Card card in cards)
×
192
                {
193
                    card.List = lists.FirstOrDefault(x => x.Id == card.ListId);
×
194
                }
195
            }
196

197
            if (options.IncludeBoard)
×
198
            {
199
                var board = await GetBoardAsync(boardId, cancellationToken);
×
200
                foreach (Card card in cards)
×
201
                {
202
                    card.Board = board;
×
203
                }
204
            }
205

206
            return cards;
×
207
        }
×
208

209
        /// <summary>
210
        /// Get all open cards on a specific list
211
        /// </summary>
212
        /// <param name="listId">Id of the List</param>
213
        /// <param name="cancellationToken">Cancellation Token</param>
214
        /// <returns>List of Cards</returns>
215
        public async Task<List<Card>> GetCardsInListAsync(string listId, CancellationToken cancellationToken = default)
216
        {
217
            return await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsInList(listId), cancellationToken,
9✔
218
#pragma warning disable CS0618 // Type or member is obsolete
9✔
219
                new QueryParameter("customFieldItems", Options.IncludeCustomFieldsInCardGetMethods),
9✔
220
                new QueryParameter("attachments", Options.IncludeAttachmentsInCardGetMethods));
9✔
221
#pragma warning restore CS0618 // Type or member is obsolete
222
        }
9✔
223

224
        /// <summary>
225
        /// Get all open cards on a specific list
226
        /// </summary>
227
        /// <param name="listId">Id of the List</param>
228
        /// <param name="options">Options on how and what should be included on the cards (Example only a few fields to increase performance or more nested data to avoid more API calls)</param>
229
        /// <param name="cancellationToken">Cancellation Token</param>
230
        /// <returns>List of Cards</returns>
231
        public async Task<List<Card>> GetCardsInListAsync(string listId, GetCardOptions options, CancellationToken cancellationToken = default)
232
        {
233
            if (options.IncludeBoard)
×
234
            {
235
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idBoard"))
×
236
                {
237
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
238
                    {
×
239
                        "idBoard"
×
240
                    }).ToArray();
×
241
                }
242
            }
243

244
            var cards = await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsInList(listId), cancellationToken, options.GetParameters(true));
×
245
            if (options.IncludeList)
×
246
            {
247
                var list = await GetListAsync(listId, cancellationToken);
×
248
                foreach (Card card in cards)
×
249
                {
250
                    card.ListId = listId;
×
251
                    card.List = list;
×
252
                }
253
            }
254

255
            if (options.IncludeBoard && cards.Count > 0)
×
256
            {
257
                var board = await GetBoardAsync(cards[0].BoardId, cancellationToken);
×
258
                foreach (Card card in cards)
×
259
                {
260
                    card.Board = board;
×
261
                }
262
            }
263

264

265
            return cards;
×
266
        }
×
267

268
        /// <summary>
269
        /// Get the cards on board based on their status regardless if they are on archived lists
270
        /// </summary>
271
        /// <param name="boardId">Id of the Board (in its long or short version)</param>
272
        /// <param name="filter">The Selected Filter</param>
273
        /// <param name="cancellationToken">Cancellation Token</param>
274
        /// <returns>List of Cards</returns>
275
        public async Task<List<Card>> GetCardsOnBoardFilteredAsync(string boardId, CardsFilter filter, CancellationToken cancellationToken = default)
276
        {
277
            return await _apiRequestController.Get<List<Card>>($"{GetUrlBuilder.GetCardsOnBoard(boardId)}/{filter.GetJsonPropertyName()}", cancellationToken,
1✔
278
#pragma warning disable CS0618 // Type or member is obsolete
1✔
279
                new QueryParameter("customFieldItems", Options.IncludeCustomFieldsInCardGetMethods),
1✔
280
                new QueryParameter("attachments", Options.IncludeAttachmentsInCardGetMethods));
1✔
281
#pragma warning restore CS0618 // Type or member is obsolete
282
        }
1✔
283

284
        /// <summary>
285
        /// Get the cards on board based on their status regardless if they are on archived lists
286
        /// </summary>
287
        /// <param name="boardId">Id of the Board (in its long or short version)</param>
288
        /// <param name="filter">The Selected Filter</param>
289
        /// <param name="options">Options on how and what should be included on the cards (Example only a few fields to increase performance or more nested data to avoid more API calls)</param> 
290
        /// <param name="cancellationToken">Cancellation Token</param>
291
        /// <returns>List of Cards</returns>
292
        public async Task<List<Card>> GetCardsOnBoardFilteredAsync(string boardId, CardsFilter filter, GetCardOptions options, CancellationToken cancellationToken = default)
293
        {
294
            if (options.IncludeList)
×
295
            {
296
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idList"))
×
297
                {
298
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
299
                    {
×
300
                        "idList"
×
301
                    }).ToArray();
×
302
                }
303
            }
304

305
            var cards = await _apiRequestController.Get<List<Card>>($"{GetUrlBuilder.GetCardsOnBoard(boardId)}/{filter.GetJsonPropertyName()}", cancellationToken, options.GetParameters(true));
×
306
            if (options.IncludeList)
×
307
            {
308
                List<List> lists;
309
                switch (filter)
310
                {
311
                    case CardsFilter.All:
312
                    case CardsFilter.Closed:
313
                    case CardsFilter.Visible:
314
                        lists = await GetListsOnBoardFilteredAsync(boardId, ListFilter.All, cancellationToken);
×
315
                        break;
×
316
                    case CardsFilter.Open:
317
                        lists = await GetListsOnBoardAsync(boardId, cancellationToken);
×
318
                        break;
×
319
                    default:
320
                        throw new ArgumentOutOfRangeException(nameof(filter), filter, null);
×
321
                }
322

323
                foreach (Card card in cards)
×
324
                {
325
                    card.List = lists.FirstOrDefault(x => x.Id == card.ListId);
×
326
                }
327
            }
328

329
            return cards;
×
330
        }
×
331

332
        /// <summary>
333
        /// Get all Cards a Member is on (across multiple boards)
334
        /// </summary>
335
        /// <param name="memberId">Id of Member</param>
336
        /// <param name="cancellationToken">Cancellation Token</param>
337
        /// <returns></returns>
338
        public async Task<List<Card>> GetCardsForMemberAsync(string memberId, CancellationToken cancellationToken = default)
339
        {
340
            return await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsForMember(memberId), cancellationToken);
1✔
341
        }
1✔
342

343
        /// <summary>
344
        /// Get all Cards a Member is on (across multiple boards)
345
        /// </summary>
346
        /// <param name="memberId">Id of Member</param>
347
        /// <param name="options">Options on how and what should be included on the cards (Example only a few fields to increase performance or more nested data to avoid more API calls)</param>
348
        /// <param name="cancellationToken">Cancellation Token</param>
349
        /// <returns></returns>
350
        public async Task<List<Card>> GetCardsForMemberAsync(string memberId, GetCardOptions options, CancellationToken cancellationToken = default)
351
        {
352
            if (options.IncludeList)
×
353
            {
354
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idList"))
×
355
                {
356
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
357
                    {
×
358
                        "idList"
×
359
                    }).ToArray();
×
360
                }
361

362
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idBoard"))
×
363
                {
364
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
365
                    {
×
366
                        "idBoard"
×
367
                    }).ToArray();
×
368
                }
369
            }
370

371
            if (options.IncludeBoard)
×
372
            {
373
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idBoard"))
×
374
                {
375
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
376
                    {
×
377
                        "idBoard"
×
378
                    }).ToArray();
×
379
                }
380
            }
381

382
            var cards = await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsForMember(memberId), cancellationToken, options.GetParameters(true));
×
383
            if (options.IncludeList)
×
384
            {
385
                var boardsToGetListsFor = cards.Select(x => x.BoardId).Distinct().ToArray();
×
386
                List<List> lists = new List<List>();
×
387
                foreach (var boardId in boardsToGetListsFor)
×
388
                {
389
                    lists.AddRange(await GetListsOnBoardAsync(boardId, cancellationToken));
×
390
                }
391

392
                foreach (Card card in cards)
×
393
                {
394
                    card.List = lists.FirstOrDefault(x => x.Id == card.ListId);
×
395
                }
396
            }
×
397

398
            if (options.IncludeBoard)
×
399
            {
400
                var boardIds = cards.Select(x => x.BoardId).Distinct().ToList();
×
401
                var boards = await GetBoardsAsync(boardIds, cancellationToken);
×
402
                foreach (Card card in cards)
×
403
                {
404
                    card.Board = boards.FirstOrDefault(x => x.Id == card.BoardId);
×
405
                }
406
            }
407

408
            return cards;
×
409
        }
×
410

411
        /// <summary>
412
        /// Set Due Date on a card
413
        /// </summary>
414
        /// <param name="cardId">Id of the Card</param>
415
        /// <param name="dueDate">The Due Date (In UTC Time)</param>
416
        /// <param name="dueComplete">If Due is complete</param>
417
        /// <param name="cancellationToken">Cancellation Token</param>
418
        public async Task<Card> SetDueDateOnCardAsync(string cardId, DateTimeOffset dueDate, bool dueComplete = false, CancellationToken cancellationToken = default)
419
        {
420
            return await UpdateCardAsync(cardId, new List<QueryParameter>
1✔
421
            {
1✔
422
                new QueryParameter(CardFieldsType.Due.GetJsonPropertyName(), dueDate),
1✔
423
                new QueryParameter(CardFieldsType.DueComplete.GetJsonPropertyName(), dueComplete)
1✔
424
            }, cancellationToken);
1✔
425
        }
1✔
426

427
        /// <summary>
428
        /// Set Due Date on a card
429
        /// </summary>
430
        /// <param name="cardId">Id of the Card</param>
431
        /// <param name="startDate">The Start Date (In UTC Time)</param>
432
        /// <param name="cancellationToken">Cancellation Token</param>
433
        public async Task<Card> SetStartDateOnCardAsync(string cardId, DateTimeOffset startDate, CancellationToken cancellationToken = default)
434
        {
435
            return await UpdateCardAsync(cardId, new List<QueryParameter>
1✔
436
            {
1✔
437
                new QueryParameter(CardFieldsType.Start.GetJsonPropertyName(), startDate)
1✔
438
            }, cancellationToken);
1✔
439
        }
1✔
440

441
        /// <summary>
442
        /// Set Start and Due Date on a card
443
        /// </summary>
444
        /// <param name="cardId">Id of the Card</param>
445
        /// <param name="startDate">The Start Date (In UTC Time)</param>
446
        /// <param name="dueDate">The Due Date (In UTC Time)</param>
447
        /// <param name="dueComplete">If Due is complete</param>
448
        /// <param name="cancellationToken">Cancellation Token</param> 
449
        public async Task<Card> SetStartDateAndDueDateOnCardAsync(string cardId, DateTimeOffset startDate, DateTimeOffset dueDate, bool dueComplete = false, CancellationToken cancellationToken = default)
450
        {
451
            return await UpdateCardAsync(cardId, new List<QueryParameter>
1✔
452
            {
1✔
453
                new QueryParameter(CardFieldsType.Start.GetJsonPropertyName(), startDate),
1✔
454
                new QueryParameter(CardFieldsType.Due.GetJsonPropertyName(), dueDate),
1✔
455
                new QueryParameter(CardFieldsType.DueComplete.GetJsonPropertyName(), dueComplete),
1✔
456
            }, cancellationToken);
1✔
457
        }
1✔
458

459
        /// <summary>
460
        /// Move a Card to a new list on the same board
461
        /// </summary>
462
        /// <param name="cardId">Id of the Card</param>
463
        /// <param name="newListId">Id of the List you wish to move it to</param>
464
        /// <param name="cancellationToken">Cancellation Token</param>
465
        /// <returns></returns>
466
        public async Task<Card> MoveCardToListAsync(string cardId, string newListId, CancellationToken cancellationToken = default)
467
        {
468
            return await UpdateCardAsync(cardId, new List<QueryParameter>
1✔
469
            {
1✔
470
                new QueryParameter(CardFieldsType.ListId.GetJsonPropertyName(), newListId)
1✔
471
            }, cancellationToken);
1✔
472
        }
1✔
473

474
        /// <summary>
475
        /// Move a Card to a new list on the same board
476
        /// </summary>
477
        /// <param name="cardId">Id of the Card</param>
478
        /// <param name="newListId">Id of the List you wish to move it to</param>
479
        /// <param name="options">Additional optional Options for the Move</param>
480
        /// <param name="cancellationToken">Cancellation Token</param>
481
        /// <returns></returns>
482
        public async Task<Card> MoveCardToListAsync(string cardId, string newListId, MoveCardToListOptions options, CancellationToken cancellationToken = default)
483
        {
484
            var parameters = new List<QueryParameter> { new QueryParameter(CardFieldsType.ListId.GetJsonPropertyName(), newListId) };
×
485
            if (options.NamedPositionOnNewList.HasValue)
×
486
            {
487
                switch (options.NamedPositionOnNewList.Value)
×
488
                {
489
                    case NamedPosition.Top:
490
                        parameters.Add(new QueryParameter("pos", "top"));
×
491
                        break;
×
492
                    case NamedPosition.Bottom:
493
                        parameters.Add(new QueryParameter("pos", "bottom"));
×
494
                        break;
×
495
                }
496
            }
497
            else if (options.PositionOnNewList.HasValue)
×
498
            {
499
                parameters.Add(new QueryParameter(CardFieldsType.Position.GetJsonPropertyName(), options.PositionOnNewList.Value));
×
500
            }
501

502
            return await UpdateCardAsync(cardId, parameters, cancellationToken);
×
503
        }
×
504

505
        /// <summary>
506
        /// Update one or more specific fields on a card (compared to a full update of all fields with UpdateCard)
507
        /// </summary>
508
        /// <param name="cardId">Id of the Card</param>
509
        /// <param name="parameters">The Specific Parameters to set</param>
510
        /// <param name="cancellationToken">CancellationToken</param>
511
        public async Task<Card> UpdateCardAsync(string cardId, List<QueryParameter> parameters, CancellationToken cancellationToken = default)
512
        {
513
            QueryParameter coverParameter = parameters.FirstOrDefault(x => x.Name == "cover");
78✔
514
            if (coverParameter != null && !string.IsNullOrWhiteSpace(coverParameter.GetRawStringValue()))
33✔
515
            {
516
                parameters.Remove(coverParameter);
1✔
517
                CardCover cover = JsonSerializer.Deserialize<CardCover>(coverParameter.GetRawStringValue());
1✔
518
                var payload = GeneratePayloadForCoverUpdate(cover, parameters);
1✔
519
                return await _apiRequestController.PutWithJsonPayload<Card>($"{UrlPaths.Cards}/{cardId}", cancellationToken, payload, parameters.ToArray());
1✔
520
            }
521

522
            //Special Cover Card
523
            return await _apiRequestController.Put<Card>($"{UrlPaths.Cards}/{cardId}", cancellationToken, parameters.ToArray());
32✔
524
        }
33✔
525

526
        /// <summary>
527
        /// Update one or more specific fields on a card (compared to a full update of all fields with UpdateCard)
528
        /// </summary>
529
        /// <param name="cardId">Id of the Card</param>
530
        /// <param name="valuesToUpdate">The Specific values to set</param>
531
        /// <param name="cancellationToken">CancellationToken</param>
532
        public async Task<Card> UpdateCardAsync(string cardId, List<CardUpdate> valuesToUpdate, CancellationToken cancellationToken = default)
533
        {
534
            return await UpdateCardAsync(cardId, valuesToUpdate.Select(x => x.ToQueryParameter()).ToList(), cancellationToken);
×
535
        }
×
536

537
        /// <summary>
538
        /// Move the Card to the top of its current list
539
        /// </summary>
540
        /// <param name="cardId">Id of the Card</param>
541
        /// <param name="cancellationToken">Cancellation Token</param>
542
        /// <returns>The Card</returns>
543
        public async Task<Card> MoveCardToTopOfCurrentListAsync(string cardId, CancellationToken cancellationToken = default)
544
        {
545
            return await UpdateCardAsync(cardId, new List<QueryParameter>
×
546
            {
×
547
                new QueryParameter(CardFieldsType.Position.GetJsonPropertyName(), "top")
×
548
            }, cancellationToken);
×
549
        }
×
550

551
        /// <summary>
552
        /// Move the Card to the bottom of its current list
553
        /// </summary>
554
        /// <param name="cardId">Id of the Card</param>
555
        /// <param name="cancellationToken">Cancellation Token</param>
556
        /// <returns>The Card</returns>
557
        public async Task<Card> MoveCardToBottomOfCurrentListAsync(string cardId, CancellationToken cancellationToken = default)
558
        {
559
            return await UpdateCardAsync(cardId, new List<QueryParameter>
×
560
            {
×
561
                new QueryParameter(CardFieldsType.Position.GetJsonPropertyName(), "bottom")
×
562
            }, cancellationToken);
×
563
        }
×
564

565
        /// <summary>
566
        /// Move a Card to another board
567
        /// </summary>
568
        /// <param name="cardId">The Id of the Card to Move</param>
569
        /// <param name="newBoardId">The ID of the New Board that the card should be moved to</param>
570
        /// <param name="options">Additional Options for the move like what list the card should end up on the new board and what happens to labels and members</param>
571
        /// <param name="cancellationToken">Cancellation Token</param>
572
        /// <returns></returns>
573
        public async Task<Card> MoveCardToBoard(string cardId, string newBoardId, MoveCardToBoardOptions options, CancellationToken cancellationToken = default)
574
        {
575
            if (options == null)
×
576
            {
577
                throw new ArgumentNullException(nameof(options), "You need to pass an options object to confirm the various options that are invovled with moving a card between boards");
×
578
            }
579

580
            List<QueryParameter> parameters = new List<QueryParameter> { new QueryParameter(CardFieldsType.BoardId.GetJsonPropertyName(), newBoardId) };
×
581
            var newListId = options.NewListId;
×
582
            if (string.IsNullOrWhiteSpace(newListId))
×
583
            {
584
                //No list specified, so we need to find the first list on the board
585
                newListId = (await GetListsOnBoardAsync(newBoardId, cancellationToken)).OrderBy(x => x.Position).FirstOrDefault()?.Id;
×
586
            }
587

588
            parameters.Add(new QueryParameter(CardFieldsType.ListId.GetJsonPropertyName(), newListId));
×
589

590
            if (options.NamedPositionOnNewList.HasValue)
×
591
            {
592
                switch (options.NamedPositionOnNewList.Value)
×
593
                {
594
                    case NamedPosition.Top:
595
                        parameters.Add(new QueryParameter("pos", "top"));
×
596
                        break;
×
597
                    case NamedPosition.Bottom:
598
                        parameters.Add(new QueryParameter("pos", "bottom"));
×
599
                        break;
×
600
                }
601
            }
602
            else if (options.PositionOnNewList.HasValue)
×
603
            {
604
                parameters.Add(new QueryParameter(CardFieldsType.Position.GetJsonPropertyName(), options.PositionOnNewList.Value));
×
605
            }
606

607
            Card card = await GetCardAsync(cardId, new GetCardOptions
×
608
            {
×
609
                CardFields = new CardFields(CardFieldsType.MemberIds, CardFieldsType.LabelIds, CardFieldsType.Labels)
×
610
            }, cancellationToken);
×
611

612
            switch (options.MemberOptions)
×
613
            {
614
                case MoveCardToBoardOptionsMemberOptions.KeepMembersAlsoOnNewBoardAndRemoveRest:
615
                    var existingMemberIdsOnNewBoard = (await GetMembersOfBoardAsync(newBoardId, cancellationToken)).Select(x => x.Id);
×
616
                    card.MemberIds = card.MemberIds.Intersect(existingMemberIdsOnNewBoard).ToList();
×
617
                    break;
×
618
                case MoveCardToBoardOptionsMemberOptions.RemoveAllMembersOnCard:
619
                    card.MemberIds.Clear();
×
620
                    break;
×
621
                default:
622
                    throw new ArgumentOutOfRangeException();
×
623
            }
624

625
            if (card.LabelIds.Any())
×
626
            {
627
                card.LabelIds.Clear();
×
628
                switch (options.LabelOptions)
×
629
                {
630
                    case MoveCardToBoardOptionsLabelOptions.MigrateToLabelsOfSameNameAndColorAndCreateMissing:
631
                    {
632
                        var existingLabels = await GetLabelsOfBoardAsync(newBoardId, cancellationToken);
×
633
                        foreach (Label cardLabel in card.Labels)
×
634
                        {
635
                            Label existingLabel = existingLabels.FirstOrDefault(x => x.Name == cardLabel.Name && x.Color == cardLabel.Color);
×
636
                            if (existingLabel != null)
×
637
                            {
638
                                card.LabelIds.Add(existingLabel.Id);
×
639
                            }
640
                            else
641
                            {
642
                                //Label need to be added
643
                                Label newLabel = await AddLabelAsync(new Label(newBoardId, cardLabel.Name, cardLabel.Color), cancellationToken);
×
644
                                card.LabelIds.Add(newLabel.Id);
×
645
                            }
646
                        }
647

648
                        break;
×
649
                    }
650
                    case MoveCardToBoardOptionsLabelOptions.MigrateToLabelsOfSameNameAndColorAndRemoveMissing:
651
                    {
652
                        var existingLabels = await GetLabelsOfBoardAsync(newBoardId, cancellationToken);
×
653
                        foreach (Label cardLabel in card.Labels)
×
654
                        {
655
                            Label existingLabel = existingLabels.FirstOrDefault(x => x.Name == cardLabel.Name && x.Color == cardLabel.Color);
×
656
                            if (existingLabel != null)
×
657
                            {
658
                                card.LabelIds.Add(existingLabel.Id);
×
659
                            }
660
                        }
661

662
                        break;
663
                    }
664
                    case MoveCardToBoardOptionsLabelOptions.MigrateToLabelsOfSameNameAndCreateMissing:
665
                    {
666
                        var existingLabels = await GetLabelsOfBoardAsync(newBoardId, cancellationToken);
×
667
                        foreach (Label cardLabel in card.Labels)
×
668
                        {
669
                            Label existingLabel = existingLabels.FirstOrDefault(x => x.Name == cardLabel.Name);
×
670
                            if (existingLabel != null)
×
671
                            {
672
                                card.LabelIds.Add(existingLabel.Id);
×
673
                            }
674
                            else
675
                            {
676
                                //Label need to be added
677
                                Label newLabel = await AddLabelAsync(new Label(newBoardId, cardLabel.Name, cardLabel.Color), cancellationToken);
×
678
                                card.LabelIds.Add(newLabel.Id);
×
679
                            }
680
                        }
681

682
                        break;
×
683
                    }
684
                    case MoveCardToBoardOptionsLabelOptions.MigrateToLabelsOfSameNameAndRemoveMissing:
685
                    {
686
                        var existingLabels = await GetLabelsOfBoardAsync(newBoardId, cancellationToken);
×
687
                        foreach (Label cardLabel in card.Labels)
×
688
                        {
689
                            Label existingLabel = existingLabels.FirstOrDefault(x => x.Name == cardLabel.Name);
×
690
                            if (existingLabel != null)
×
691
                            {
692
                                card.LabelIds.Add(existingLabel.Id);
×
693
                            }
694
                        }
695

696
                        break;
697
                    }
698
                    case MoveCardToBoardOptionsLabelOptions.RemoveAllLabelsOnCard:
699
                        //No more Work needed
700
                        break;
701
                    default:
702
                        throw new ArgumentOutOfRangeException();
×
703
                }
704
            }
705

706
            parameters.Add(new QueryParameter(CardFieldsType.LabelIds.GetJsonPropertyName(), card.LabelIds));
×
707
            parameters.Add(new QueryParameter(CardFieldsType.MemberIds.GetJsonPropertyName(), card.MemberIds));
×
708

709
            if (options.RemoveDueDate)
×
710
            {
711
                parameters.Add(new QueryParameter(CardFieldsType.Due.GetJsonPropertyName(), (DateTimeOffset?)null));
×
712
            }
713

714
            if (options.RemoveStartDate)
×
715
            {
716
                parameters.Add(new QueryParameter(CardFieldsType.Start.GetJsonPropertyName(), (DateTimeOffset?)null));
×
717
            }
718

719
            return await UpdateCardAsync(cardId, parameters, cancellationToken);
×
720
        }
×
721

722
        private static string GeneratePayloadForCoverUpdate(CardCover cardCover, List<QueryParameter> parameters)
723
        {
724
            //Special code for Cover
725
            string payload = string.Empty;
10✔
726
            if (cardCover == null)
10✔
727
            {
728
                //Remove cover
729
                parameters.Add(new QueryParameter("cover", ""));
1✔
730
            }
731
            else
732
            {
733
                cardCover.PrepareForAddUpdate();
9✔
734
                if (cardCover.Color != null || cardCover.BackgroundImageId != null)
9✔
735
                {
736
                    QueryParameter queryParameter = parameters.FirstOrDefault(x => x.Name == "idAttachmentCover");
29✔
737
                    if (queryParameter != null)
3✔
738
                    {
739
                        parameters.Remove(queryParameter); //This parameter can't be there while a cover is added
2✔
740
                    }
741
                }
742

743
                payload = $"{{\"cover\":{JsonSerializer.Serialize(cardCover)}}}";
9✔
744
            }
745

746
            return payload;
10✔
747
        }
748
    }
749
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc