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

rwjdk / TrelloDotNet / 13163656973

05 Feb 2025 06:02PM UTC coverage: 45.486% (-2.7%) from 48.162%
13163656973

push

github

rwjdk
WIP

942 of 2803 branches covered (33.61%)

Branch coverage included in aggregate %.

1 of 456 new or added lines in 6 files covered. (0.22%)

11 existing lines in 4 files now uncovered.

2444 of 4641 relevant lines covered (52.66%)

42.53 hits per line

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

26.56
/src/TrelloDotNet/TrelloClient.Cards.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Globalization;
4
using System.Linq;
5
using System.Text.Json;
6
using System.Threading;
7
using System.Threading.Tasks;
8
using TrelloDotNet.Control;
9
using TrelloDotNet.Model;
10
using TrelloDotNet.Model.Options;
11
using TrelloDotNet.Model.Options.AddCardFromTemplateOptions;
12
using TrelloDotNet.Model.Options.AddCardOptions;
13
using TrelloDotNet.Model.Options.AddCardToInboxOptions;
14
using TrelloDotNet.Model.Options.CopyCardOptions;
15
using TrelloDotNet.Model.Options.GetCardOptions;
16
using TrelloDotNet.Model.Options.GetInboxCardOptions;
17
using TrelloDotNet.Model.Options.MirrorCardOptions;
18
using TrelloDotNet.Model.Options.MoveCardToBoardOptions;
19
using TrelloDotNet.Model.Options.MoveCardToListOptions;
20

21
// ReSharper disable UnusedMember.Global
22

