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

rwjdk / TrelloDotNet / 13662485221

04 Mar 2025 08:37PM UTC coverage: 67.357% (-4.7%) from 72.018%
13662485221

push

github

rwjdk
Removed [ExcludeFromCodeCoverage] (Not the right way to approch this)

2001 of 3425 branches covered (58.42%)

Branch coverage included in aggregate %.

3820 of 5217 relevant lines covered (73.22%)

91.52 hits per line

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

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

22
// ReSharper disable UnusedMember.Global
23

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

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

46
            if (options.Start.HasValue)
125✔
47
            {
48
                input.Start = options.Start.Value;
16✔
49
            }
50

51
            if (options.Due.HasValue)
125✔
52
            {
53
                input.Due = options.Due.Value;
23✔
54
            }
55

56
            input.DueComplete = options.DueComplete;
125✔
57

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

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

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

77
            QueryParameter[] parameters = _queryParametersBuilder.GetViaQueryParameterAttributes(input);
125✔
78

79
            _queryParametersBuilder.AdjustForNamedPosition(parameters, input.NamedPosition);
125✔
80
            Card addedCard = await _apiRequestController.Post<Card>($"{UrlPaths.Cards}", cancellationToken, parameters);
125✔
81

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

95
            if (options.AttachmentFileUploads != null)
125✔
96
            {
97
                needGet = true;
125✔
98
                getCardOptions.IncludeAttachments = GetCardOptionsIncludeAttachments.True;
125✔
99
                foreach (AttachmentFileUpload fileUpload in options.AttachmentFileUploads)
252✔
100
                {
101
                    await AddAttachmentToCardAsync(addedCard.Id, fileUpload, cancellationToken: cancellationToken);
1✔
102
                }
103
            }
104

105
            if (options.AttachmentUrlLinks != null)
125✔
106
            {
107
                needGet = true;
125✔
108
                getCardOptions.IncludeAttachments = GetCardOptionsIncludeAttachments.True;
125✔
109
                foreach (AttachmentUrlLink urlLink in options.AttachmentUrlLinks)
252✔
110
                {
111
                    await AddAttachmentToCardAsync(addedCard.Id, urlLink, cancellationToken: cancellationToken);
1✔
112
                }
113
            }
114

115
            if (options.Cover != null)
125✔
116
            {
117
                needGet = true;
2✔
118
                await AddCoverToCardAsync(addedCard.Id, options.Cover, cancellationToken);
2✔
119
            }
120

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

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

159
            return needGet ? await GetCardAsync(addedCard.Id, getCardOptions, cancellationToken) : addedCard;
125!
160
        }
125✔
161

162

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

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

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

188
            input.DueComplete = options.DueComplete;
×
189

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

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

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

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

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

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

232
            return addedCard;
×
233
        }
×
234

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

250
            string position = "bottom";
1✔
251

252
            if (options.Position.HasValue)
1!
253
            {
254
                position = options.Position.Value.ToString(CultureInfo.InvariantCulture);
×
255
            }
256

257
            if (options.NamedPosition.HasValue)
1✔
258
            {
259
                position = options.NamedPosition.Value == NamedPosition.Bottom ? "bottom" : "top";
1!
260
            }
261

262
            string keepFromSource = "all";
1✔
263
            if (options.Keep.HasValue)
1✔
264
            {
265
                AddCardFromTemplateOptionsToKeep keep = options.Keep.Value;
1✔
266

267
                if (keep.HasFlag(AddCardFromTemplateOptionsToKeep.All))
1!
268
                {
269
                    keepFromSource = "all";
1✔
270
                }
271
                else
272
                {
273
                    var keepStrings = new List<string>();
×
274
                    var enumValues = Enum.GetValues(typeof(AddCardFromTemplateOptionsToKeep)).Cast<AddCardFromTemplateOptionsToKeep>().ToList();
×
275
                    foreach (AddCardFromTemplateOptionsToKeep toKeep in enumValues.Where(x => x != AddCardFromTemplateOptionsToKeep.All))
×
276
                    {
277
                        if (keep.HasFlag(toKeep))
×
278
                        {
279
                            keepStrings.Add(toKeep.GetJsonPropertyName());
×
280
                        }
281
                    }
282

283
                    keepFromSource = string.Join(",", keepStrings);
×
284
                }
285
            }
286

287
            QueryParameter[] parameters =
1✔
288
            {
1✔
289
                new QueryParameter("name", nameOnNewCard),
1✔
290
                new QueryParameter("idList", options.TargetListId),
1✔
291
                new QueryParameter("pos", position),
1✔
292
                new QueryParameter("idCardSource", options.SourceTemplateCardId),
1✔
293
                new QueryParameter("keepFromSource", keepFromSource)
1✔
294
            };
1✔
295
            return await _apiRequestController.Post<Card>($"{UrlPaths.Cards}", cancellationToken, parameters);
1✔
296
        }
