@remit/backend 0.0.99 → 0.0.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { describe, test } from "node:test";
|
|
3
|
+
import type { MessageCategory } from "@remit/api-openapi-types";
|
|
3
4
|
import type {
|
|
4
5
|
AccountItem,
|
|
5
6
|
AccountSettingItem,
|
|
6
7
|
MailboxItem,
|
|
8
|
+
SearchOptions,
|
|
7
9
|
} from "@remit/data-ports";
|
|
8
10
|
import {
|
|
9
11
|
buildInboxMailboxMap,
|
|
@@ -11,7 +13,10 @@ import {
|
|
|
11
13
|
buildListStarredThreadsOptions,
|
|
12
14
|
buildSearchAllThreadsOptions,
|
|
13
15
|
dedupeByMessageId,
|
|
16
|
+
executeUnifiedThreadListing,
|
|
14
17
|
type InboxMapClient,
|
|
18
|
+
type UnifiedThreadClient,
|
|
19
|
+
type UnifiedThreadParams,
|
|
15
20
|
} from "./unified-threads.js";
|
|
16
21
|
|
|
17
22
|
// #44: Flagged filtered client-side over the newest 50 unified-inbox rows, so a
|
|
@@ -432,3 +437,162 @@ describe("buildSearchAllThreadsOptions", () => {
|
|
|
432
437
|
assert.equal(search.limit, unified.limit);
|
|
433
438
|
});
|
|
434
439
|
});
|
|
440
|
+
|
|
441
|
+
// #308: the cross-account collections filtered and counted in the browser, over
|
|
442
|
+
// the pages a client happened to have loaded. Both are the query's job now.
|
|
443
|
+
describe("executeUnifiedThreadListing", () => {
|
|
444
|
+
interface Call {
|
|
445
|
+
mode: "listByDate" | "listByStarred" | "searchByDate";
|
|
446
|
+
search?: SearchOptions;
|
|
447
|
+
limit?: number;
|
|
448
|
+
continuationToken?: string;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const emptyPage = { items: [], continuationToken: undefined };
|
|
452
|
+
|
|
453
|
+
const buildListingClient = (calls: Call[], count = 0) =>
|
|
454
|
+
({
|
|
455
|
+
account: { listAllByAccountConfig: async () => [account("a1")] },
|
|
456
|
+
mailbox: {
|
|
457
|
+
listAllByAccount: async () => [
|
|
458
|
+
mailbox("m-inbox", "a1", "INBOX"),
|
|
459
|
+
mailbox("m-archive", "a1", "Archive"),
|
|
460
|
+
],
|
|
461
|
+
},
|
|
462
|
+
accountSetting: { listByAccountConfig: async () => [] },
|
|
463
|
+
message: { get: async () => [] },
|
|
464
|
+
address: { getAddress: async () => [] },
|
|
465
|
+
messageLabel: { listByMessageIds: async () => [] },
|
|
466
|
+
label: { listByAccountConfig: async () => [] },
|
|
467
|
+
threadMessage: {
|
|
468
|
+
listByDate: async (
|
|
469
|
+
_config: string,
|
|
470
|
+
options?: { search?: SearchOptions; limit?: number },
|
|
471
|
+
) => {
|
|
472
|
+
calls.push({ mode: "listByDate", ...options });
|
|
473
|
+
return emptyPage;
|
|
474
|
+
},
|
|
475
|
+
listByStarred: async (
|
|
476
|
+
_config: string,
|
|
477
|
+
options?: { search?: SearchOptions; limit?: number },
|
|
478
|
+
) => {
|
|
479
|
+
calls.push({ mode: "listByStarred", ...options });
|
|
480
|
+
return emptyPage;
|
|
481
|
+
},
|
|
482
|
+
searchByDate: async (
|
|
483
|
+
_config: string,
|
|
484
|
+
search: SearchOptions,
|
|
485
|
+
options?: { limit?: number },
|
|
486
|
+
) => {
|
|
487
|
+
calls.push({ mode: "searchByDate", search, ...options });
|
|
488
|
+
return emptyPage;
|
|
489
|
+
},
|
|
490
|
+
countThreadsInScope: async () => count,
|
|
491
|
+
},
|
|
492
|
+
}) as unknown as UnifiedThreadClient;
|
|
493
|
+
|
|
494
|
+
const params = (
|
|
495
|
+
overrides: Partial<UnifiedThreadParams> = {},
|
|
496
|
+
): UnifiedThreadParams => ({
|
|
497
|
+
starredOnly: false,
|
|
498
|
+
count: false,
|
|
499
|
+
results: true,
|
|
500
|
+
...overrides,
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
test("sends the row filters into the starred query", async () => {
|
|
504
|
+
const calls: Call[] = [];
|
|
505
|
+
await executeUnifiedThreadListing(buildListingClient(calls), CONFIG_ID, {
|
|
506
|
+
...params({ starredOnly: true }),
|
|
507
|
+
category: ["social", "personal"] as MessageCategory[],
|
|
508
|
+
unread: true,
|
|
509
|
+
attachments: true,
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
assert.equal(calls.length, 1);
|
|
513
|
+
assert.equal(calls[0].mode, "listByStarred");
|
|
514
|
+
assert.deepEqual(calls[0].search, {
|
|
515
|
+
starred: true,
|
|
516
|
+
category: ["social", "personal"],
|
|
517
|
+
unread: true,
|
|
518
|
+
attachments: true,
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
test("sends the row filters into the INBOX query", async () => {
|
|
523
|
+
const calls: Call[] = [];
|
|
524
|
+
await executeUnifiedThreadListing(buildListingClient(calls), CONFIG_ID, {
|
|
525
|
+
...params(),
|
|
526
|
+
category: ["uncategorized"] as MessageCategory[],
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
assert.equal(calls[0].mode, "listByDate");
|
|
530
|
+
assert.deepEqual(calls[0].search, { category: ["uncategorized"] });
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
test("sends the row filters into the search query", async () => {
|
|
534
|
+
const calls: Call[] = [];
|
|
535
|
+
await executeUnifiedThreadListing(buildListingClient(calls), CONFIG_ID, {
|
|
536
|
+
...params({ starredOnly: true }),
|
|
537
|
+
searchText: "invoice",
|
|
538
|
+
unread: true,
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
assert.equal(calls[0].mode, "searchByDate");
|
|
542
|
+
assert.deepEqual(calls[0].search, {
|
|
543
|
+
query: "invoice",
|
|
544
|
+
starred: true,
|
|
545
|
+
unread: true,
|
|
546
|
+
});
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// The defect the count replaces: a page-length figure grew with every press
|
|
550
|
+
// of "load more" while the header presented it as a total.
|
|
551
|
+
test("counts the same however far the caller has paged", async () => {
|
|
552
|
+
const calls: Call[] = [];
|
|
553
|
+
const first = await executeUnifiedThreadListing(
|
|
554
|
+
buildListingClient(calls, 42),
|
|
555
|
+
CONFIG_ID,
|
|
556
|
+
params({ starredOnly: true, count: true, unread: true }),
|
|
557
|
+
);
|
|
558
|
+
const later = await executeUnifiedThreadListing(
|
|
559
|
+
buildListingClient(calls, 42),
|
|
560
|
+
CONFIG_ID,
|
|
561
|
+
params({
|
|
562
|
+
starredOnly: true,
|
|
563
|
+
count: true,
|
|
564
|
+
unread: true,
|
|
565
|
+
continuationToken: "page-3",
|
|
566
|
+
limit: 10,
|
|
567
|
+
}),
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
assert.equal(first.count, 42);
|
|
571
|
+
assert.equal(later.count, 42);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
test("reads the count alone when results are not wanted", async () => {
|
|
575
|
+
const calls: Call[] = [];
|
|
576
|
+
const response = await executeUnifiedThreadListing(
|
|
577
|
+
buildListingClient(calls, 7),
|
|
578
|
+
CONFIG_ID,
|
|
579
|
+
params({ starredOnly: true, count: true, results: false }),
|
|
580
|
+
);
|
|
581
|
+
|
|
582
|
+
assert.deepEqual(calls, []);
|
|
583
|
+
assert.equal(response.items, undefined);
|
|
584
|
+
assert.equal(response.count, 7);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
test("omits the count when it was not asked for", async () => {
|
|
588
|
+
const calls: Call[] = [];
|
|
589
|
+
const response = await executeUnifiedThreadListing(
|
|
590
|
+
buildListingClient(calls, 7),
|
|
591
|
+
CONFIG_ID,
|
|
592
|
+
params({ starredOnly: true }),
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
assert.equal(response.count, undefined);
|
|
596
|
+
assert.deepEqual(response.items, []);
|
|
597
|
+
});
|
|
598
|
+
});
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MessageCategory,
|
|
3
|
+
ThreadSearchResponse,
|
|
4
|
+
} from "@remit/api-openapi-types";
|
|
1
5
|
import type {
|
|
2
6
|
AccountItem,
|
|
3
7
|
IAccountSettingRepository,
|
|
4
8
|
MailboxItem,
|
|
9
|
+
ResultList,
|
|
10
|
+
SearchOptions,
|
|
11
|
+
ThreadMessageItem,
|
|
5
12
|
} from "@remit/data-ports";
|
|
6
13
|
import { isVirtualCopyMailbox } from "@remit/data-ports/virtual-copy";
|
|
7
14
|
import { MailboxSpecialUse } from "@remit/domain-enums";
|
|
@@ -9,7 +16,10 @@ import type { APIGatewayProxyEvent } from "aws-lambda";
|
|
|
9
16
|
import type { Context } from "openapi-backend";
|
|
10
17
|
import pMap from "p-map";
|
|
11
18
|
import { getAccountConfigIdFromEvent } from "../auth.js";
|
|
12
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
type EnrichClient,
|
|
21
|
+
enrichThreadRows,
|
|
22
|
+
} from "../derive/enrichThreadRows.js";
|
|
13
23
|
import { getClient } from "../service/data-client.js";
|
|
14
24
|
import type { OperationHandler, UnifiedThreadOperationIds } from "../types.js";
|
|
15
25
|
import {
|
|
@@ -209,12 +219,14 @@ export const buildListAllThreadsOptions = (
|
|
|
209
219
|
limit?: number;
|
|
210
220
|
},
|
|
211
221
|
inboxMailboxIds: Set<string>,
|
|
222
|
+
search?: SearchOptions,
|
|
212
223
|
) => ({
|
|
213
224
|
order: query.order ?? ("desc" as const),
|
|
214
225
|
continuationToken: query.continuationToken,
|
|
215
226
|
limit: query.limit ?? DEFAULT_UNIFIED_THREADS_PAGE_SIZE,
|
|
216
227
|
inboxMailboxIds,
|
|
217
228
|
excludeDeleted: true,
|
|
229
|
+
search,
|
|
218
230
|
});
|
|
219
231
|
|
|
220
232
|
/**
|
|
@@ -231,12 +243,14 @@ export const buildListStarredThreadsOptions = (
|
|
|
231
243
|
limit?: number;
|
|
232
244
|
},
|
|
233
245
|
starredMailboxIds: Set<string>,
|
|
246
|
+
search?: SearchOptions,
|
|
234
247
|
) => ({
|
|
235
248
|
order: query.order ?? ("desc" as const),
|
|
236
249
|
continuationToken: query.continuationToken,
|
|
237
250
|
limit: query.limit ?? DEFAULT_UNIFIED_THREADS_PAGE_SIZE,
|
|
238
251
|
mailboxIds: starredMailboxIds,
|
|
239
252
|
excludeDeleted: true,
|
|
253
|
+
search,
|
|
240
254
|
});
|
|
241
255
|
|
|
242
256
|
/**
|
|
@@ -311,75 +325,140 @@ export const attachAccountIds = (
|
|
|
311
325
|
accountId: mailboxIdToAccountId.get(row.mailboxId),
|
|
312
326
|
}));
|
|
313
327
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
328
|
+
/**
|
|
329
|
+
* The row criteria the listing carries, whichever mode answers it.
|
|
330
|
+
*
|
|
331
|
+
* `category`, `unread` and `attachments` are columns on the ThreadMessage row,
|
|
332
|
+
* so each is a predicate inside the query. Filtering the rows a page returned
|
|
333
|
+
* instead is the defect this replaces: a category whose mail sits below the
|
|
334
|
+
* newest page rendered an empty list however much of it the collection held
|
|
335
|
+
* (#308). `starred` and `query` are in here too so one `SearchOptions` says
|
|
336
|
+
* what the whole request narrows by, which is what lets the count run the same
|
|
337
|
+
* predicate as the listing.
|
|
338
|
+
*/
|
|
339
|
+
export const buildUnifiedThreadSearch = (params: {
|
|
340
|
+
starredOnly: boolean;
|
|
341
|
+
searchText?: string;
|
|
342
|
+
category?: MessageCategory[];
|
|
343
|
+
unread?: boolean;
|
|
344
|
+
attachments?: boolean;
|
|
345
|
+
}): SearchOptions => ({
|
|
346
|
+
...(params.searchText ? { query: params.searchText } : {}),
|
|
347
|
+
...(params.starredOnly ? { starred: true } : {}),
|
|
348
|
+
...(params.category?.length ? { category: params.category } : {}),
|
|
349
|
+
...(params.unread !== undefined ? { unread: params.unread } : {}),
|
|
350
|
+
...(params.attachments !== undefined
|
|
351
|
+
? { attachments: params.attachments }
|
|
352
|
+
: {}),
|
|
353
|
+
});
|
|
337
354
|
|
|
338
|
-
|
|
355
|
+
/**
|
|
356
|
+
* Minimal client surface `executeUnifiedThreadListing` needs, declared
|
|
357
|
+
* structurally (like `ThreadSearchClient`) so the mode selection, the row
|
|
358
|
+
* filters and the count are testable with an in-memory fake.
|
|
359
|
+
*/
|
|
360
|
+
export interface UnifiedThreadClient extends EnrichClient, InboxMapClient {
|
|
361
|
+
threadMessage: {
|
|
362
|
+
listByDate(
|
|
363
|
+
accountConfigId: string,
|
|
364
|
+
options?: ReturnType<typeof buildListAllThreadsOptions>,
|
|
365
|
+
): Promise<ResultList<ThreadMessageItem>>;
|
|
366
|
+
listByStarred(
|
|
367
|
+
accountConfigId: string,
|
|
368
|
+
options?: ReturnType<typeof buildListStarredThreadsOptions>,
|
|
369
|
+
): Promise<ResultList<ThreadMessageItem>>;
|
|
370
|
+
searchByDate(
|
|
371
|
+
accountConfigId: string,
|
|
372
|
+
search: SearchOptions,
|
|
373
|
+
options?: ReturnType<typeof buildSearchAllThreadsOptions>,
|
|
374
|
+
): Promise<ResultList<ThreadMessageItem>>;
|
|
375
|
+
countThreadsInScope(
|
|
376
|
+
accountConfigId: string,
|
|
377
|
+
search: SearchOptions,
|
|
378
|
+
options?: { mailboxIds?: Set<string>; excludeDeleted?: boolean },
|
|
379
|
+
): Promise<number>;
|
|
380
|
+
};
|
|
381
|
+
}
|
|
339
382
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
383
|
+
export type UnifiedThreadParams = {
|
|
384
|
+
continuationToken?: string;
|
|
385
|
+
order?: "asc" | "desc";
|
|
386
|
+
limit?: number;
|
|
387
|
+
starredOnly: boolean;
|
|
388
|
+
searchText?: string;
|
|
389
|
+
category?: MessageCategory[];
|
|
390
|
+
unread?: boolean;
|
|
391
|
+
attachments?: boolean;
|
|
392
|
+
count: boolean;
|
|
393
|
+
results: boolean;
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Run the cross-account listing: pick the mode, apply the row criteria inside
|
|
398
|
+
* the query, and optionally count the matches.
|
|
399
|
+
*
|
|
400
|
+
* `count` names the whole match, never the page: a page size bounds the rows a
|
|
401
|
+
* response carries and has no bearing on how much matches, so pressing "load
|
|
402
|
+
* more" cannot move it. It counts conversations, the unit the listing renders
|
|
403
|
+
* once its per-mailbox rows are collapsed by `threadId`. `results: false` reads
|
|
404
|
+
* the count alone, which is how a header total is fetched without also paying
|
|
405
|
+
* for a page of mail.
|
|
406
|
+
*/
|
|
407
|
+
export const executeUnifiedThreadListing = async (
|
|
408
|
+
client: UnifiedThreadClient,
|
|
409
|
+
accountConfigId: string,
|
|
410
|
+
params: UnifiedThreadParams,
|
|
411
|
+
): Promise<ThreadSearchResponse> => {
|
|
412
|
+
const searching =
|
|
413
|
+
params.searchText !== undefined && params.searchText.length > 0;
|
|
414
|
+
const {
|
|
415
|
+
mailboxIdToAccountId,
|
|
416
|
+
inboxMailboxIds,
|
|
417
|
+
starredMailboxIds,
|
|
418
|
+
searchMailboxIds,
|
|
419
|
+
virtualCopyMailboxIds,
|
|
420
|
+
} = await buildInboxMailboxMap(accountConfigId, client);
|
|
359
421
|
|
|
422
|
+
// Search widens past INBOX to every folder it may reach; `starred=true`
|
|
423
|
+
// still narrows it to the starred scope, so the two compose.
|
|
424
|
+
const searchScope = params.starredOnly ? starredMailboxIds : searchMailboxIds;
|
|
425
|
+
const scope = searching
|
|
426
|
+
? searchScope
|
|
427
|
+
: params.starredOnly
|
|
428
|
+
? starredMailboxIds
|
|
429
|
+
: inboxMailboxIds;
|
|
430
|
+
|
|
431
|
+
const search = buildUnifiedThreadSearch(params);
|
|
432
|
+
const page = {
|
|
433
|
+
continuationToken: params.continuationToken,
|
|
434
|
+
order: params.order,
|
|
435
|
+
limit: params.limit,
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
if (scope.size === 0) {
|
|
439
|
+
return {
|
|
440
|
+
...(params.results ? { items: [] } : {}),
|
|
441
|
+
...(params.count ? { count: 0 } : {}),
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const response: ThreadSearchResponse = {};
|
|
446
|
+
|
|
447
|
+
if (params.results) {
|
|
360
448
|
const result = searching
|
|
361
449
|
? await client.threadMessage.searchByDate(
|
|
362
450
|
accountConfigId,
|
|
363
|
-
|
|
364
|
-
buildSearchAllThreadsOptions(
|
|
365
|
-
{ continuationToken, order, limit },
|
|
366
|
-
searchScope,
|
|
367
|
-
),
|
|
451
|
+
search,
|
|
452
|
+
buildSearchAllThreadsOptions(page, searchScope),
|
|
368
453
|
)
|
|
369
|
-
: starredOnly
|
|
454
|
+
: params.starredOnly
|
|
370
455
|
? await client.threadMessage.listByStarred(
|
|
371
456
|
accountConfigId,
|
|
372
|
-
buildListStarredThreadsOptions(
|
|
373
|
-
{ continuationToken, order, limit },
|
|
374
|
-
starredMailboxIds,
|
|
375
|
-
),
|
|
457
|
+
buildListStarredThreadsOptions(page, starredMailboxIds, search),
|
|
376
458
|
)
|
|
377
459
|
: await client.threadMessage.listByDate(
|
|
378
460
|
accountConfigId,
|
|
379
|
-
buildListAllThreadsOptions(
|
|
380
|
-
{ continuationToken, order, limit },
|
|
381
|
-
inboxMailboxIds,
|
|
382
|
-
),
|
|
461
|
+
buildListAllThreadsOptions(page, inboxMailboxIds, search),
|
|
383
462
|
);
|
|
384
463
|
|
|
385
464
|
// Search spans folders, so it is the one mode that can see the same mail
|
|
@@ -393,11 +472,97 @@ export const UnifiedThreadOperations: Record<
|
|
|
393
472
|
: result.items;
|
|
394
473
|
|
|
395
474
|
const enriched = await enrichThreadRows(rows, client, accountConfigId);
|
|
396
|
-
|
|
475
|
+
response.items = attachAccountIds(enriched, mailboxIdToAccountId);
|
|
476
|
+
response.continuationToken = result.continuationToken;
|
|
477
|
+
}
|
|
397
478
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
479
|
+
if (params.count) {
|
|
480
|
+
response.count = await client.threadMessage.countThreadsInScope(
|
|
481
|
+
accountConfigId,
|
|
482
|
+
search,
|
|
483
|
+
{ mailboxIds: scope, excludeDeleted: true },
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return response;
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
const toArray = <T>(value: T | T[] | undefined): T[] | undefined => {
|
|
491
|
+
if (value === undefined) return undefined;
|
|
492
|
+
return Array.isArray(value) ? value : [value];
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* A query-string boolean, as three states.
|
|
497
|
+
*
|
|
498
|
+
* Query strings carry text; openapi-backend coerces where the schema says
|
|
499
|
+
* boolean and leaves the raw string where it cannot, so both forms are read.
|
|
500
|
+
* Anything else — absent, or a value that is neither — is `undefined`, and
|
|
501
|
+
* every caller below decides what that means for its own parameter. Folding
|
|
502
|
+
* "not stated" into `false` would turn an absent `unread` into "only read
|
|
503
|
+
* mail": a filter nobody asked for, and the opposite of the one they might
|
|
504
|
+
* have meant.
|
|
505
|
+
*/
|
|
506
|
+
const toBoolean = (
|
|
507
|
+
value: boolean | string | undefined,
|
|
508
|
+
): boolean | undefined => {
|
|
509
|
+
if (value === true || value === "true") return true;
|
|
510
|
+
if (value === false || value === "false") return false;
|
|
511
|
+
return undefined;
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
export const UnifiedThreadOperations: Record<
|
|
515
|
+
UnifiedThreadOperationIds,
|
|
516
|
+
OperationHandler<UnifiedThreadOperationIds>
|
|
517
|
+
> = {
|
|
518
|
+
UnifiedThreadOperations_listAllThreads: async (
|
|
519
|
+
context: Context,
|
|
520
|
+
...args: unknown[]
|
|
521
|
+
) => {
|
|
522
|
+
const event = args[0] as APIGatewayProxyEvent;
|
|
523
|
+
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
524
|
+
const {
|
|
525
|
+
continuationToken,
|
|
526
|
+
order,
|
|
527
|
+
limit,
|
|
528
|
+
starred,
|
|
529
|
+
query,
|
|
530
|
+
category,
|
|
531
|
+
unread,
|
|
532
|
+
attachments,
|
|
533
|
+
count,
|
|
534
|
+
results,
|
|
535
|
+
} = context.request.query as {
|
|
536
|
+
continuationToken?: string;
|
|
537
|
+
order?: "asc" | "desc";
|
|
538
|
+
limit?: number;
|
|
539
|
+
starred?: boolean | string;
|
|
540
|
+
query?: string;
|
|
541
|
+
category?: MessageCategory | MessageCategory[];
|
|
542
|
+
unread?: boolean | string;
|
|
543
|
+
attachments?: boolean | string;
|
|
544
|
+
count?: boolean | string;
|
|
545
|
+
results?: boolean | string;
|
|
401
546
|
};
|
|
547
|
+
|
|
548
|
+
// Whitespace-only text is not a search: it would widen the scope to every
|
|
549
|
+
// folder while matching nothing in particular.
|
|
550
|
+
const searchText = query?.trim();
|
|
551
|
+
|
|
552
|
+
return executeUnifiedThreadListing(await getClient(), accountConfigId, {
|
|
553
|
+
continuationToken,
|
|
554
|
+
order,
|
|
555
|
+
limit,
|
|
556
|
+
// Absent means unstated for all five, and each says what it does with
|
|
557
|
+
// that: the three filters drop out of the predicate, `count` is off
|
|
558
|
+
// unless it is asked for, and `results` is on unless it is refused.
|
|
559
|
+
starredOnly: toBoolean(starred) === true,
|
|
560
|
+
searchText: searchText || undefined,
|
|
561
|
+
category: toArray(category),
|
|
562
|
+
unread: toBoolean(unread),
|
|
563
|
+
attachments: toBoolean(attachments),
|
|
564
|
+
count: toBoolean(count) === true,
|
|
565
|
+
results: toBoolean(results) !== false,
|
|
566
|
+
});
|
|
402
567
|
},
|
|
403
568
|
};
|