23
namespace TrelloDotNet
24
{
25
    public partial class TrelloClient
26
    {
27
        /// <summary>
28
        /// Add a Card
29
        /// </summary>
30
        /// <param name="options">Add Card Options (Name, Dates, Checklist, etc.)</param>
31
        /// <param name="cancellationToken">Cancellation Token</param>
32
        /// <returns>The Added Card</returns>
33
        public async Task<Card> AddCardAsync(AddCardOptions options, CancellationToken cancellationToken = default)
34
        {
35
            if (string.IsNullOrWhiteSpace(options.ListId))
59!
36
            {
37
                throw new TrelloApiException("No ListId provided in options (Mandatory)", string.Empty);
×
38
            }
39

40
            var input = new Card(options.ListId, options.Name, options.Description)
59✔
41
            {
59✔
42
                IsTemplate = options.IsTemplate
59✔
43
            };
59✔
44

45
            if (options.Start.HasValue)
59✔
46
            {
47
                input.Start = options.Start.Value;
2✔
48
            }
49

50
            if (options.Due.HasValue)
59✔
51
            {
52
                input.Due = options.Due.Value;
3✔
53
            }
54

55
            input.DueComplete = options.DueComplete;
59✔
56

57
            if (options.Position.HasValue)
59!
58
            {
59
                input.Position = options.Position.Value;
×
60
            }
61
            else
62
            {
63
                input.NamedPosition = options.NamedPosition;
59✔
64
            }
65

66
            if (options.LabelIds != null)
59✔
67
            {
68
                input.LabelIds = options.LabelIds;
59✔
69
            }
70

71
            if (options.MemberIds != null)
59✔
72
            {
73
                input.MemberIds = options.MemberIds;
59✔
74
            }
75

76
            QueryParameter[] parameters = _queryParametersBuilder.GetViaQueryParameterAttributes(input);
59✔
77
            _queryParametersBuilder.AdjustForNamedPosition(parameters, input.NamedPosition);
59✔
78
            Card addedCard = await _apiRequestController.Post<Card>($"{UrlPaths.Cards}", cancellationToken, parameters);
59✔
79

80
            bool needGet = false;
59✔
81
            var getCardOptions = new GetCardOptions();
59✔
82
            if (options.Checklists != null)
59✔
83
            {
84
                needGet = true;
59✔
85
                getCardOptions.IncludeChecklists = true;
59✔
86
                foreach (Checklist checklist in options.Checklists)
118!
87
                {
88
                    checklist.NamedPosition = NamedPosition.Bottom;
×
89
                    await AddChecklistAsync(addedCard.Id, checklist, cancellationToken: cancellationToken);
×
90
                }
91
            }
92

93
            if (options.AttachmentFileUploads != null)
59✔
94
            {
95
                needGet = true;
59✔
96
                getCardOptions.IncludeAttachments = GetCardOptionsIncludeAttachments.True;
59✔
97
                foreach (AttachmentFileUpload fileUpload in options.AttachmentFileUploads)
118!
98
                {
99
                    await AddAttachmentToCardAsync(addedCard.Id, fileUpload, cancellationToken: cancellationToken);
×
100
                }
101
            }
102

103
            if (options.AttachmentUrlLinks != null)
59✔
104
            {
105
                needGet = true;
59✔
106
                getCardOptions.IncludeAttachments = GetCardOptionsIncludeAttachments.True;
59✔
107
                foreach (AttachmentUrlLink urlLink in options.AttachmentUrlLinks)
118!
108
                {
109
                    await AddAttachmentToCardAsync(addedCard.Id, urlLink, cancellationToken: cancellationToken);
×
110
                }
111
            }
112

113
            if (options.Cover != null)
59✔
114
            {
115
                needGet = true;
1✔
116
                await AddCoverToCardAsync(addedCard.Id, options.Cover, cancellationToken);
1✔
117
            }
118

119
            // ReSharper disable once InvertIf
120
            if (options.CustomFields != null)
59✔
121
            {
122
                needGet = true;
59✔
123
                getCardOptions.IncludeCustomFieldItems = true;
59✔
124
                foreach (var customField in options.CustomFields)
118!
125
                {
126
                    switch (customField.Field.Type)
×
127
                    {
128
                        case CustomFieldType.Checkbox:
129
                            await UpdateCustomFieldValueOnCardAsync(addedCard.Id, customField.Field, (bool)customField.Value, cancellationToken);
×
130
                            break;
×
131
                        case CustomFieldType.Date:
132
                            await UpdateCustomFieldValueOnCardAsync(addedCard.Id, customField.Field, (DateTimeOffset)customField.Value, cancellationToken);
×
133
                            break;
×
134
                        case CustomFieldType.List:
135
                            await UpdateCustomFieldValueOnCardAsync(addedCard.Id, customField.Field, (CustomFieldOption)customField.Value, cancellationToken);
×
136
                            break;
×
137
                        case CustomFieldType.Number:
138
                            switch (customField.Value)
×
139
                            {
140
                                case int intValue:
141
                                    await UpdateCustomFieldValueOnCardAsync(addedCard.Id, customField.Field, intValue, cancellationToken);
×
142
                                    break;
×
143
                                case decimal decimalValue:
144
                                    await UpdateCustomFieldValueOnCardAsync(addedCard.Id, customField.Field, decimalValue, cancellationToken);
×
145
                                    break;
×
146
                            }
147

148
                            break;
149
                        case CustomFieldType.Text:
150
                        default:
151
                            await UpdateCustomFieldValueOnCardAsync(addedCard.Id, customField.Field, (string)customField.Value, cancellationToken);
×
152
                            break;
153
                    }
154
                }
155
            }
156

157
            return needGet ? await GetCardAsync(addedCard.Id, getCardOptions, cancellationToken) : addedCard;
59!
158
        }
59✔
159

160

161
        /// <summary>
162
        /// Add a Card to the Inbox of the Member that own the Trello token
163
        /// </summary>
164
        /// <param name="options">Add Card options (Name, Dates, Checklists, etc.)</param>
165
        /// <param name="cancellationToken">CancellationToken</param>
166
        /// <returns>The Added Card</returns>
167
        public async Task<Card> AddCardToInboxAsync(AddCardToInboxOptions options, CancellationToken cancellationToken = default)
168
        {
169
            TokenMemberInbox inbox = await GetTokenMemberInboxAsync(cancellationToken);
×
170
            if (inbox == null)
×
171
            {
172
                throw new TrelloApiException("Could not find your inbox", string.Empty);
×
173
            }
174

175
            var input = new Card(inbox.ListId, options.Name, options.Description);
×
176
            if (options.Start.HasValue)
×
177
            {
178
                input.Start = options.Start.Value;
×
179
            }
180

181
            if (options.Due.HasValue)
×
182
            {
183
                input.Due = options.Due.Value;
×
184
            }
185

186
            input.DueComplete = options.DueComplete;
×
187

188
            if (options.Position.HasValue)
×
189
            {
190
                input.Position = options.Position.Value;
×
191
            }
192
            else
193
            {
194
                input.NamedPosition = options.NamedPosition;
×
195
            }
196

197
            QueryParameter[] parameters = _queryParametersBuilder.GetViaQueryParameterAttributes(input);
×
198
            Card addedCard = await _apiRequestController.Post<Card>($"{UrlPaths.Cards}", cancellationToken, parameters);
×
199

200
            if (options.Checklists != null)
×
201
            {
202
                foreach (Checklist checklist in options.Checklists)
×
203
                {
204
                    checklist.NamedPosition = NamedPosition.Bottom;
×
205
                    await AddChecklistAsync(addedCard.Id, checklist, cancellationToken: cancellationToken);
×
206
                }
207
            }
208

209
            if (options.AttachmentFileUploads != null)
×
210
            {
211
                foreach (AttachmentFileUpload fileUpload in options.AttachmentFileUploads)
×
212
                {
213
                    await AddAttachmentToCardAsync(addedCard.Id, fileUpload, cancellationToken: cancellationToken);
×
214
                }
215
            }
216

217
            if (options.AttachmentUrlLinks != null)
×
218
            {
219
                foreach (AttachmentUrlLink urlLink in options.AttachmentUrlLinks)
×
220
                {
221
                    await AddAttachmentToCardAsync(addedCard.Id, urlLink, cancellationToken: cancellationToken);
×
222
                }
223
            }
224

225
            if (options.Cover != null)
×
226
            {
227
                await AddCoverToCardAsync(addedCard.Id, options.Cover, cancellationToken);
×
228
            }
229

230
            return addedCard;
×
231
        }
×
232

233
        /// <summary>
234
        /// Add new card based on a Template Card
235
        /// </summary>
236
        /// <param name="options">Parameters for Adding the Card</param>
237
        /// <param name="cancellationToken">Cancellation Token</param>
238
        /// <returns>The New Card</returns>
239
        public async Task<Card> AddCardFromTemplateAsync(AddCardFromTemplateOptions options, CancellationToken cancellationToken = default)
240
        {
241
            var nameOnNewCard = !string.IsNullOrWhiteSpace(options.Name)
×
242
                ? options.Name
×
243
                : (await GetCardAsync(options.SourceTemplateCardId, new GetCardOptions
×
244
                {
×
245
                    CardFields = new CardFields(CardFieldsType.Name)
×
246
                }, cancellationToken)).Name;
×
247

248
            QueryParameter[] parameters =
×
249
            {
×
250
                new QueryParameter("name", nameOnNewCard),
×
251
                new QueryParameter("idList", options.TargetListId),
×
252
                new QueryParameter("pos", "bottom"),
×
253
                new QueryParameter("idCardSource", options.SourceTemplateCardId)
×
254
            };
×
255
            return await _apiRequestController.Post<Card>($"{UrlPaths.Cards}", cancellationToken, parameters);
×
256
        }
×
257

258
        /// <summary>
259
        /// Copy a Card
260
        /// </summary>
261
        /// <param name="options">Parameters for copying the card</param>
262
        /// <param name="cancellationToken">Cancellation Token</param>
263
        /// <returns>The Card Copy</returns>
264
        public async Task<Card> CopyCardAsync(CopyCardOptions options, CancellationToken cancellationToken = default)
265

266
        {
267
            var nameOnNewCard = !string.IsNullOrWhiteSpace(options.Name)
×
268
                ? options.Name
×
269
                : (await GetCardAsync(options.SourceCardId, new GetCardOptions
×
270
                {
×
271
                    CardFields = new CardFields(CardFieldsType.Name)
×
272
                }, cancellationToken)).Name;
×
273

274
            string position = "bottom";
×
275

276
            if (options.Position.HasValue)
×
277
            {
278
                position = options.Position.Value.ToString(CultureInfo.InvariantCulture);
×
279
            }
280

281
            if (options.NamedPosition.HasValue)
×
282
            {
283
                position = options.NamedPosition.Value == NamedPosition.Bottom ? "bottom" : "top";
×
284
            }
285

286
            string keepFromSource = "all";
×
287
            if (options.Keep.HasValue)
×
288
            {
289
                CopyCardOptionsToKeep keep = options.Keep.Value;
×
290

291
                if (keep.HasFlag(CopyCardOptionsToKeep.All))
×
292
                {
293
                    keepFromSource = "all";
×
294
                }
295
                else
296
                {
297
                    var keepStrings = new List<string>();
×
298
                    var enumValues = Enum.GetValues(typeof(CopyCardOptionsToKeep)).Cast<CopyCardOptionsToKeep>().ToList();
×
299
                    foreach (CopyCardOptionsToKeep toKeep in enumValues.Where(x => x != CopyCardOptionsToKeep.All))
×
300
                    {
301
                        if (keep.HasFlag(toKeep))
×
302
                        {
303
                            keepStrings.Add(toKeep.GetJsonPropertyName());
×
304
                        }
305
                    }
306

307
                    keepFromSource = string.Join(",", keepStrings);
×
308
                }
309
            }
310

311
            QueryParameter[] parameters =
×
312
            {
×
313
                new QueryParameter("name", nameOnNewCard),
×
314
                new QueryParameter("idList", options.TargetListId),
×
315
                new QueryParameter("pos", position),
×
316
                new QueryParameter("idCardSource", options.SourceCardId),
×
317
                new QueryParameter("keepFromSource", keepFromSource)
×
318
            };
×
319
            return await _apiRequestController.Post<Card>($"{UrlPaths.Cards}", cancellationToken, parameters);
×
320
        }
×
321

322
        /// <summary>
323
        /// Mirror a Card
324
        /// </summary>
325
        /// <param name="options">Parameters for create the mirror</param>
326
        /// <param name="cancellationToken">Cancellation Token</param>
327
        /// <returns>The Mirror Card</returns>
328
        public async Task<Card> MirrorCardAsync(MirrorCardOptions options, CancellationToken cancellationToken = default)
329
        {
330
            //Get Source-Card as we need the ShortUrl to make the Card Mirror magic Happen
331
            Card sourceCard = await GetCardAsync(options.SourceCardId, new GetCardOptions
×
332
            {
×
333
                CardFields = new CardFields(CardFieldsType.ShortUrl)
×
334
            }, cancellationToken);
×
335

336
            string position = "bottom";
×
337

338
            if (options.Position.HasValue)
×
339
            {
340
                position = options.Position.Value.ToString(CultureInfo.InvariantCulture);
×
341
            }
342

343
            if (options.NamedPosition.HasValue)
×
344
            {
345
                position = options.NamedPosition.Value == NamedPosition.Bottom ? "bottom" : "top";
×
346
            }
347

348
            var parameters = new List<QueryParameter>
×
349
            {
×
350
                new QueryParameter("idList", options.TargetListId),
×
351
                new QueryParameter("name", sourceCard.ShortUrl),
×
352
                new QueryParameter("isTemplate", false),
×
353
                new QueryParameter("closed", false),
×
354
                new QueryParameter("pos", position),
×
355
                new QueryParameter("cardRole", "mirror"),
×
356
            };
×
357

358
            var result = await _apiRequestController.Post<Card>($"{UrlPaths.Cards}", cancellationToken, parameters.ToArray());
×
359
            return result;
×
360
        }
×
361

362
        /// <summary>
363
        /// Archive (Close) a Card
364
        /// </summary>
365
        /// <param name="cardId">The id of card that should be archived</param>
366
        /// <param name="cancellationToken">Cancellation Token</param>
367
        /// <returns>The Archived Card</returns>
368
        public async Task<Card> ArchiveCardAsync(string cardId, CancellationToken cancellationToken = default)
369
        {
370
            return await _apiRequestController.Put<Card>($"{UrlPaths.Cards}/{cardId}", cancellationToken, new QueryParameter("closed", true));
1✔
371
        }
1✔
372

373
        /// <summary>
374
        /// ReOpen (Send back to board) a Card
375
        /// </summary>
376
        /// <param name="cardId">The id of card that should be reopened</param>
377
        /// <param name="cancellationToken">Cancellation Token</param>
378
        /// <returns>The ReOpened Card</returns>
379
        public async Task<Card> ReOpenCardAsync(string cardId, CancellationToken cancellationToken = default)
380
        {
381
            return await _apiRequestController.Put<Card>($"{UrlPaths.Cards}/{cardId}", cancellationToken, new QueryParameter("closed", false));
1✔
382
        }
1✔
383

384
        /// <summary>
385
        /// Update one or more specific fields on a card
386
        /// </summary>
387
        /// <param name="cardId">Id of the Card</param>
388
        /// <param name="valuesToUpdate">The Specific values to set</param>
389
        /// <param name="cancellationToken">CancellationToken</param>
390
        public async Task<Card> UpdateCardAsync(string cardId, List<CardUpdate> valuesToUpdate, CancellationToken cancellationToken = default)
391
        {
392
            var parameters = valuesToUpdate.Select(x => x.ToQueryParameter()).ToList();
99✔
393
            QueryParameter coverParameter = parameters.FirstOrDefault(x => x.Name == "cover");
95✔
394
            if (coverParameter != null && !string.IsNullOrWhiteSpace(coverParameter.GetRawStringValue()))
39✔
395
            {
396
                //Special Cover Card
397
                parameters.Remove(coverParameter);
3✔
398
                CardCover cover = JsonSerializer.Deserialize<CardCover>(coverParameter.GetRawStringValue());
3✔
399
                var payload = GeneratePayloadForCoverUpdate(cover, parameters);
3✔
400
                return await _apiRequestController.PutWithJsonPayload<Card>($"{UrlPaths.Cards}/{cardId}", cancellationToken, payload, parameters.ToArray());
3✔
401
            }
402

403
            return await _apiRequestController.Put<Card>($"{UrlPaths.Cards}/{cardId}", cancellationToken, parameters.ToArray());
36✔
404
        }
39✔
405

406
        /// <summary>
407
        /// Archive all cards on in a List
408
        /// </summary>
409
        /// <param name="listId">The id of the List that should have its cards archived</param>
410
        /// <param name="cancellationToken">Cancellation Token</param>
411
        public async Task ArchiveAllCardsInListAsync(string listId, CancellationToken cancellationToken = default)
412
        {
413
            await _apiRequestController.Post<List>($"{UrlPaths.Lists}/{listId}/archiveAllCards", cancellationToken);
1✔
414
        }
1✔
415

416
        /// <summary>
417
        /// Move all cards of a list to another list
418
        /// </summary>
419
        /// <param name="currentListId">The id of the List that should have its cards moved</param>
420
        /// <param name="newListId">The id of the new List that should receive the cards</param>
421
        /// <param name="cancellationToken">Cancellation Token</param>
422
        public async Task MoveAllCardsInListAsync(string currentListId, string newListId, CancellationToken cancellationToken = default)
423
        {
424
            var newList = await GetListAsync(newListId, cancellationToken); //Get the new list's BoardId so the user do not need to provide it.
1✔
425
            await _apiRequestController.Post($"{UrlPaths.Lists}/{currentListId}/moveAllCards", cancellationToken,
1✔
426
                0,
1✔
427
                new QueryParameter("idBoard", newList.BoardId),
1✔
428
                new QueryParameter("idList", newListId)
1✔
429
            );
1✔
430
        }
1✔
431

432
        /// <summary>
433
        /// Delete a Card (WARNING: THERE IS NO WAY GOING BACK!!!). Alternative use ArchiveCardAsync() for non-permanency
434
        /// </summary>
435
        /// <param name="cardId">The id of the Card to Delete</param>
436
        /// <param name="cancellationToken">Cancellation Token</param>
437
        public async Task DeleteCardAsync(string cardId, CancellationToken cancellationToken = default)
438
        {
439
            await _apiRequestController.Delete($"{UrlPaths.Cards}/{cardId}", cancellationToken, 0);
1✔
440
        }
1✔
441

442
        /// <summary>
443
        /// Get a Card by its Id
444
        /// </summary>
445
        /// <param name="cardId">Id of the Card</param>
446
        /// <param name="cancellationToken">Cancellation Token</param>
447
        /// <returns>The Card</returns>
448
        public async Task<Card> GetCardAsync(string cardId, CancellationToken cancellationToken = default)
449
        {
450
            return await _apiRequestController.Get<Card>(GetUrlBuilder.GetCard(cardId), cancellationToken);
49✔
451
        }
49✔
452

453
        /// <summary>
454
        /// Get a Card by its Id
455
        /// </summary>
456
        /// <param name="cardId">Id of the Card</param>
457
        /// <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>
458
        /// <param name="cancellationToken">Cancellation Token</param>
459
        /// <returns>The Card</returns>
460
        public async Task<Card> GetCardAsync(string cardId, GetCardOptions options, CancellationToken cancellationToken = default)
461
        {
462
            return await _apiRequestController.Get<Card>(GetUrlBuilder.GetCard(cardId), cancellationToken, options.GetParameters(false));
119✔
463
        }
119✔
464

465
        /// <summary>
466
        /// Get all open cards on un-archived lists on a board
467
        /// </summary>
468
        /// <param name="boardId">Id of the Board (in its long or short version)</param>
469
        /// <param name="cancellationToken">CancellationToken</param>
470
        /// <returns>List of Cards</returns>
471
        public async Task<List<Card>> GetCardsOnBoardAsync(string boardId, CancellationToken cancellationToken = default)
472
        {
473
            return await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsOnBoard(boardId), cancellationToken);
6✔
474
        }
6✔
475

476
        /// <summary>
477
        /// Get all cards (default only all un-archived lists on a board; use options.Filter to get archived cards)
478
        /// </summary>
479
        /// <param name="boardId">Id of the Board (in its long or short version)</param>
480
        /// <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>
481
        /// <param name="cancellationToken">CancellationToken</param>
482
        /// <returns>List of Cards</returns>
483
        public async Task<List<Card>> GetCardsOnBoardAsync(string boardId, GetCardOptions options, CancellationToken cancellationToken = default)
484
        {
485
            if (options.IncludeList)
1!
486
            {
487
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idList"))
×
488
                {
489
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
490
                    {
×
491
                        "idList"
×
492
                    }).ToArray();
×
493
                }
494
            }
495

496
            if (options.IncludeBoard)
1!
497
            {
498
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idBoard"))
×
499
                {
500
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
501
                    {
×
502
                        "idBoard"
×
503
                    }).ToArray();
×
504
                }
505
            }