1✔
297

298
        /// <summary>
299
        /// Copy a Card
300
        /// </summary>
301
        /// <param name="options">Parameters for copying the card</param>
302
        /// <param name="cancellationToken">Cancellation Token</param>
303
        /// <returns>The Card Copy</returns>
304
        public async Task<Card> CopyCardAsync(CopyCardOptions options, CancellationToken cancellationToken = default)
305

306
        {
307
            var nameOnNewCard = !string.IsNullOrWhiteSpace(options.Name)
1!
308
                ? options.Name
1✔
309
                : (await GetCardAsync(options.SourceCardId, new GetCardOptions
1✔
310
                {
1✔
311
                    CardFields = new CardFields(CardFieldsType.Name)
1✔
312
                }, cancellationToken)).Name;
1✔
313

314
            string position = "bottom";
1✔
315

316
            if (options.Position.HasValue)
1!
317
            {
318
                position = options.Position.Value.ToString(CultureInfo.InvariantCulture);
×
319
            }
320

321
            if (options.NamedPosition.HasValue)
1✔
322
            {
323
                position = options.NamedPosition.Value == NamedPosition.Bottom ? "bottom" : "top";
1!
324
            }
325

326
            string keepFromSource = "all";
1✔
327
            if (options.Keep.HasValue)
1✔
328
            {
329
                CopyCardOptionsToKeep keep = options.Keep.Value;
1✔
330

331
                if (keep.HasFlag(CopyCardOptionsToKeep.All))
1!
332
                {
333
                    keepFromSource = "all";
×
334
                }
335
                else
336
                {
337
                    var keepStrings = new List<string>();
1✔
338
                    var enumValues = Enum.GetValues(typeof(CopyCardOptionsToKeep)).Cast<CopyCardOptionsToKeep>().ToList();
1✔
339
                    foreach (CopyCardOptionsToKeep toKeep in enumValues.Where(x => x != CopyCardOptionsToKeep.All))
30✔
340
                    {
341
                        if (keep.HasFlag(toKeep))
9✔
342
                        {
343
                            keepStrings.Add(toKeep.GetJsonPropertyName());
3✔
344
                        }
345
                    }
346

347
                    keepFromSource = string.Join(",", keepStrings);
1✔
348
                }
349
            }
350

351
            QueryParameter[] parameters =
1✔
352
            {
1✔
353
                new QueryParameter("name", nameOnNewCard),
1✔
354
                new QueryParameter("idList", options.TargetListId),
1✔
355
                new QueryParameter("pos", position),
1✔
356
                new QueryParameter("idCardSource", options.SourceCardId),
1✔
357
                new QueryParameter("keepFromSource", keepFromSource)
1✔
358
            };
1✔
359
            return await _apiRequestController.Post<Card>($"{UrlPaths.Cards}", cancellationToken, parameters);
1✔
360
        }
1✔
361

362
        /// <summary>
363
        /// Mirror a Card
364
        /// </summary>
365
        /// <param name="options">Parameters for create the mirror</param>
366
        /// <param name="cancellationToken">Cancellation Token</param>
367
        /// <returns>The Mirror Card</returns>
368
        public async Task<Card> MirrorCardAsync(MirrorCardOptions options, CancellationToken cancellationToken = default)
369
        {
370
            //Get Source-Card as we need the ShortUrl to make the Card Mirror magic Happen
371
            Card sourceCard = await GetCardAsync(options.SourceCardId, new GetCardOptions
1✔
372
            {
1✔
373
                CardFields = new CardFields(CardFieldsType.ShortUrl)
1✔
374
            }, cancellationToken);
1✔
375

376
            string position = "bottom";
1✔
377

378
            if (options.Position.HasValue)
1!
379
            {
380
                position = options.Position.Value.ToString(CultureInfo.InvariantCulture);
×
381
            }
382

383
            if (options.NamedPosition.HasValue)
1✔
384
            {
385
                position = options.NamedPosition.Value == NamedPosition.Bottom ? "bottom" : "top";
1!
386
            }
387

388
            var parameters = new List<QueryParameter>
1✔
389
            {
1✔
390
                new QueryParameter("idList", options.TargetListId),
1✔
391
                new QueryParameter("name", sourceCard.ShortUrl),
1✔
392
                new QueryParameter("isTemplate", false),
1✔
393
                new QueryParameter("closed", false),
1✔
394
                new QueryParameter("pos", position),
1✔
395
                new QueryParameter("cardRole", "mirror"),
1✔
396
            };
1✔
397

398
            var result = await _apiRequestController.Post<Card>($"{UrlPaths.Cards}", cancellationToken, parameters.ToArray());
1✔
399
            return result;
1✔
400
        }
1✔
401

402
        /// <summary>
403
        /// Archive (Close) a Card
404
        /// </summary>
405
        /// <param name="cardId">The id of card that should be archived</param>
406
        /// <param name="cancellationToken">Cancellation Token</param>