506

507
            List<Card> cards;
508
            if (options.Filter.HasValue)
1!
509
            {
510
                cards = await _apiRequestController.Get<List<Card>>($"{GetUrlBuilder.GetCardsOnBoard(boardId)}/{options.Filter.Value.GetJsonPropertyName()}", cancellationToken, options.GetParameters(true));
1✔
511
            }
512
            else
513
            {
514
                cards = await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsOnBoard(boardId), cancellationToken, options.GetParameters(true));
×
515
            }
516

517
            if (options.IncludeList)
1!
518
            {
519
                var lists = await GetListsOnBoardAsync(boardId, cancellationToken);
×
520
                foreach (Card card in cards)
×
521
                {
522
                    card.List = lists.FirstOrDefault(x => x.Id == card.ListId);
×
523
                }
524
            }
525

526
            if (options.IncludeBoard)
1!
527
            {
528
                var board = await GetBoardAsync(boardId, cancellationToken);
×
529
                foreach (Card card in cards)
×
530
                {
531
                    card.Board = board;
×
532
                }
533
            }
534

535
            //todo - Add cardFields on the fly based on filter-conditions (adding any that is missing based on the filter)
536
            cards = FilterCards(cards, options.FilterConditions);
1✔
537
            return OrderCards(cards, options.OrderBy);
1✔
538
        }
1✔
539

540
        /// <summary>
541
        /// Get all open cards on a specific list
542
        /// </summary>
543
        /// <param name="listId">Id of the List</param>
544
        /// <param name="cancellationToken">Cancellation Token</param>
545
        /// <returns>List of Cards</returns>
546
        public async Task<List<Card>> GetCardsInListAsync(string listId, CancellationToken cancellationToken = default)
547
        {
548
            return await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsInList(listId), cancellationToken);
9✔
549
        }
9✔
550

551
        /// <summary>
552
        /// Get all open cards on a specific list
553
        /// </summary>
554
        /// <param name="listId">Id of the List</param>
555
        /// <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>
556
        /// <param name="cancellationToken">Cancellation Token</param>
557
        /// <returns>List of Cards</returns>
558
        public async Task<List<Card>> GetCardsInListAsync(string listId, GetCardOptions options, CancellationToken cancellationToken = default)
559
        {
560
            if (options.IncludeBoard)
×
561
            {
562
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idBoard"))
×
563
                {
564
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
565
                    {
×
566
                        "idBoard"
×
567
                    }).ToArray();
×
568
                }
569
            }
570

571
            var cards = await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsInList(listId), cancellationToken, options.GetParameters(true));
×
572
            if (options.IncludeList)