407
        /// <returns>The Archived Card</returns>
408
        public async Task<Card> ArchiveCardAsync(string cardId, CancellationToken cancellationToken = default)
409
        {
410
            return await _apiRequestController.Put<Card>($"{UrlPaths.Cards}/{cardId}", cancellationToken, new QueryParameter("closed", true));
1✔
411
        }
1✔
412

413
        /// <summary>
414
        /// ReOpen (Send back to board) a Card
415
        /// </summary>
416
        /// <param name="cardId">The id of card that should be reopened</param>
417
        /// <param name="cancellationToken">Cancellation Token</param>
418
        /// <returns>The ReOpened Card</returns>
419
        public async Task<Card> ReOpenCardAsync(string cardId, CancellationToken cancellationToken = default)
420
        {
421
            return await _apiRequestController.Put<Card>($"{UrlPaths.Cards}/{cardId}", cancellationToken, new QueryParameter("closed", false));
1✔
422
        }
1✔
423

424
        /// <summary>
425
        /// Update one or more specific fields on a card
426
        /// </summary>
427
        /// <param name="cardId">Id of the Card</param>
428
        /// <param name="valuesToUpdate">The Specific values to set</param>
429
        /// <param name="cancellationToken">CancellationToken</param>
430
        public async Task<Card> UpdateCardAsync(string cardId, List<CardUpdate> valuesToUpdate, CancellationToken cancellationToken = default)
431
        {
432
            var parameters = valuesToUpdate.Select(x => x.ToQueryParameter()).ToList();
207✔
433
            QueryParameter coverParameter = parameters.FirstOrDefault(x => x.Name == "cover");
187✔
434
            if (coverParameter != null && !string.IsNullOrWhiteSpace(coverParameter.GetRawStringValue()))
67✔
435
            {
436
                //Special Cover Card
437
                parameters.Remove(coverParameter);
5✔
438
                CardCover cover = JsonSerializer.Deserialize<CardCover>(coverParameter.GetRawStringValue());
5✔
439
                var payload = GeneratePayloadForCoverUpdate(cover, parameters);
5✔
440
                return await _apiRequestController.PutWithJsonPayload<Card>($"{UrlPaths.Cards}/{cardId}", cancellationToken, payload, parameters.ToArray());
5✔
441
            }
442

443
            return await _apiRequestController.Put<Card>($"{UrlPaths.Cards}/{cardId}", cancellationToken, parameters.ToArray());
62✔
444
        }
67✔
445

446
        /// <summary>
447
        /// Archive all cards on in a List
448
        /// </summary>
449
        /// <param name="listId">The id of the List that should have its cards archived</param>
450
        /// <param name="cancellationToken">Cancellation Token</param>
451
        public async Task ArchiveAllCardsInListAsync(string listId, CancellationToken cancellationToken = default)
452
        {
453
            await _apiRequestController.Post<List>($"{UrlPaths.Lists}/{listId}/archiveAllCards", cancellationToken);
1✔
454
        }
1✔
455

456
        /// <summary>
457
        /// Move all cards of a list to another list
458
        /// </summary>
459
        /// <param name="currentListId">The id of the List that should have its cards moved</param>
460
        /// <param name="newListId">The id of the new List that should receive the cards</param>
461
        /// <param name="cancellationToken">Cancellation Token</param>
462
        public async Task MoveAllCardsInListAsync(string currentListId, string newListId, CancellationToken cancellationToken = default)
463
        {
464
            var newList = await GetListAsync(newListId, cancellationToken); //Get the new list's BoardId so the user do not need to provide it.
1✔
465
            await _apiRequestController.Post($"{UrlPaths.Lists}/{currentListId}/moveAllCards", cancellationToken,
1✔
466
                0,
1✔
467
                new QueryParameter("idBoard", newList.BoardId),
1✔
468
                new QueryParameter("idList", newListId)
1✔
469
            );
1✔
470
        }
1✔
471

472
        /// <summary>
473
        /// Delete a Card (WARNING: THERE IS NO WAY GOING BACK!!!). Alternative use ArchiveCardAsync() for non-permanency
474
        /// </summary>
475
        /// <param name="cardId">The id of the Card to Delete</param>
476
        /// <param name="cancellationToken">Cancellation Token</param>
477
        public async Task DeleteCardAsync(string cardId, CancellationToken cancellationToken = default)
478
        {
479
            await _apiRequestController.Delete($"{UrlPaths.Cards}/{cardId}", cancellationToken, 0);
4✔
480
        }
4✔
481

482
        /// <summary>
483
        /// Get a Card by its Id
484
        /// </summary>
485
        /// <param name="cardId">Id of the Card</param>
486
        /// <param name="cancellationToken">Cancellation Token</param>
487
        /// <returns>The Card</returns>
488
        public async Task<Card> GetCardAsync(string cardId, CancellationToken cancellationToken = default)
489
        {
490
            return await _apiRequestController.Get<Card>(GetUrlBuilder.GetCard(cardId), cancellationToken);
68✔
491
        }
68✔
492

493
        /// <summary>
494
        /// Get a Card by its Id
495
        /// </summary>
496
        /// <param name="cardId">Id of the Card</param>
497
        /// <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>
498
        /// <param name="cancellationToken">Cancellation Token</param>
499
        /// <returns>The Card</returns>
500
        public async Task<Card> GetCardAsync(string cardId, GetCardOptions options, CancellationToken cancellationToken = default)
501
        {
502
            return await _apiRequestController.Get<Card>(GetUrlBuilder.GetCard(cardId), cancellationToken, options.GetParameters(false));
191✔
503
        }
191✔
504

505
        /// <summary>
506
        /// Get all open cards on un-archived lists on a board
507
        /// </summary>
508
        /// <param name="boardId">Id of the Board (in its long or short version)</param>
509
        /// <param name="cancellationToken">CancellationToken</param>
510
        /// <returns>List of Cards</returns>
511
        public async Task<List<Card>> GetCardsOnBoardAsync(string boardId, CancellationToken cancellationToken = default)
512
        {
513
            return await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsOnBoard(boardId), cancellationToken);
18✔
514
        }
18✔
515

516
        /// <summary>
517
        /// Get all cards (default only all un-archived lists on a board; use options.Filter to get archived cards)
518
        /// </summary>
519
        /// <param name="boardId">Id of the Board (in its long or short version)</param>
520
        /// <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>
521
        /// <param name="cancellationToken">CancellationToken</param>
522
        /// <returns>List of Cards</returns>
523
        public async Task<List<Card>> GetCardsOnBoardAsync(string boardId, GetCardOptions options, CancellationToken cancellationToken = default)
524
        {
525
            options.AdjustFieldsBasedOnSelectedOptions();
250✔
526
            if (options.IncludeList)
250✔
527
            {
528
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idList"))
25✔
529
                {
530
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
1✔
531
                    {
1✔
532
                        "idList"
1✔
533
                    }).ToArray();
1✔
534
                }
535
            }
536

537
            if (options.IncludeBoard)
250✔
538
            {
539
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idBoard"))
1✔
540
                {
541
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
1✔
542
                    {
1✔
543
                        "idBoard"
1✔
544
                    }).ToArray();
1✔
545
                }
546
            }
547

548
            List<Card> cards;
549
            if (options.Filter.HasValue)
250✔
550
            {
551
                cards = await _apiRequestController.Get<List<Card>>($"{GetUrlBuilder.GetCardsOnBoard(boardId)}/{options.Filter.Value.GetJsonPropertyName()}", cancellationToken, options.GetParameters(true));
2✔
552
            }
553
            else
554
            {
555
                cards = await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsOnBoard(boardId), cancellationToken, options.GetParameters(true));
248✔
556
            }
557

558
            if (options.IncludeList)
250✔
559
            {
560
                var lists = await GetListsOnBoardAsync(boardId, cancellationToken);
25✔
561
                foreach (Card card in cards)
254✔
562
                {
563
                    card.List = lists.FirstOrDefault(x => x.Id == card.ListId);
559✔
564
                }
565
            }
566

567
            if (options.IncludeBoard)
250✔
568
            {
569
                var board = await GetBoardAsync(boardId, cancellationToken);
1✔
570
                foreach (Card card in cards)
62✔
571
                {
572
                    card.Board = board;
30✔
573
                }
574
            }
575

576
            cards = FilterCards(cards, options.FilterConditions);
250✔
577
            return OrderCards(cards, options.OrderBy);
244✔
578
        }
244✔
579

580
        /// <summary>
581
        /// Get all open cards on a specific list
582
        /// </summary>
583
        /// <param name="listId">Id of the List</param>
584
        /// <param name="cancellationToken">Cancellation Token</param>
585
        /// <returns>List of Cards</returns>
586
        public async Task<List<Card>> GetCardsInListAsync(string listId, CancellationToken cancellationToken = default)
587
        {
588
            return await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsInList(listId), cancellationToken);
13✔
589
        }
13✔
590

591
        /// <summary>
592
        /// Get all open cards on a specific list
593
        /// </summary>
594
        /// <param name="listId">Id of the List</param>
595
        /// <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>
596
        /// <param name="cancellationToken">Cancellation Token</param>
597
        /// <returns>List of Cards</returns>
598
        public async Task<List<Card>> GetCardsInListAsync(string listId, GetCardOptions options, CancellationToken cancellationToken = default)
599
        {
600
            options.AdjustFieldsBasedOnSelectedOptions();
1✔
601
            if (options.IncludeBoard)
1✔
602
            {
603
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idBoard"))
1✔
604
                {
605
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
1✔
606
                    {
1✔
607
                        "idBoard"
1✔
608
                    }).ToArray();
1✔
609
                }
610
            }