×
573
            {
574
                var list = await GetListAsync(listId, cancellationToken);
×
575
                foreach (Card card in cards)
×
576
                {
577
                    card.ListId = listId;
×
578
                    card.List = list;
×
579
                }
580
            }
581

582
            if (options.IncludeBoard && cards.Count > 0)
×
583
            {
584
                var board = await GetBoardAsync(cards[0].BoardId, cancellationToken);
×
585
                foreach (Card card in cards)
×
586
                {
587
                    card.Board = board;
×
588
                }
589
            }
590

591
            //todo - Add cardFields on the fly based on filter-conditions (adding any that is missing based on the filter)
NEW
592
            cards = FilterCards(cards, options.FilterConditions);
×
593
            return OrderCards(cards, options.OrderBy);
×
594
        }
×
595

596
        /// <summary>
597
        /// Get the Cards in your Inbox
598
        /// </summary>
599
        /// <param name="cancellationToken">CancellationToken</param>
600
        /// <returns>The Cards</returns>
601
        public async Task<List<Card>> GetCardsInInboxAsync(CancellationToken cancellationToken = default)
602
        {
603
            TokenMemberInbox inbox = await GetTokenMemberInboxAsync(cancellationToken);
×
604
            if (inbox == null)
×
605
            {
606
                throw new TrelloApiException("Could not find your inbox", string.Empty);
×
607
            }
608

609
            return await GetCardsOnBoardAsync(inbox.BoardId, cancellationToken);
×
610
        }
×
611

612
        /// <summary>
613
        /// Get the Cards in your Inbox
614
        /// </summary>
615
        /// <param name="options">Options on what parts of the cards to get</param>
616
        /// <param name="cancellationToken">CancellationToken</param>
617
        /// <returns>The Cards</returns>
618
        public async Task<List<Card>> GetCardsInInboxAsync(GetInboxCardOptions options, CancellationToken cancellationToken = default)