611

612
            var cards = await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsInList(listId), cancellationToken, options.GetParameters(true));
1✔
613
            if (options.IncludeList)
1✔
614
            {
615
                var list = await GetListAsync(listId, cancellationToken);
1✔
616
                foreach (Card card in cards)
8✔
617
                {
618
                    card.ListId = listId;
3✔
619
                    card.List = list;
3✔
620
                }
621
            }
622

623
            if (options.IncludeBoard && cards.Count > 0)
1✔
624
            {
625
                var board = await GetBoardAsync(cards[0].BoardId, cancellationToken);
1✔
626
                foreach (Card card in cards)
8✔
627
                {
628
                    card.Board = board;
3✔
629
                }
630
            }
631

632
            cards = FilterCards(cards, options.FilterConditions);
1✔
633
            return OrderCards(cards, options.OrderBy);
1✔
634
        }
1✔
635

636
        /// <summary>
637
        /// Get the Cards in your Inbox
638
        /// </summary>
639
        /// <param name="cancellationToken">CancellationToken</param>
640
        /// <returns>The Cards</returns>
641
        public async Task<List<Card>> GetCardsInInboxAsync(CancellationToken cancellationToken = default)
642
        {
643
            TokenMemberInbox inbox = await GetTokenMemberInboxAsync(cancellationToken);
×
644
            if (inbox == null)
×
645
            {
646
                throw new TrelloApiException("Could not find your inbox", string.Empty);
×
647
            }
648

649
            return await GetCardsOnBoardAsync(inbox.BoardId, cancellationToken);
×
650
        }
×
651

652
        /// <summary>
653
        /// Get the Cards in your Inbox
654
        /// </summary>
655
        /// <param name="options">Options on what parts of the cards to get</param>
656
        /// <param name="cancellationToken">CancellationToken</param>
657
        /// <returns>The Cards</returns>
658
        public async Task<List<Card>> GetCardsInInboxAsync(GetInboxCardOptions options, CancellationToken cancellationToken = default)
659
        {
660
            TokenMemberInbox inbox = await GetTokenMemberInboxAsync(cancellationToken);
×
661
            if (inbox == null)
×
662
            {
663
                throw new TrelloApiException("Could not find your inbox", string.Empty);
×
664
            }
665

666
            GetCardOptions getCardOptions = options.ToCardOptions();
×
667
            var cards = await GetCardsOnBoardAsync(inbox.BoardId, getCardOptions, cancellationToken);
×
668
            cards = FilterCards(cards, getCardOptions.FilterConditions);
×
669
            return OrderCards(cards, getCardOptions.OrderBy);
×
670
        }
×
671

672
        /// <summary>
673
        /// Get all Cards a Member is on (across multiple boards)
674
        /// </summary>
675
        /// <param name="memberId">Id of Member</param>
676
        /// <param name="cancellationToken">Cancellation Token</param>
677
        /// <returns></returns>
678
        public async Task<List<Card>> GetCardsForMemberAsync(string memberId, CancellationToken cancellationToken = default)
679
        {
680
            return await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsForMember(memberId), cancellationToken);
1✔
681
        }
1✔
682

683
        /// <summary>
684
        /// Get all Cards a Member is on (across multiple boards)
685
        /// </summary>
686
        /// <param name="memberId">Id of Member</param>
687
        /// <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>
688
        /// <param name="cancellationToken">Cancellation Token</param>
689
        /// <returns></returns>
690
        public async Task<List<Card>> GetCardsForMemberAsync(string memberId, GetCardOptions options, CancellationToken cancellationToken = default)
691
        {
692
            options.AdjustFieldsBasedOnSelectedOptions();
1✔
693
            if (options.IncludeList)
1✔
694
            {
695
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idList"))
1✔
696
                {
697
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
1✔
698
                    {
1✔
699
                        "idList"
1✔
700
                    }).ToArray();
1✔
701
                }
702

703
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idBoard"))
1✔
704
                {
705
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
1✔
706
                    {
1✔
707
                        "idBoard"
1✔
708
                    }).ToArray();
1✔
709
                }
710
            }
711

712
            if (options.IncludeBoard)
1✔
713
            {
714
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idBoard"))
1!
715
                {
716
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
717
                    {
×
718
                        "idBoard"
×
719
                    }).ToArray();
×
720
                }
721
            }
722

723
            var cards = await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsForMember(memberId), cancellationToken, options.GetParameters(true));
1✔
724
            if (options.IncludeList)
1✔
725
            {
726
                var boardsToGetListsFor = cards.Select(x => x.BoardId).Distinct().ToArray();
10✔
727
                List<List> lists = new List<List>();
1✔
728
                foreach (var boardId in boardsToGetListsFor)
12✔
729
                {
730
                    lists.AddRange(await GetListsOnBoardAsync(boardId, cancellationToken));
5✔
731
                }
732

733
                foreach (Card card in cards)
20✔
734
                {
735
                    card.List = lists.FirstOrDefault(x => x.Id == card.ListId);
222✔
736
                }
737
            }