619
        {
620
            TokenMemberInbox inbox = await GetTokenMemberInboxAsync(cancellationToken);
×
621
            if (inbox == null)
×
622
            {
623
                throw new TrelloApiException("Could not find your inbox", string.Empty);
×
624
            }
625

626
            var cards = await GetCardsOnBoardAsync(inbox.BoardId, options.ToCardOptions(), cancellationToken);
×
627
            //todo - Add cardFields on the fly based on filter-conditions (adding any that is missing based on the filter)
NEW
628
            cards = FilterCards(cards, options.FilterConditions);
×
629
            return OrderCards(cards, options.OrderBy);
×
630
        }
×
631

632
        /// <summary>
633
        /// Get all Cards a Member is on (across multiple boards)
634
        /// </summary>
635
        /// <param name="memberId">Id of Member</param>
636
        /// <param name="cancellationToken">Cancellation Token</param>
637
        /// <returns></returns>
638
        public async Task<List<Card>> GetCardsForMemberAsync(string memberId, CancellationToken cancellationToken = default)
639
        {
640
            return await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsForMember(memberId), cancellationToken);
1✔
641
        }
1✔
642

643
        /// <summary>
644
        /// Get all Cards a Member is on (across multiple boards)
645
        /// </summary>
646
        /// <param name="memberId">Id of Member</param>
647
        /// <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>
648
        /// <param name="cancellationToken">Cancellation Token</param>
649
        /// <returns></returns>
650
        public async Task<List<Card>> GetCardsForMemberAsync(string memberId, GetCardOptions options, CancellationToken cancellationToken = default)
651
        {
652
            if (options.IncludeList)
×
653
            {
654
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idList"))
×
655
                {
656
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
657
                    {
×
658
                        "idList"
×
659
                    }).ToArray();
×
660
                }
661

662
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idBoard"))
×
663
                {
664
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
665
                    {
×
666
                        "idBoard"
×
667
                    }).ToArray();
×
668
                }
669
            }
670

671
            if (options.IncludeBoard)
×
672
            {
673
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idBoard"))
×
674
                {
675
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
676
                    {
×
677
                        "idBoard"
×
678
                    }).ToArray();
×
679
                }
680
            }
681

682
            var cards = await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsForMember(memberId), cancellationToken, options.GetParameters(true));
×
683
            if (options.IncludeList)
×
684
            {
685
                var boardsToGetListsFor = cards.Select(x => x.BoardId).Distinct().ToArray();
×
686
                List<List> lists = new List<List>();
×
687
                foreach (var boardId in boardsToGetListsFor)
×
688
                {
689
                    lists.AddRange(await GetListsOnBoardAsync(boardId, cancellationToken));
×
690
                }
691

692
                foreach (Card card in cards)
×
693
                {
694
                    card.List = lists.FirstOrDefault(x => x.Id == card.ListId);
×
695
                }
696
            }
×
697

698
            if (options.IncludeBoard)
×
699
            {
700
                var boardIds = cards.Select(x => x.BoardId).Distinct().ToList();
×
701
                var boards = await GetBoardsAsync(boardIds, cancellationToken);
×
702
                foreach (Card card in cards)
×
703
                {
704
                    card.Board = boards.FirstOrDefault(x => x.Id == card.BoardId);
×
705
                }
706
            }
707

708
            //todo - Add cardFields on the fly based on filter-conditions (adding any that is missing based on the filter)
NEW
709
            cards = FilterCards(cards, options.FilterConditions);
×
710
            return OrderCards(cards, options.OrderBy);
×
711
        }
×
712

713
        /// <summary>
714
        /// Set Due Date on a card
715
        /// </summary>
716
        /// <param name="cardId">Id of the Card</param>
717
        /// <param name="dueDate">The Due Date (In UTC Time)</param>
718
        /// <param name="dueComplete">If Card is complete</param>
719
        /// <param name="cancellationToken">Cancellation Token</param>
720
        public async Task<Card> SetDueDateOnCardAsync(string cardId, DateTimeOffset dueDate, bool dueComplete = false, CancellationToken cancellationToken = default)
721
        {
722
            return await UpdateCardAsync(cardId, new List<CardUpdate>
1✔
723
            {
1✔
724
                CardUpdate.DueDate(dueDate),
1✔
725
                CardUpdate.DueComplete(dueComplete)
1✔
726
            }, cancellationToken);
1✔
727
        }
1✔
728

729
        /// <summary>
730
        /// Set Due Date on a card
731
        /// </summary>
732
        /// <param name="cardId">Id of the Card</param>
733
        /// <param name="startDate">The Start Date (In UTC Time)</param>
734
        /// <param name="cancellationToken">Cancellation Token</param>
735
        public async Task<Card> SetStartDateOnCardAsync(string cardId, DateTimeOffset startDate, CancellationToken cancellationToken = default)
736
        {
737
            return await UpdateCardAsync(cardId, new List<CardUpdate>
1✔
738
            {
1✔
739
                CardUpdate.StartDate(startDate)
1✔
740
            }, cancellationToken);
1✔
741
        }
1✔
742

743
        /// <summary>
744
        /// Set Start and Due Date on a card
745
        /// </summary>
746
        /// <param name="cardId">Id of the Card</param>
747
        /// <param name="startDate">The Start Date (In UTC Time)</param>
748
        /// <param name="dueDate">The Due Date (In UTC Time)</param>
749
        /// <param name="dueComplete">If Card is complete</param>
750
        /// <param name="cancellationToken">Cancellation Token</param> 
751
        public async Task<Card> SetStartDateAndDueDateOnCardAsync(string cardId, DateTimeOffset startDate, DateTimeOffset dueDate, bool dueComplete = false, CancellationToken cancellationToken = default)