1✔
738

739
            if (options.IncludeBoard)
1✔
740
            {
741
                var boardIds = cards.Select(x => x.BoardId).Distinct().ToList();
10✔
742
                var boards = await GetBoardsAsync(boardIds, cancellationToken);
1✔
743
                foreach (Card card in cards)
20✔
744
                {
745
                    card.Board = boards.FirstOrDefault(x => x.Id == card.BoardId);
44✔
746
                }
747
            }
748

749
            cards = FilterCards(cards, options.FilterConditions);
1✔
750
            return OrderCards(cards, options.OrderBy);
1✔
751
        }
1✔
752

753
        /// <summary>
754
        /// Set Due Date on a card
755
        /// </summary>
756
        /// <param name="cardId">Id of the Card</param>
757
        /// <param name="dueDate">The Due Date (In UTC Time)</param>
758
        /// <param name="dueComplete">If Card is complete</param>
759
        /// <param name="cancellationToken">Cancellation Token</param>
760
        public async Task<Card> SetDueDateOnCardAsync(string cardId, DateTimeOffset dueDate, bool dueComplete = false, CancellationToken cancellationToken = default)
761
        {
762
            return await UpdateCardAsync(cardId, new List<CardUpdate>
1✔
763
            {
1✔
764
                CardUpdate.DueDate(dueDate),
1✔
765
                CardUpdate.DueComplete(dueComplete)
1✔
766
            }, cancellationToken);
1✔
767
        }
1✔
768

769
        /// <summary>
770
        /// Set Due Date on a card
771
        /// </summary>
772
        /// <param name="cardId">Id of the Card</param>
773
        /// <param name="startDate">The Start Date (In UTC Time)</param>
774
        /// <param name="cancellationToken">Cancellation Token</param>
775
        public async Task<Card> SetStartDateOnCardAsync(string cardId, DateTimeOffset startDate, CancellationToken cancellationToken = default)
776
        {
777
            return await UpdateCardAsync(cardId, new List<CardUpdate>
1✔
778
            {
1✔
779
                CardUpdate.StartDate(startDate)
1✔
780
            }, cancellationToken);
1✔
781
        }
1✔
782

783
        /// <summary>
784
        /// Set Start and Due Date on a card
785
        /// </summary>
786
        /// <param name="cardId">Id of the Card</param>
787
        /// <param name="startDate">The Start Date (In UTC Time)</param>
788
        /// <param name="dueDate">The Due Date (In UTC Time)</param>
789
        /// <param name="dueComplete">If Card is complete</param>
790
        /// <param name="cancellationToken">Cancellation Token</param> 
791
        public async Task<Card> SetStartDateAndDueDateOnCardAsync(string cardId, DateTimeOffset startDate, DateTimeOffset dueDate, bool dueComplete = false, CancellationToken cancellationToken = default)
792
        {
793
            return await UpdateCardAsync(cardId, new List<CardUpdate>
1✔
794
            {
1✔
795
                CardUpdate.StartDate(startDate),
1✔
796
                CardUpdate.DueDate(dueDate),
1✔
797
                CardUpdate.DueComplete(dueComplete)
1✔
798
            }, cancellationToken);
1✔
799
        }
1✔
800

801
        /// <summary>
802
        /// Move a Card to a new list on the same board
803
        /// </summary>
804
        /// <param name="cardId">Id of the Card</param>
805
        /// <param name="newListId">Id of the List you wish to move it to</param>
806
        /// <param name="cancellationToken">Cancellation Token</param>
807
        /// <returns></returns>
808
        public async Task<Card> MoveCardToListAsync(string cardId, string newListId, CancellationToken cancellationToken = default)
809
        {
810
            return await UpdateCardAsync(cardId, new List<CardUpdate>
1✔
811
            {
1✔
812
                CardUpdate.List(newListId)
1✔
813
            }, cancellationToken);
1✔
814
        }
1✔
815

816
        /// <summary>
817
        /// Move a Card to a new list on the same board
818
        /// </summary>
819
        /// <param name="cardId">Id of the Card</param>
820
        /// <param name="newListId">Id of the List you wish to move it to</param>
821
        /// <param name="options">Additional optional Options for the Move</param>
822
        /// <param name="cancellationToken">Cancellation Token</param>
823
        /// <returns></returns>
824
        public async Task<Card> MoveCardToListAsync(string cardId, string newListId, MoveCardToListOptions options, CancellationToken cancellationToken = default)
825
        {
826
            var parameters = new List<CardUpdate> { CardUpdate.List(newListId) };
1✔
827
            if (options.NamedPositionOnNewList.HasValue)
1!
828
            {
829
                parameters.Add(CardUpdate.Position(options.NamedPositionOnNewList.Value));
1✔
830
            }
831
            else if (options.PositionOnNewList.HasValue)
×
832
            {
833
                parameters.Add(CardUpdate.Position(options.PositionOnNewList.Value));
×
834
            }
835

836
            return await UpdateCardAsync(cardId, parameters, cancellationToken);
1✔
837
        }
1✔
838

839
        /// <summary>
840
        /// Move the Card to the top of its current list
841
        /// </summary>
842
        /// <param name="cardId">Id of the Card</param>
843
        /// <param name="cancellationToken">Cancellation Token</param>
844
        /// <returns>The Card</returns>
845
        public async Task<Card> MoveCardToTopOfCurrentListAsync(string cardId, CancellationToken cancellationToken = default)
846
        {
847
            return await UpdateCardAsync(cardId, new List<CardUpdate>
1✔
848
            {
1✔
849
                CardUpdate.Position(NamedPosition.Top)
1✔
850
            }, cancellationToken);
1✔
851
        }
1✔
852

853
        /// <summary>
854
        /// Move the Card to the bottom of its current list
855
        /// </summary>
856
        /// <param name="cardId">Id of the Card</param>
857
        /// <param name="cancellationToken">Cancellation Token</param>
858
        /// <returns>The Card</returns>
859
        public async Task<Card> MoveCardToBottomOfCurrentListAsync(string cardId, CancellationToken cancellationToken = default)
860
        {
861
            return await UpdateCardAsync(cardId, new List<CardUpdate>
1✔
862
            {
1✔
863
                CardUpdate.Position(NamedPosition.Bottom)
1✔
864
            }, cancellationToken);
1✔
865
        }
1✔
866

867
        /// <summary>
868
        /// Move a Card to another board
869
        /// </summary>
870
        /// <param name="cardId">The Id of the Card to Move</param>
871
        /// <param name="newBoardId">The ID of the New Board that the card should be moved to</param>
872
        /// <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>
873
        /// <param name="cancellationToken">Cancellation Token</param>
874
        /// <returns></returns>
875
        public async Task<Card> MoveCardToBoardAsync(string cardId, string newBoardId, MoveCardToBoardOptions options, CancellationToken cancellationToken = default)