752
        {
753
            return await UpdateCardAsync(cardId, new List<CardUpdate>
1✔
754
            {
1✔
755
                CardUpdate.StartDate(startDate),
1✔
756
                CardUpdate.DueDate(dueDate),
1✔
757
                CardUpdate.DueComplete(dueComplete)
1✔
758
            }, cancellationToken);
1✔
759
        }
1✔
760

761
        /// <summary>
762
        /// Move a Card to a new list on the same board
763
        /// </summary>
764
        /// <param name="cardId">Id of the Card</param>
765
        /// <param name="newListId">Id of the List you wish to move it to</param>
766
        /// <param name="cancellationToken">Cancellation Token</param>
767
        /// <returns></returns>
768
        public async Task<Card> MoveCardToListAsync(string cardId, string newListId, CancellationToken cancellationToken = default)
769
        {
770
            return await UpdateCardAsync(cardId, new List<CardUpdate>
1✔
771
            {
1✔
772
                CardUpdate.List(newListId)
1✔
773
            }, cancellationToken);
1✔
774
        }
1✔
775

776
        /// <summary>
777
        /// Move a Card to a new list on the same board
778
        /// </summary>
779
        /// <param name="cardId">Id of the Card</param>
780
        /// <param name="newListId">Id of the List you wish to move it to</param>
781
        /// <param name="options">Additional optional Options for the Move</param>
782
        /// <param name="cancellationToken">Cancellation Token</param>
783
        /// <returns></returns>
784
        public async Task<Card> MoveCardToListAsync(string cardId, string newListId, MoveCardToListOptions options, CancellationToken cancellationToken = default)
785
        {
786
            var parameters = new List<CardUpdate> { CardUpdate.List(newListId) };
×
787
            if (options.NamedPositionOnNewList.HasValue)
×
788
            {
789
                parameters.Add(CardUpdate.Position(options.NamedPositionOnNewList.Value));
×
790
            }
791
            else if (options.PositionOnNewList.HasValue)
×
792
            {
793
                parameters.Add(CardUpdate.Position(options.PositionOnNewList.Value));
×
794
            }
795

796
            return await UpdateCardAsync(cardId, parameters, cancellationToken);
×
797
        }
×
798

799
        /// <summary>
800
        /// Move the Card to the top of its current list
801
        /// </summary>
802
        /// <param name="cardId">Id of the Card</param>
803
        /// <param name="cancellationToken">Cancellation Token</param>
804
        /// <returns>The Card</returns>
805
        public async Task<Card> MoveCardToTopOfCurrentListAsync(string cardId, CancellationToken cancellationToken = default)
806
        {
807
            return await UpdateCardAsync(cardId, new List<CardUpdate>
×
808
            {
×
809
                CardUpdate.Position(NamedPosition.Top)
×
810
            }, cancellationToken);
×
811
        }
×
812

813
        /// <summary>
814
        /// Move the Card to the bottom of its current list
815
        /// </summary>
816
        /// <param name="cardId">Id of the Card</param>
817
        /// <param name="cancellationToken">Cancellation Token</param>
818
        /// <returns>The Card</returns>
819
        public async Task<Card> MoveCardToBottomOfCurrentListAsync(string cardId, CancellationToken cancellationToken = default)
820
        {
821
            return await UpdateCardAsync(cardId, new List<CardUpdate>
×
822
            {
×
823
                CardUpdate.Position(NamedPosition.Bottom)
×
824
            }, cancellationToken);
×
825
        }
×
826

827
        /// <summary>
828
        /// Move a Card to another board
829
        /// </summary>
830
        /// <param name="cardId">The Id of the Card to Move</param>
831
        /// <param name="newBoardId">The ID of the New Board that the card should be moved to</param>
832
        /// <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>
833
        /// <param name="cancellationToken">Cancellation Token</param>
834
        /// <returns></returns>
835
        public async Task<Card> MoveCardToBoard(string cardId, string newBoardId, MoveCardToBoardOptions options, CancellationToken cancellationToken = default)
836
        {
837
            if (options == null)
×
838
            {
839
                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");
×
840
            }
841

842
            List<CardUpdate> parameters = new List<CardUpdate> { CardUpdate.Board(newBoardId) };
×
843
            var newListId = options.NewListId;
×
844
            if (string.IsNullOrWhiteSpace(newListId))
×
845
            {
846
                //No list specified, so we need to find the first list on the board
847
                newListId = (await GetListsOnBoardAsync(newBoardId, cancellationToken)).OrderBy(x => x.Position).FirstOrDefault()?.Id;
×
848
            }
849

850
            parameters.Add(CardUpdate.List(newListId));
×
851

852
            if (options.NamedPositionOnNewList.HasValue)
×
853
            {
854
                parameters.Add(CardUpdate.Position((options.NamedPositionOnNewList.Value)));
×
855
            }
856
            else if (options.PositionOnNewList.HasValue)
×
857
            {
858
                parameters.Add(CardUpdate.Position(options.PositionOnNewList.Value));
×
859
            }
860

861
            Card card = await GetCardAsync(cardId, new GetCardOptions
×
862
            {
×
863
                CardFields = new CardFields(CardFieldsType.MemberIds, CardFieldsType.LabelIds, CardFieldsType.Labels)
×
864
            }, cancellationToken);
×
865

866
            switch (options.MemberOptions)
×
867
            {
868
                case MoveCardToBoardOptionsMemberOptions.KeepMembersAlsoOnNewBoardAndRemoveRest:
869
                    var existingMemberIdsOnNewBoard = (await GetMembersOfBoardAsync(newBoardId, cancellationToken)).Select(x => x.Id);
×
870
                    card.MemberIds = card.MemberIds.Intersect(existingMemberIdsOnNewBoard).ToList();
×
871
                    break;
×
872
                case MoveCardToBoardOptionsMemberOptions.RemoveAllMembersOnCard:
873
                    card.MemberIds.Clear();
×
874
                    break;
×
875
                default:
876
                    throw new ArgumentOutOfRangeException();
×
877
            }
878

879
            if (card.LabelIds.Any())
×
880
            {
881
                card.LabelIds.Clear();
×
882
                switch (options.LabelOptions)
×
883
                {
884
                    case MoveCardToBoardOptionsLabelOptions.MigrateToLabelsOfSameNameAndColorAndCreateMissing:
885
                    {
886
                        var existingLabels = await GetLabelsOfBoardAsync(newBoardId, cancellationToken);
×
887
                        foreach (Label cardLabel in card.Labels)
×
888
                        {
889
                            Label existingLabel = existingLabels.FirstOrDefault(x => x.Name == cardLabel.Name && x.Color == cardLabel.Color);
×
890
                            if (existingLabel != null)
×
891
                            {
892
                                card.LabelIds.Add(existingLabel.Id);
×
893
                            }
894
                            else
895
                            {
896
                                //Label need to be added
897
                                Label newLabel = await AddLabelAsync(new Label(newBoardId, cardLabel.Name, cardLabel.Color), cancellationToken);
×
898
                                card.LabelIds.Add(newLabel.Id);
×
899
                            }
900
                        }
901

902
                        break;
×
903
                    }
904
                    case MoveCardToBoardOptionsLabelOptions.MigrateToLabelsOfSameNameAndColorAndRemoveMissing:
905
                    {
906
                        var existingLabels = await GetLabelsOfBoardAsync(newBoardId, cancellationToken);
×
907
                        foreach (Label cardLabel in card.Labels)
×
908
                        {
909
                            Label existingLabel = existingLabels.FirstOrDefault(x => x.Name == cardLabel.Name && x.Color == cardLabel.Color);
×
910
                            if (existingLabel != null)
×
911
                            {
912
                                card.LabelIds.Add(existingLabel.Id);
×
913
                            }
914
                        }
915

916
                        break;
917
                    }
918
                    case MoveCardToBoardOptionsLabelOptions.MigrateToLabelsOfSameNameAndCreateMissing:
919
                    {
920
                        var existingLabels = await GetLabelsOfBoardAsync(newBoardId, cancellationToken);
×
921
                        foreach (Label cardLabel in card.Labels)
×
922
                        {
923
                            Label existingLabel = existingLabels.FirstOrDefault(x => x.Name == cardLabel.Name);
×
924
                            if (existingLabel != null)
×
925
                            {
926
                                card.LabelIds.Add(existingLabel.Id);
×
927
                            }
928
                            else
929
                            {
930
                                //Label need to be added
931
                                Label newLabel = await AddLabelAsync(new Label(newBoardId, cardLabel.Name, cardLabel.Color), cancellationToken);
×
932
                                card.LabelIds.Add(newLabel.Id);
×
933
                            }
934
                        }
935

936
                        break;
×
937
                    }
938
                    case MoveCardToBoardOptionsLabelOptions.MigrateToLabelsOfSameNameAndRemoveMissing:
939
                    {
940
                        var existingLabels = await GetLabelsOfBoardAsync(newBoardId, cancellationToken);
×
941
                        foreach (Label cardLabel in card.Labels)
×
942
                        {
943
                            Label existingLabel = existingLabels.FirstOrDefault(x => x.Name == cardLabel.Name);
×
944
                            if (existingLabel != null)
×
945
                            {
946
                                card.LabelIds.Add(existingLabel.Id);
×
947
                            }
948
                        }
949

950
                        break;
951
                    }
952
                    case MoveCardToBoardOptionsLabelOptions.RemoveAllLabelsOnCard:
953
                        //No more Work needed
954
                        break;
955
                    default:
956
                        throw new ArgumentOutOfRangeException();
×
957
                }
958
            }
959

960
            parameters.Add(CardUpdate.Labels(card.LabelIds));
×
961
            parameters.Add(CardUpdate.Members(card.MemberIds));
×
962

963
            if (options.RemoveDueDate)
×
964
            {
965
                parameters.Add(CardUpdate.DueDate(null));
×
966
            }
967

968
            if (options.RemoveStartDate)
×
969
            {
970
                parameters.Add(CardUpdate.StartDate(null));
×
971
            }
972

973
            return await UpdateCardAsync(cardId, parameters, cancellationToken);
×
974
        }
×
975

976
        private static string GeneratePayloadForCoverUpdate(CardCover cardCover, List<QueryParameter> parameters)
977
        {
978
            //Special code for Cover
979
            string payload = string.Empty;
3✔
980
            if (cardCover == null)
3✔
981
            {
982
                //Remove cover
983
                parameters.Add(new QueryParameter("cover", ""));
2✔
984
            }
985
            else
986
            {
987
                cardCover.PrepareForAddUpdate();
1✔
988
                if (cardCover.Color != null || cardCover.BackgroundImageId != null)
1!
989
                {
990
                    QueryParameter queryParameter = parameters.FirstOrDefault(x => x.Name == "idAttachmentCover");
1✔
991
                    if (queryParameter != null)
1!
992
                    {
993
                        parameters.Remove(queryParameter); //This parameter can't be there while a cover is added
×
994
                    }
995
                }
996

997
                payload = $"{{\"cover\":{JsonSerializer.Serialize(cardCover)}}}";
1✔
998
            }
999

1000
            return payload;
3✔
1001
        }
1002
    }
1003
}
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