876
        {
877
            if (options == null)
5!
878
            {
879
                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");
×
880
            }
881

882
            List<CardUpdate> parameters = new List<CardUpdate> { CardUpdate.Board(newBoardId) };
5✔
883
            var newListId = options.NewListId;
5✔
884
            if (string.IsNullOrWhiteSpace(newListId))
5!
885
            {
886
                //No list specified, so we need to find the first list on the board
887
                newListId = (await GetListsOnBoardAsync(newBoardId, cancellationToken)).OrderBy(x => x.Position).FirstOrDefault()?.Id;
×
888
            }
889

890
            parameters.Add(CardUpdate.List(newListId));
5✔
891

892
            if (options.NamedPositionOnNewList.HasValue)
5!
893
            {
894
                parameters.Add(CardUpdate.Position((options.NamedPositionOnNewList.Value)));
5✔
895
            }
896
            else if (options.PositionOnNewList.HasValue)
×
897
            {
898
                parameters.Add(CardUpdate.Position(options.PositionOnNewList.Value));
×
899
            }
900

901
            Card card = await GetCardAsync(cardId, new GetCardOptions
5✔
902
            {
5✔
903
                CardFields = new CardFields(CardFieldsType.MemberIds, CardFieldsType.LabelIds, CardFieldsType.Labels)
5✔
904
            }, cancellationToken);
5✔
905

906
            switch (options.MemberOptions)
5!
907
            {
908
                case MoveCardToBoardOptionsMemberOptions.KeepMembersAlsoOnNewBoardAndRemoveRest:
909
                    var existingMemberIdsOnNewBoard = (await GetMembersOfBoardAsync(newBoardId, cancellationToken)).Select(x => x.Id);
6✔
910
                    card.MemberIds = card.MemberIds.Intersect(existingMemberIdsOnNewBoard).ToList();
3✔
911
                    break;
3✔
912
                case MoveCardToBoardOptionsMemberOptions.RemoveAllMembersOnCard:
913
                    card.MemberIds.Clear();
2✔
914
                    break;
2✔
915
                default:
916
                    throw new ArgumentOutOfRangeException();
×
917
            }
918

919
            if (card.LabelIds.Any())
5✔
920
            {
921
                card.LabelIds.Clear();
5✔
922
                switch (options.LabelOptions)
5!
923
                {
924
                    case MoveCardToBoardOptionsLabelOptions.MigrateToLabelsOfSameNameAndColorAndCreateMissing:
925
                    {
926
                        var existingLabels = await GetLabelsOfBoardAsync(newBoardId, cancellationToken);
2✔
927
                        foreach (Label cardLabel in card.Labels)
28✔
928
                        {
929
                            Label existingLabel = existingLabels.FirstOrDefault(x => x.Name == cardLabel.Name && x.Color == cardLabel.Color);
54!
930
                            if (existingLabel != null)
12!
931
                            {
932
                                card.LabelIds.Add(existingLabel.Id);
12✔
933
                            }
934
                            else
935
                            {
936
                                //Label need to be added
937
                                Label newLabel = await AddLabelAsync(new Label(newBoardId, cardLabel.Name, cardLabel.Color), cancellationToken);
×
938
                                card.LabelIds.Add(newLabel.Id);
×
939
                            }
940
                        }
941

942
                        break;
2✔
943
                    }
944
                    case MoveCardToBoardOptionsLabelOptions.MigrateToLabelsOfSameNameAndColorAndRemoveMissing:
945
                    {
946
                        var existingLabels = await GetLabelsOfBoardAsync(newBoardId, cancellationToken);
1✔
947
                        foreach (Label cardLabel in card.Labels)
14✔
948
                        {
949
                            Label existingLabel = existingLabels.FirstOrDefault(x => x.Name == cardLabel.Name && x.Color == cardLabel.Color);
27!
950
                            if (existingLabel != null)
6✔
951
                            {
952
                                card.LabelIds.Add(existingLabel.Id);
6✔
953
                            }
954
                        }
955

956
                        break;
957
                    }
958
                    case MoveCardToBoardOptionsLabelOptions.MigrateToLabelsOfSameNameAndCreateMissing:
959
                    {
960
                        var existingLabels = await GetLabelsOfBoardAsync(newBoardId, cancellationToken);
1✔
961
                        foreach (Label cardLabel in card.Labels)
14✔
962
                        {
963
                            Label existingLabel = existingLabels.FirstOrDefault(x => x.Name == cardLabel.Name);
12✔
964
                            if (existingLabel != null)
6!
965
                            {
966
                                card.LabelIds.Add(existingLabel.Id);
6✔
967
                            }
968
                            else
969
                            {
970
                                //Label need to be added
971
                                Label newLabel = await AddLabelAsync(new Label(newBoardId, cardLabel.Name, cardLabel.Color), cancellationToken);
×
972
                                card.LabelIds.Add(newLabel.Id);
×
973
                            }
974
                        }
975

976
                        break;
1✔
977
                    }
978
                    case MoveCardToBoardOptionsLabelOptions.MigrateToLabelsOfSameNameAndRemoveMissing:
979
                    {
980
                        var existingLabels = await GetLabelsOfBoardAsync(newBoardId, cancellationToken);
×
981
                        foreach (Label cardLabel in card.Labels)
×
982
                        {
983
                            Label existingLabel = existingLabels.FirstOrDefault(x => x.Name == cardLabel.Name);
×
984
                            if (existingLabel != null)
×
985
                            {
986
                                card.LabelIds.Add(existingLabel.Id);
×
987
                            }
988
                        }
989

990
                        break;
991
                    }
992
                    case MoveCardToBoardOptionsLabelOptions.RemoveAllLabelsOnCard:
993
                        //No more Work needed
994
                        break;
995
                    default:
996
                        throw new ArgumentOutOfRangeException();
×
997
                }
998
            }
999

1000
            parameters.Add(CardUpdate.Labels(card.LabelIds));
5✔
1001
            parameters.Add(CardUpdate.Members(card.MemberIds));
5✔
1002

1003
            if (options.RemoveDueDate)
5✔
1004
            {
1005
                parameters.Add(CardUpdate.DueDate(null));
4✔
1006
            }
1007

1008
            if (options.RemoveStartDate)
5✔
1009
            {
1010
                parameters.Add(CardUpdate.StartDate(null));
4✔
1011
            }
1012

1013
            return await UpdateCardAsync(cardId, parameters, cancellationToken);
5✔
1014
        }
5✔
1015

1016
        private static string GeneratePayloadForCoverUpdate(CardCover cardCover, List<QueryParameter> parameters)
1017
        {
1018
            //Special code for Cover
1019
            string payload = string.Empty;
5✔
1020
            if (cardCover == null)
5✔
1021
            {
1022
                //Remove cover
1023
                parameters.Add(new QueryParameter("cover", ""));
2✔
1024
            }
1025
            else
1026
            {
1027
                cardCover.PrepareForAddUpdate();
3✔
1028
                if (cardCover.Color != null || cardCover.BackgroundImageId != null)
3!
1029
                {
1030
                    QueryParameter queryParameter = parameters.FirstOrDefault(x => x.Name == "idAttachmentCover");
27✔
1031
                    if (queryParameter != null)
3!
1032
                    {
1033
                        parameters.Remove(queryParameter); //This parameter can't be there while a cover is added
×
1034
                    }
1035
                }
1036

1037
                payload = $"{{\"cover\":{JsonSerializer.Serialize(cardCover)}}}";
3✔
1038
            }
1039

1040
            return payload;
5✔
1041
        }
1042
    }
1043
}
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