@remit/backend 0.0.143 → 0.0.144
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/dist/dev-server/content-handler.d.ts +38 -0
- package/dist/dev-server/content-handler.js +46 -0
- package/dist/src/api.d.ts +7 -0
- package/dist/src/api.js +144 -0
- package/dist/src/auth.d.ts +30 -0
- package/dist/src/auth.js +73 -0
- package/dist/src/config/msoauth.d.ts +30 -0
- package/dist/src/config/msoauth.js +30 -0
- package/{src/data-backend.ts → dist/src/data-backend.d.ts} +2 -4
- package/dist/src/data-backend.js +12 -0
- package/{src/derive/autoMoved.ts → dist/src/derive/autoMoved.d.ts} +1 -29
- package/dist/src/derive/autoMoved.js +49 -0
- package/dist/src/derive/contentSignature.d.ts +41 -0
- package/{src/derive/contentSignature.ts → dist/src/derive/contentSignature.js} +27 -72
- package/dist/src/derive/contentUrl.d.ts +46 -0
- package/dist/src/derive/contentUrl.js +46 -0
- package/{src/derive/deriveMuted.ts → dist/src/derive/deriveMuted.d.ts} +1 -3
- package/dist/src/derive/deriveMuted.js +7 -0
- package/dist/src/derive/enrichThreadRows.d.ts +118 -0
- package/dist/src/derive/enrichThreadRows.js +151 -0
- package/dist/src/derive/filterThreadCriteria.d.ts +23 -0
- package/dist/src/derive/filterThreadCriteria.js +24 -0
- package/{src/derive/pendingMoveCounts.ts → dist/src/derive/pendingMoveCounts.d.ts} +5 -53
- package/dist/src/derive/pendingMoveCounts.js +53 -0
- package/{src/derive/senderTrust.ts → dist/src/derive/senderTrust.d.ts} +1 -9
- package/dist/src/derive/senderTrust.js +19 -0
- package/dist/src/error.d.ts +21 -0
- package/dist/src/error.js +85 -0
- package/dist/src/handlers/account-guards.d.ts +36 -0
- package/dist/src/handlers/account-guards.js +107 -0
- package/dist/src/handlers/account-oauth.d.ts +14 -0
- package/dist/src/handlers/account-oauth.js +387 -0
- package/dist/src/handlers/account-overrides.d.ts +53 -0
- package/dist/src/handlers/account-overrides.js +233 -0
- package/dist/src/handlers/account-signature.d.ts +28 -0
- package/dist/src/handlers/account-signature.js +85 -0
- package/dist/src/handlers/account.d.ts +6 -0
- package/dist/src/handlers/account.js +350 -0
- package/dist/src/handlers/address.d.ts +35 -0
- package/dist/src/handlers/address.js +141 -0
- package/dist/src/handlers/calendar-event.d.ts +70 -0
- package/dist/src/handlers/calendar-event.js +425 -0
- package/dist/src/handlers/calendar-feed.d.ts +47 -0
- package/dist/src/handlers/calendar-feed.js +192 -0
- package/dist/src/handlers/calendar-suggestion.d.ts +47 -0
- package/dist/src/handlers/calendar-suggestion.js +212 -0
- package/dist/src/handlers/calendar.d.ts +116 -0
- package/dist/src/handlers/calendar.js +330 -0
- package/dist/src/handlers/config.d.ts +43 -0
- package/dist/src/handlers/config.js +176 -0
- package/dist/src/handlers/ensure-account-config.d.ts +9 -0
- package/dist/src/handlers/ensure-account-config.js +27 -0
- package/dist/src/handlers/filter.d.ts +3 -0
- package/dist/src/handlers/filter.js +103 -0
- package/dist/src/handlers/folder-role-appointments.d.ts +68 -0
- package/dist/src/handlers/folder-role-appointments.js +168 -0
- package/dist/src/handlers/folder-role-labels.d.ts +26 -0
- package/dist/src/handlers/folder-role-labels.js +40 -0
- package/dist/src/handlers/folder-role.d.ts +9 -0
- package/dist/src/handlers/folder-role.js +40 -0
- package/dist/src/handlers/index.d.ts +2 -0
- package/dist/src/handlers/index.js +60 -0
- package/dist/src/handlers/label.d.ts +43 -0
- package/dist/src/handlers/label.js +114 -0
- package/dist/src/handlers/mailbox-bindings.d.ts +41 -0
- package/dist/src/handlers/mailbox-bindings.js +61 -0
- package/dist/src/handlers/mailbox.d.ts +23 -0
- package/dist/src/handlers/mailbox.js +219 -0
- package/dist/src/handlers/me.d.ts +2 -0
- package/dist/src/handlers/me.js +138 -0
- package/dist/src/handlers/message.d.ts +84 -0
- package/dist/src/handlers/message.js +607 -0
- package/dist/src/handlers/organize.d.ts +10 -0
- package/dist/src/handlers/organize.js +112 -0
- package/dist/src/handlers/outbox-attachment.d.ts +23 -0
- package/dist/src/handlers/outbox-attachment.js +81 -0
- package/dist/src/handlers/outbox.d.ts +4 -0
- package/dist/src/handlers/outbox.js +144 -0
- package/dist/src/handlers/sync-progress.d.ts +46 -0
- package/dist/src/handlers/sync-progress.js +63 -0
- package/dist/src/handlers/sync.d.ts +28 -0
- package/dist/src/handlers/sync.js +82 -0
- package/dist/src/handlers/system-update.d.ts +2 -0
- package/dist/src/handlers/system-update.js +172 -0
- package/dist/src/handlers/thread.d.ts +148 -0
- package/dist/src/handlers/thread.js +233 -0
- package/dist/src/handlers/unified-threads.d.ts +247 -0
- package/dist/src/handlers/unified-threads.js +409 -0
- package/{src/handlers/vip-suggestions.ts → dist/src/handlers/vip-suggestions.d.ts} +1 -13
- package/dist/src/handlers/vip-suggestions.js +16 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +37 -0
- package/{src/json.ts → dist/src/json.d.ts} +1 -4
- package/dist/src/json.js +10 -0
- package/dist/src/jwt-auth.d.ts +18 -0
- package/dist/src/jwt-auth.js +97 -0
- package/dist/src/request-context.d.ts +9 -0
- package/dist/src/request-context.js +22 -0
- package/dist/src/request.d.ts +3 -0
- package/dist/src/request.js +7 -0
- package/dist/src/response.d.ts +27 -0
- package/dist/src/response.js +87 -0
- package/dist/src/service/account-deletion.d.ts +22 -0
- package/dist/src/service/account-deletion.js +39 -0
- package/dist/src/service/compose-sqlite.d.ts +2 -0
- package/dist/src/service/compose-sqlite.js +11 -0
- package/dist/src/service/create-remit-client.d.ts +95 -0
- package/dist/src/service/create-remit-client.js +207 -0
- package/dist/src/service/data-client.d.ts +12 -0
- package/dist/src/service/data-client.js +44 -0
- package/dist/src/service/export-identity.d.ts +3 -0
- package/dist/src/service/export-identity.js +35 -0
- package/dist/src/service/fire-and-forget.d.ts +54 -0
- package/dist/src/service/fire-and-forget.js +38 -0
- package/dist/src/service/organize.d.ts +184 -0
- package/dist/src/service/organize.js +384 -0
- package/dist/src/service/refusal.d.ts +4 -0
- package/dist/src/service/refusal.js +20 -0
- package/dist/src/service/sender-category.d.ts +51 -0
- package/{src/service/sender-category.ts → dist/src/service/sender-category.js} +40 -81
- package/dist/src/service/sqs.d.ts +1 -0
- package/{src/service/sqs.ts → dist/src/service/sqs.js} +0 -1
- package/dist/src/service/trigger-sync.d.ts +51 -0
- package/dist/src/service/trigger-sync.js +39 -0
- package/dist/src/types.d.ts +49 -0
- package/dist/src/types.js +1 -0
- package/package.json +113 -98
- package/.env.test +0 -4
- package/dev-server/content-auth.test.ts +0 -113
- package/dev-server/content-auth.ts +0 -54
- package/dev-server/content-handler.test.ts +0 -105
- package/dev-server/content-handler.ts +0 -79
- package/dev-server/content-path.test.ts +0 -37
- package/dev-server/content-path.ts +0 -27
- package/dev-server/cors.test.ts +0 -48
- package/dev-server/cors.ts +0 -25
- package/dev-server/keep-alive.test.ts +0 -24
- package/dev-server/lambda-helpers.ts +0 -69
- package/dev-server/log-output.test.ts +0 -173
- package/dev-server/oauth-callback-route.test.ts +0 -66
- package/dev-server/relational-health.test.ts +0 -83
- package/dev-server/relational-health.ts +0 -39
- package/dev-server/server.ts +0 -384
- package/dev-server/sync-age.test.ts +0 -91
- package/dev-server/sync-age.ts +0 -55
- package/dev-server/upload-handler.ts +0 -132
- package/dev-server/upload-route.test.ts +0 -196
- package/scripts/backfill-classification.ts +0 -91
- package/scripts/backfill-list-id.ts +0 -83
- package/scripts/config-save.ts +0 -128
- package/src/auth.test.ts +0 -12
- package/src/auth.ts +0 -92
- package/src/config/msoauth.test.ts +0 -48
- package/src/config/msoauth.ts +0 -48
- package/src/data-backend.test.ts +0 -23
- package/src/derive/autoMoved.test.ts +0 -104
- package/src/derive/contentSignature.test.ts +0 -161
- package/src/derive/contentUrl.test.ts +0 -172
- package/src/derive/contentUrl.ts +0 -86
- package/src/derive/deriveMuted.test.ts +0 -34
- package/src/derive/enrichThreadRows.test.ts +0 -360
- package/src/derive/enrichThreadRows.ts +0 -234
- package/src/derive/filterThreadCriteria.test.ts +0 -126
- package/src/derive/filterThreadCriteria.ts +0 -51
- package/src/derive/senderTrust.test.ts +0 -67
- package/src/error.test.ts +0 -201
- package/src/error.ts +0 -123
- package/src/handlers/account-guards.test.ts +0 -67
- package/src/handlers/account-guards.ts +0 -169
- package/src/handlers/account-oauth-callback.test.ts +0 -155
- package/src/handlers/account-oauth-consent.test.ts +0 -336
- package/src/handlers/account-oauth-start.test.ts +0 -191
- package/src/handlers/account-oauth.test.ts +0 -439
- package/src/handlers/account-oauth.ts +0 -635
- package/src/handlers/account-overrides.test.ts +0 -88
- package/src/handlers/account-overrides.ts +0 -323
- package/src/handlers/account-ownership.ts +0 -25
- package/src/handlers/account-signature.ts +0 -117
- package/src/handlers/account-synced-services.test.ts +0 -245
- package/src/handlers/account.ts +0 -552
- package/src/handlers/address.test.ts +0 -381
- package/src/handlers/address.ts +0 -200
- package/src/handlers/calendar-event.test.ts +0 -685
- package/src/handlers/calendar-event.ts +0 -738
- package/src/handlers/calendar-feed-validators.test.ts +0 -228
- package/src/handlers/calendar-feed.test.ts +0 -439
- package/src/handlers/calendar-feed.ts +0 -291
- package/src/handlers/calendar-sqlite-fixture.ts +0 -36
- package/src/handlers/calendar-suggestion.test.ts +0 -1269
- package/src/handlers/calendar-suggestion.ts +0 -425
- package/src/handlers/calendar.test.ts +0 -1080
- package/src/handlers/calendar.ts +0 -517
- package/src/handlers/config.test.ts +0 -245
- package/src/handlers/config.ts +0 -332
- package/src/handlers/ensure-account-config.ts +0 -30
- package/src/handlers/filter.test.ts +0 -644
- package/src/handlers/filter.ts +0 -518
- package/src/handlers/folder-role-appointments.test.ts +0 -438
- package/src/handlers/folder-role-appointments.ts +0 -249
- package/src/handlers/folder-role-labels.ts +0 -63
- package/src/handlers/folder-role.test.ts +0 -209
- package/src/handlers/folder-role.ts +0 -80
- package/src/handlers/index.ts +0 -83
- package/src/handlers/label.test.ts +0 -112
- package/src/handlers/label.ts +0 -210
- package/src/handlers/mailbox-bindings.ts +0 -111
- package/src/handlers/mailbox-rename-refusal.test.ts +0 -247
- package/src/handlers/mailbox-settled-target.test.ts +0 -251
- package/src/handlers/mailbox.test.ts +0 -280
- package/src/handlers/mailbox.ts +0 -581
- package/src/handlers/me.test.ts +0 -81
- package/src/handlers/me.ts +0 -203
- package/src/handlers/message-read-backfill.test.ts +0 -103
- package/src/handlers/message.test.ts +0 -131
- package/src/handlers/message.ts +0 -1131
- package/src/handlers/organize.test.ts +0 -277
- package/src/handlers/organize.ts +0 -197
- package/src/handlers/outbox-attachment.test.ts +0 -765
- package/src/handlers/outbox-attachment.ts +0 -126
- package/src/handlers/outbox-list.test.ts +0 -179
- package/src/handlers/outbox.test.ts +0 -772
- package/src/handlers/outbox.ts +0 -258
- package/src/handlers/search.test.ts +0 -161
- package/src/handlers/search.ts +0 -105
- package/src/handlers/sync-progress.test.ts +0 -205
- package/src/handlers/sync-progress.ts +0 -99
- package/src/handlers/sync.test.ts +0 -168
- package/src/handlers/sync.ts +0 -118
- package/src/handlers/system-update.test.ts +0 -497
- package/src/handlers/system-update.ts +0 -230
- package/src/handlers/thread.test.ts +0 -396
- package/src/handlers/thread.ts +0 -427
- package/src/handlers/unified-threads-mail-sources.sqlite.test.ts +0 -197
- package/src/handlers/unified-threads.test.ts +0 -785
- package/src/handlers/unified-threads.ts +0 -669
- package/src/index.test.ts +0 -83
- package/src/index.ts +0 -228
- package/src/jwt-auth.test.ts +0 -152
- package/src/jwt-auth.ts +0 -132
- package/src/log-redaction.test.ts +0 -144
- package/src/openapi-spec.test.ts +0 -118
- package/src/request-context.test.ts +0 -61
- package/src/request-context.ts +0 -33
- package/src/request-pipeline.test.ts +0 -88
- package/src/request.ts +0 -10
- package/src/response.test.ts +0 -130
- package/src/response.ts +0 -144
- package/src/service/compose-sqlite.ts +0 -98
- package/src/service/config-import.ts +0 -39
- package/src/service/create-remit-client.ts +0 -522
- package/src/service/data-client.test.ts +0 -97
- package/src/service/data-client.ts +0 -58
- package/src/service/export-identity.ts +0 -39
- package/src/service/filter.ts +0 -40
- package/src/service/fire-and-forget.test.ts +0 -126
- package/src/service/fire-and-forget.ts +0 -79
- package/src/service/localhost-env-config.test.ts +0 -36
- package/src/service/organize-queue.ts +0 -98
- package/src/service/organize.test.ts +0 -1451
- package/src/service/organize.ts +0 -1025
- package/src/service/read-path-filter-config.test.ts +0 -243
- package/src/service/semantic-capability.test.ts +0 -78
- package/src/service/semantic-capability.ts +0 -95
- package/src/service/sender-category.test.ts +0 -235
- package/src/service/trigger-sync.test.ts +0 -205
- package/src/service/trigger-sync.ts +0 -100
- package/src/stdout-capture-fixture.ts +0 -40
- package/src/types.ts +0 -271
- package/tsconfig.json +0 -7
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { logger } from "@remit/logger";
|
|
2
|
+
import { createFilter, listFilters, ownedAccount, updateFilter, } from "@remit/mailbox-service";
|
|
3
|
+
import { env } from "expect-env";
|
|
4
|
+
import { getAccountConfigIdFromEvent, getSubFromEvent } from "../auth.js";
|
|
5
|
+
import { getClient } from "../service/data-client.js";
|
|
6
|
+
import { unwrap } from "../service/refusal.js";
|
|
7
|
+
const toFilterResponse = (item) => ({
|
|
8
|
+
filterId: item.filterId,
|
|
9
|
+
accountConfigId: item.accountConfigId,
|
|
10
|
+
name: item.name,
|
|
11
|
+
scope: item.scope,
|
|
12
|
+
expiresAt: item.expiresAt,
|
|
13
|
+
state: item.state,
|
|
14
|
+
disabledReason: item.disabledReason,
|
|
15
|
+
ruleChangedAt: item.ruleChangedAt,
|
|
16
|
+
actionChangedAt: item.actionChangedAt,
|
|
17
|
+
matchOperator: item.matchOperator,
|
|
18
|
+
literalClauses: item.literalClauses,
|
|
19
|
+
actionLabelId: item.actionLabelId,
|
|
20
|
+
actionMailboxId: item.actionMailboxId,
|
|
21
|
+
createdAt: item.createdAt,
|
|
22
|
+
updatedAt: item.updatedAt,
|
|
23
|
+
});
|
|
24
|
+
const requireUserId = (event) => {
|
|
25
|
+
const userId = getSubFromEvent(event);
|
|
26
|
+
if (!userId) {
|
|
27
|
+
throw new Error("Missing Cognito `sub`: cannot attribute an organize job to a user");
|
|
28
|
+
}
|
|
29
|
+
return userId;
|
|
30
|
+
};
|
|
31
|
+
export const FilterOperations = {
|
|
32
|
+
FilterOperations_listFilters: async (context, ...args) => {
|
|
33
|
+
const event = args[0];
|
|
34
|
+
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
35
|
+
const { accountId } = context.request.params;
|
|
36
|
+
const { continuationToken } = context.request.query;
|
|
37
|
+
const page = unwrap(await listFilters(await getClient(), accountConfigId, {
|
|
38
|
+
accountId,
|
|
39
|
+
continuationToken,
|
|
40
|
+
}));
|
|
41
|
+
return {
|
|
42
|
+
items: page.items.map(toFilterResponse),
|
|
43
|
+
continuationToken: page.continuationToken,
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
FilterOperations_createFilter: async (context, ...args) => {
|
|
47
|
+
const event = args[0];
|
|
48
|
+
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
49
|
+
const { accountId } = context.request.params;
|
|
50
|
+
const filter = context.request.requestBody;
|
|
51
|
+
const userId = requireUserId(event);
|
|
52
|
+
const client = await getClient();
|
|
53
|
+
const created = unwrap(await createFilter({
|
|
54
|
+
account: client.account,
|
|
55
|
+
mailbox: client.mailbox,
|
|
56
|
+
filter: client.filter,
|
|
57
|
+
organizeJobRequest: client.organizeJobRequest,
|
|
58
|
+
sqsQueueUrl: env.SQS_QUEUE_URL_ACCOUNT_FANOUT,
|
|
59
|
+
logger,
|
|
60
|
+
}, { accountConfigId, userId }, { accountId, filter }));
|
|
61
|
+
return {
|
|
62
|
+
...toFilterResponse(created.filter),
|
|
63
|
+
organizeJobId: created.organizeJob.organizeJobId,
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
export const FilterDetailOperations = {
|
|
68
|
+
FilterDetailOperations_getFilter: async (context, ...args) => {
|
|
69
|
+
const event = args[0];
|
|
70
|
+
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
71
|
+
const { accountId, filterId } = context.request.params;
|
|
72
|
+
const client = await getClient();
|
|
73
|
+
const account = await client.account.get(accountId);
|
|
74
|
+
unwrap(ownedAccount(account, accountConfigId, "read"));
|
|
75
|
+
const { filter } = client;
|
|
76
|
+
const item = await filter.get(accountConfigId, filterId);
|
|
77
|
+
const refreshed = await filter.refreshExpiry(item);
|
|
78
|
+
return toFilterResponse(refreshed);
|
|
79
|
+
},
|
|
80
|
+
FilterDetailOperations_updateFilter: async (context, ...args) => {
|
|
81
|
+
const event = args[0];
|
|
82
|
+
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
83
|
+
const { accountId, filterId } = context.request.params;
|
|
84
|
+
const patch = context.request.requestBody;
|
|
85
|
+
const updated = await updateFilter(await getClient(), accountConfigId, {
|
|
86
|
+
accountId,
|
|
87
|
+
filterId,
|
|
88
|
+
patch,
|
|
89
|
+
});
|
|
90
|
+
return toFilterResponse(unwrap(updated));
|
|
91
|
+
},
|
|
92
|
+
FilterDetailOperations_deleteFilter: async (context, ...args) => {
|
|
93
|
+
const event = args[0];
|
|
94
|
+
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
95
|
+
const { accountId, filterId } = context.request.params;
|
|
96
|
+
const client = await getClient();
|
|
97
|
+
const account = await client.account.get(accountId);
|
|
98
|
+
unwrap(ownedAccount(account, accountConfigId, "act"));
|
|
99
|
+
const { filter } = client;
|
|
100
|
+
await filter.delete(accountConfigId, filterId);
|
|
101
|
+
return { statusCode: 204 };
|
|
102
|
+
},
|
|
103
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { FolderAppointment } from "@remit/api-openapi-types";
|
|
2
|
+
import type { AccountSettingItem, IAccountSettingRepository, IMailboxRepository } from "@remit/data-ports";
|
|
3
|
+
import { CANONICAL_ROLES, type CanonicalMailboxRoleValue, type RoleMailboxCandidate } from "@remit/data-ports/folder-role";
|
|
4
|
+
/**
|
|
5
|
+
* RFC 032 exclusive-folder-appointment (#976): a per-account role→mailbox map.
|
|
6
|
+
* Each row is a `FolderRoleAppointment#<accountId>#<role>` AccountSetting (RFC
|
|
7
|
+
* 032 settings tiers), so a role can never be persisted twice for one account —
|
|
8
|
+
* writing it replaces whichever mailbox previously held it. This module owns
|
|
9
|
+
* that persistence; the read side is `resolveRoleForAccount`
|
|
10
|
+
* (`@remit/data-ports/folder-role`), the same rule every special-folder lookup
|
|
11
|
+
* in the backend and the workers goes through.
|
|
12
|
+
*
|
|
13
|
+
* A second row, `FolderRoleAppointmentLabel#<accountId>#<role>`, records the
|
|
14
|
+
* path the appointed mailbox had at the time (#887), so a folder another client
|
|
15
|
+
* later deletes can still be named back to the user. It is display only: it
|
|
16
|
+
* lives beside the appointment rather than inside it precisely so the
|
|
17
|
+
* repository that resolves roles composes one name and cannot reach the other.
|
|
18
|
+
*/
|
|
19
|
+
export { CANONICAL_ROLES };
|
|
20
|
+
/** One role's persisted appointment: the choice, plus the path it was made on. */
|
|
21
|
+
export interface PersistedFolderAppointment {
|
|
22
|
+
mailboxId: string;
|
|
23
|
+
lastKnownPath?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Group every persisted folder-role appointment in one config-wide settings
|
|
27
|
+
* read (e.g. GET /config's already-loaded `listByAccountConfig` result) by
|
|
28
|
+
* accountId, then by role. Mirrors `groupAccountOverrides` / `groupMailboxOverrides`.
|
|
29
|
+
*/
|
|
30
|
+
export declare const groupFolderAppointmentsByAccount: (settings: AccountSettingItem[]) => Map<string, Map<string, PersistedFolderAppointment>>;
|
|
31
|
+
/**
|
|
32
|
+
* Load one account's persisted appointments by reading each role's row
|
|
33
|
+
* directly (mirrors `loadMailboxOverrides`'s three-get pattern). Used by the
|
|
34
|
+
* create/update account handlers, which only ever need a single account.
|
|
35
|
+
*/
|
|
36
|
+
export declare const loadFolderAppointmentsForAccount: (accountSetting: Pick<IAccountSettingRepository, "get">, accountConfigId: string, accountId: string) => Promise<Map<string, PersistedFolderAppointment>>;
|
|
37
|
+
/**
|
|
38
|
+
* Persist (or clear) one role's appointment. `mailboxId: null` deletes the row
|
|
39
|
+
* — the role goes back to unfilled (RFC 032 settings tiers: absence is unset).
|
|
40
|
+
* A value upserts it, replacing whatever mailbox the role pointed at before;
|
|
41
|
+
* there is no second row a duplicate could live in.
|
|
42
|
+
*
|
|
43
|
+
* The label row moves with it, so a path recorded for a previous choice can
|
|
44
|
+
* never be shown against the current one.
|
|
45
|
+
*/
|
|
46
|
+
export declare const writeFolderRoleAppointment: (accountSetting: Pick<IAccountSettingRepository, "upsert" | "delete">, accountConfigId: string, accountId: string, role: CanonicalMailboxRoleValue, mailboxId: string | null, lastKnownPath?: string) => Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Resolve the full appointment set for one account, each entry saying where it
|
|
49
|
+
* came from: the user's persisted choice when set, else the server's
|
|
50
|
+
* SPECIAL-USE flag, else the reserved INBOX name, else a name proposal. A
|
|
51
|
+
* choice naming a mailbox the account no longer holds resolves to whatever the
|
|
52
|
+
* account would have had without it, and says so — the user's decision is
|
|
53
|
+
* surfaced as broken rather than quietly discarded. Always one entry per
|
|
54
|
+
* `CANONICAL_ROLES` member (RFC 032 settings tiers: total, never a sparse
|
|
55
|
+
* array).
|
|
56
|
+
*/
|
|
57
|
+
export declare const resolveFolderAppointments: (persisted: ReadonlyMap<string, PersistedFolderAppointment>, mailboxes: readonly RoleMailboxCandidate[]) => FolderAppointment[];
|
|
58
|
+
/**
|
|
59
|
+
* End-to-end resolution for one account: load its persisted appointments and
|
|
60
|
+
* its mailboxes, then resolve. The single entry point every handler that
|
|
61
|
+
* builds an AccountResponse calls (create/update account, GET /config's
|
|
62
|
+
* per-account fan-out mirrors this with a pre-loaded settings batch instead —
|
|
63
|
+
* see `groupFolderAppointmentsByAccount` — to avoid an N+1 settings read).
|
|
64
|
+
*/
|
|
65
|
+
export declare const resolveAccountFolderAppointments: (client: {
|
|
66
|
+
mailbox: Pick<IMailboxRepository, "listAllByAccount">;
|
|
67
|
+
accountSetting: Pick<IAccountSettingRepository, "get">;
|
|
68
|
+
}, accountConfigId: string, accountId: string) => Promise<FolderAppointment[]>;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { CANONICAL_ROLES, composeFolderRoleAppointmentLabelName, composeFolderRoleAppointmentName, parseFolderRoleAppointmentLabelName, parseFolderRoleAppointmentName, resolveRoleForAccount, } from "@remit/data-ports/folder-role";
|
|
2
|
+
import { FolderAppointmentSource } from "@remit/domain-enums";
|
|
3
|
+
/**
|
|
4
|
+
* RFC 032 exclusive-folder-appointment (#976): a per-account role→mailbox map.
|
|
5
|
+
* Each row is a `FolderRoleAppointment#<accountId>#<role>` AccountSetting (RFC
|
|
6
|
+
* 032 settings tiers), so a role can never be persisted twice for one account —
|
|
7
|
+
* writing it replaces whichever mailbox previously held it. This module owns
|
|
8
|
+
* that persistence; the read side is `resolveRoleForAccount`
|
|
9
|
+
* (`@remit/data-ports/folder-role`), the same rule every special-folder lookup
|
|
10
|
+
* in the backend and the workers goes through.
|
|
11
|
+
*
|
|
12
|
+
* A second row, `FolderRoleAppointmentLabel#<accountId>#<role>`, records the
|
|
13
|
+
* path the appointed mailbox had at the time (#887), so a folder another client
|
|
14
|
+
* later deletes can still be named back to the user. It is display only: it
|
|
15
|
+
* lives beside the appointment rather than inside it precisely so the
|
|
16
|
+
* repository that resolves roles composes one name and cannot reach the other.
|
|
17
|
+
*/
|
|
18
|
+
export { CANONICAL_ROLES };
|
|
19
|
+
const stringValueOf = (item) => {
|
|
20
|
+
const { value } = item;
|
|
21
|
+
return value.kind === "String" ? value.value : undefined;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Group every persisted folder-role appointment in one config-wide settings
|
|
25
|
+
* read (e.g. GET /config's already-loaded `listByAccountConfig` result) by
|
|
26
|
+
* accountId, then by role. Mirrors `groupAccountOverrides` / `groupMailboxOverrides`.
|
|
27
|
+
*/
|
|
28
|
+
export const groupFolderAppointmentsByAccount = (settings) => {
|
|
29
|
+
const byAccount = new Map();
|
|
30
|
+
for (const setting of settings) {
|
|
31
|
+
const target = parseFolderRoleAppointmentName(setting.name);
|
|
32
|
+
if (!target)
|
|
33
|
+
continue;
|
|
34
|
+
const mailboxId = stringValueOf(setting);
|
|
35
|
+
if (mailboxId === undefined)
|
|
36
|
+
continue;
|
|
37
|
+
const roles = byAccount.get(target.accountId) ??
|
|
38
|
+
new Map();
|
|
39
|
+
roles.set(target.role, { mailboxId });
|
|
40
|
+
byAccount.set(target.accountId, roles);
|
|
41
|
+
}
|
|
42
|
+
for (const setting of settings) {
|
|
43
|
+
const target = parseFolderRoleAppointmentLabelName(setting.name);
|
|
44
|
+
if (!target)
|
|
45
|
+
continue;
|
|
46
|
+
const lastKnownPath = stringValueOf(setting);
|
|
47
|
+
if (lastKnownPath === undefined)
|
|
48
|
+
continue;
|
|
49
|
+
const appointment = byAccount.get(target.accountId)?.get(target.role);
|
|
50
|
+
if (!appointment)
|
|
51
|
+
continue;
|
|
52
|
+
appointment.lastKnownPath = lastKnownPath;
|
|
53
|
+
}
|
|
54
|
+
return byAccount;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Load one account's persisted appointments by reading each role's row
|
|
58
|
+
* directly (mirrors `loadMailboxOverrides`'s three-get pattern). Used by the
|
|
59
|
+
* create/update account handlers, which only ever need a single account.
|
|
60
|
+
*/
|
|
61
|
+
export const loadFolderAppointmentsForAccount = async (accountSetting, accountConfigId, accountId) => {
|
|
62
|
+
const entries = await Promise.all(CANONICAL_ROLES.map(async (role) => {
|
|
63
|
+
const appointment = await accountSetting.get(accountConfigId, composeFolderRoleAppointmentName(accountId, role));
|
|
64
|
+
const mailboxId = appointment ? stringValueOf(appointment) : undefined;
|
|
65
|
+
if (mailboxId === undefined)
|
|
66
|
+
return [role, undefined];
|
|
67
|
+
const label = await accountSetting.get(accountConfigId, composeFolderRoleAppointmentLabelName(accountId, role));
|
|
68
|
+
const lastKnownPath = label ? stringValueOf(label) : undefined;
|
|
69
|
+
return [
|
|
70
|
+
role,
|
|
71
|
+
lastKnownPath === undefined
|
|
72
|
+
? { mailboxId }
|
|
73
|
+
: { mailboxId, lastKnownPath },
|
|
74
|
+
];
|
|
75
|
+
}));
|
|
76
|
+
const roles = new Map();
|
|
77
|
+
for (const [role, appointment] of entries) {
|
|
78
|
+
if (appointment)
|
|
79
|
+
roles.set(role, appointment);
|
|
80
|
+
}
|
|
81
|
+
return roles;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Persist (or clear) one role's appointment. `mailboxId: null` deletes the row
|
|
85
|
+
* — the role goes back to unfilled (RFC 032 settings tiers: absence is unset).
|
|
86
|
+
* A value upserts it, replacing whatever mailbox the role pointed at before;
|
|
87
|
+
* there is no second row a duplicate could live in.
|
|
88
|
+
*
|
|
89
|
+
* The label row moves with it, so a path recorded for a previous choice can
|
|
90
|
+
* never be shown against the current one.
|
|
91
|
+
*/
|
|
92
|
+
export const writeFolderRoleAppointment = async (accountSetting, accountConfigId, accountId, role, mailboxId, lastKnownPath) => {
|
|
93
|
+
const name = composeFolderRoleAppointmentName(accountId, role);
|
|
94
|
+
const labelName = composeFolderRoleAppointmentLabelName(accountId, role);
|
|
95
|
+
if (mailboxId === null) {
|
|
96
|
+
await accountSetting.delete(accountConfigId, name);
|
|
97
|
+
await accountSetting.delete(accountConfigId, labelName);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
await accountSetting.upsert({
|
|
101
|
+
accountConfigId,
|
|
102
|
+
name,
|
|
103
|
+
value: { kind: "String", value: mailboxId },
|
|
104
|
+
});
|
|
105
|
+
if (lastKnownPath === undefined) {
|
|
106
|
+
await accountSetting.delete(accountConfigId, labelName);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
await accountSetting.upsert({
|
|
110
|
+
accountConfigId,
|
|
111
|
+
name: labelName,
|
|
112
|
+
value: { kind: "String", value: lastKnownPath },
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
const SOURCE_BY_KIND = {
|
|
116
|
+
appointed: FolderAppointmentSource.Appointed,
|
|
117
|
+
flagged: FolderAppointmentSource.Flagged,
|
|
118
|
+
reserved: FolderAppointmentSource.Reserved,
|
|
119
|
+
proposed: FolderAppointmentSource.Proposed,
|
|
120
|
+
};
|
|
121
|
+
const toFolderAppointment = (role, resolution, lastKnownPath) => {
|
|
122
|
+
if (resolution.kind === "appointment_stale") {
|
|
123
|
+
const { fallback } = resolution;
|
|
124
|
+
return {
|
|
125
|
+
role,
|
|
126
|
+
source: FolderAppointmentSource.Stale,
|
|
127
|
+
mailboxId: fallback.kind === "none" ? undefined : fallback.mailbox.mailboxId,
|
|
128
|
+
staleAppointmentMailboxId: resolution.appointedMailboxId,
|
|
129
|
+
staleAppointmentPath: lastKnownPath,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (resolution.kind === "none") {
|
|
133
|
+
return { role, source: FolderAppointmentSource.None };
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
role,
|
|
137
|
+
source: SOURCE_BY_KIND[resolution.kind],
|
|
138
|
+
mailboxId: resolution.mailbox.mailboxId,
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Resolve the full appointment set for one account, each entry saying where it
|
|
143
|
+
* came from: the user's persisted choice when set, else the server's
|
|
144
|
+
* SPECIAL-USE flag, else the reserved INBOX name, else a name proposal. A
|
|
145
|
+
* choice naming a mailbox the account no longer holds resolves to whatever the
|
|
146
|
+
* account would have had without it, and says so — the user's decision is
|
|
147
|
+
* surfaced as broken rather than quietly discarded. Always one entry per
|
|
148
|
+
* `CANONICAL_ROLES` member (RFC 032 settings tiers: total, never a sparse
|
|
149
|
+
* array).
|
|
150
|
+
*/
|
|
151
|
+
export const resolveFolderAppointments = (persisted, mailboxes) => CANONICAL_ROLES.map((role) => {
|
|
152
|
+
const appointment = persisted.get(role);
|
|
153
|
+
return toFolderAppointment(role, resolveRoleForAccount(role, mailboxes, appointment?.mailboxId), appointment?.lastKnownPath);
|
|
154
|
+
});
|
|
155
|
+
/**
|
|
156
|
+
* End-to-end resolution for one account: load its persisted appointments and
|
|
157
|
+
* its mailboxes, then resolve. The single entry point every handler that
|
|
158
|
+
* builds an AccountResponse calls (create/update account, GET /config's
|
|
159
|
+
* per-account fan-out mirrors this with a pre-loaded settings batch instead —
|
|
160
|
+
* see `groupFolderAppointmentsByAccount` — to avoid an N+1 settings read).
|
|
161
|
+
*/
|
|
162
|
+
export const resolveAccountFolderAppointments = async (client, accountConfigId, accountId) => {
|
|
163
|
+
const [persisted, mailboxes] = await Promise.all([
|
|
164
|
+
loadFolderAppointmentsForAccount(client.accountSetting, accountConfigId, accountId),
|
|
165
|
+
client.mailbox.listAllByAccount(accountId),
|
|
166
|
+
]);
|
|
167
|
+
return resolveFolderAppointments(persisted, mailboxes);
|
|
168
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { IAccountSettingRepository } from "@remit/data-ports";
|
|
2
|
+
/**
|
|
3
|
+
* The label row beside each role appointment records the path its folder had at
|
|
4
|
+
* the time (#887), so a folder another client later deletes can still be named
|
|
5
|
+
* back to the user. It is display only, and it lives here rather than beside
|
|
6
|
+
* the appointment writer because that writer is guarded as the only writer of
|
|
7
|
+
* the appointment row itself.
|
|
8
|
+
*
|
|
9
|
+
* A rename keeps every mailboxId, so an appointment survives it — but the path
|
|
10
|
+
* recorded beside it still names where the folder was. Move the labels with the
|
|
11
|
+
* branch, or a later third-party delete names a path the user has not seen
|
|
12
|
+
* since the rename.
|
|
13
|
+
*
|
|
14
|
+
* Driven by the settle, not by the intent: under D2 the row keeps the path the
|
|
15
|
+
* mail server holds until the rename lands, so a label moved when the intent
|
|
16
|
+
* was recorded would name a path that does not exist — permanently, if the
|
|
17
|
+
* rename then fails.
|
|
18
|
+
*
|
|
19
|
+
* The renamed folder is matched by id; its descendants are matched by the path
|
|
20
|
+
* each label already holds.
|
|
21
|
+
*/
|
|
22
|
+
export declare const refreshFolderAppointmentLabels: (accountSetting: Pick<IAccountSettingRepository, "get" | "upsert">, accountConfigId: string, accountId: string, renamed: {
|
|
23
|
+
mailboxId: string;
|
|
24
|
+
oldPath: string;
|
|
25
|
+
newPath: string;
|
|
26
|
+
}, delimiter: string) => Promise<void>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { composeFolderRoleAppointmentLabelName, } from "@remit/data-ports/folder-role";
|
|
2
|
+
import { rebaseMailboxPath } from "@remit/data-ports/mailbox-name";
|
|
3
|
+
import { loadFolderAppointmentsForAccount } from "./folder-role-appointments.js";
|
|
4
|
+
/**
|
|
5
|
+
* The label row beside each role appointment records the path its folder had at
|
|
6
|
+
* the time (#887), so a folder another client later deletes can still be named
|
|
7
|
+
* back to the user. It is display only, and it lives here rather than beside
|
|
8
|
+
* the appointment writer because that writer is guarded as the only writer of
|
|
9
|
+
* the appointment row itself.
|
|
10
|
+
*
|
|
11
|
+
* A rename keeps every mailboxId, so an appointment survives it — but the path
|
|
12
|
+
* recorded beside it still names where the folder was. Move the labels with the
|
|
13
|
+
* branch, or a later third-party delete names a path the user has not seen
|
|
14
|
+
* since the rename.
|
|
15
|
+
*
|
|
16
|
+
* Driven by the settle, not by the intent: under D2 the row keeps the path the
|
|
17
|
+
* mail server holds until the rename lands, so a label moved when the intent
|
|
18
|
+
* was recorded would name a path that does not exist — permanently, if the
|
|
19
|
+
* rename then fails.
|
|
20
|
+
*
|
|
21
|
+
* The renamed folder is matched by id; its descendants are matched by the path
|
|
22
|
+
* each label already holds.
|
|
23
|
+
*/
|
|
24
|
+
export const refreshFolderAppointmentLabels = async (accountSetting, accountConfigId, accountId, renamed, delimiter) => {
|
|
25
|
+
const persisted = await loadFolderAppointmentsForAccount(accountSetting, accountConfigId, accountId);
|
|
26
|
+
for (const [role, appointment] of persisted) {
|
|
27
|
+
const moved = appointment.mailboxId === renamed.mailboxId
|
|
28
|
+
? renamed.newPath
|
|
29
|
+
: appointment.lastKnownPath === undefined
|
|
30
|
+
? undefined
|
|
31
|
+
: rebaseMailboxPath(appointment.lastKnownPath, renamed.oldPath, renamed.newPath, delimiter);
|
|
32
|
+
if (moved === undefined || moved === appointment.lastKnownPath)
|
|
33
|
+
continue;
|
|
34
|
+
await accountSetting.upsert({
|
|
35
|
+
accountConfigId,
|
|
36
|
+
name: composeFolderRoleAppointmentLabelName(accountId, role),
|
|
37
|
+
value: { kind: "String", value: moved },
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FolderRoleOperationIds, OperationHandler } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* RFC 032 exclusive-folder-appointment (#976): the single write operation for
|
|
4
|
+
* the per-account role map. `appoint(role, mailboxId)` sets `map[role]` —
|
|
5
|
+
* there is no second place a duplicate appointment could live, so writing a
|
|
6
|
+
* role replaces whichever mailbox previously held it. `mailboxId: null` clears
|
|
7
|
+
* the role back to unfilled.
|
|
8
|
+
*/
|
|
9
|
+
export declare const FolderRoleOperations: Record<FolderRoleOperationIds, OperationHandler<FolderRoleOperationIds>>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { folderInAccount, ownedAccount, settledFolder, } from "@remit/mailbox-service";
|
|
2
|
+
import { getAccountConfigIdFromEvent } from "../auth.js";
|
|
3
|
+
import { getClient } from "../service/data-client.js";
|
|
4
|
+
import { unwrap } from "../service/refusal.js";
|
|
5
|
+
import { toAccountResponse } from "./account-guards.js";
|
|
6
|
+
import { loadAccountOverrides } from "./account-overrides.js";
|
|
7
|
+
import { loadSignatureForAccount } from "./account-signature.js";
|
|
8
|
+
import { resolveAccountFolderAppointments, writeFolderRoleAppointment, } from "./folder-role-appointments.js";
|
|
9
|
+
/**
|
|
10
|
+
* RFC 032 exclusive-folder-appointment (#976): the single write operation for
|
|
11
|
+
* the per-account role map. `appoint(role, mailboxId)` sets `map[role]` —
|
|
12
|
+
* there is no second place a duplicate appointment could live, so writing a
|
|
13
|
+
* role replaces whichever mailbox previously held it. `mailboxId: null` clears
|
|
14
|
+
* the role back to unfilled.
|
|
15
|
+
*/
|
|
16
|
+
export const FolderRoleOperations = {
|
|
17
|
+
FolderRoleOperations_appointFolderRole: async (context, ...args) => {
|
|
18
|
+
const event = args[0];
|
|
19
|
+
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
20
|
+
const { accountId, role } = context.request.params;
|
|
21
|
+
const body = context.request.requestBody;
|
|
22
|
+
const { account, accountSetting, mailbox } = await getClient();
|
|
23
|
+
const existing = await account.get(accountId);
|
|
24
|
+
unwrap(ownedAccount(existing, accountConfigId, "act"));
|
|
25
|
+
let lastKnownPath;
|
|
26
|
+
if (body.mailboxId) {
|
|
27
|
+
const target = await mailbox.get(accountId, body.mailboxId);
|
|
28
|
+
unwrap(folderInAccount(target, accountId, "act"));
|
|
29
|
+
unwrap(settledFolder(target));
|
|
30
|
+
lastKnownPath = target.fullPath;
|
|
31
|
+
}
|
|
32
|
+
await writeFolderRoleAppointment(accountSetting, accountConfigId, accountId, role, body.mailboxId ?? null, lastKnownPath);
|
|
33
|
+
const [signature, overrides, folderAppointments] = await Promise.all([
|
|
34
|
+
loadSignatureForAccount(accountSetting, accountConfigId, accountId),
|
|
35
|
+
loadAccountOverrides(accountSetting, accountConfigId, accountId),
|
|
36
|
+
resolveAccountFolderAppointments({ mailbox, accountSetting }, accountConfigId, accountId),
|
|
37
|
+
]);
|
|
38
|
+
return toAccountResponse(existing, signature, overrides, folderAppointments);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { AccountDetailOperations, AccountOperations } from "./account.js";
|
|
2
|
+
import { MicrosoftOAuthOperations } from "./account-oauth.js";
|
|
3
|
+
import { AddressDetailOperations, AddressOperations } from "./address.js";
|
|
4
|
+
import { CalendarDetailOperations, CalendarOperations } from "./calendar.js";
|
|
5
|
+
import { CalendarEventDetailOperations, CalendarEventOperations, CalendarFreeBusyOperations, } from "./calendar-event.js";
|
|
6
|
+
import { CalendarFeedDetailOperations, CalendarFeedOperations, } from "./calendar-feed.js";
|
|
7
|
+
import { CalendarSuggestionActionOperations, CalendarSuggestionOperations, MessageCalendarSuggestionOperations, } from "./calendar-suggestion.js";
|
|
8
|
+
import { ConfigOperations } from "./config.js";
|
|
9
|
+
import { FilterDetailOperations, FilterOperations } from "./filter.js";
|
|
10
|
+
import { FolderRoleOperations } from "./folder-role.js";
|
|
11
|
+
import { LabelDetailOperations, LabelOperations } from "./label.js";
|
|
12
|
+
import { MailboxDetailOperations, MailboxOperations, TrashOperations, } from "./mailbox.js";
|
|
13
|
+
import { MeOperations } from "./me.js";
|
|
14
|
+
import { MessageBulkOperations, MessageOperations } from "./message.js";
|
|
15
|
+
import { OrganizeJobDetailOperations, OrganizeOperations } from "./organize.js";
|
|
16
|
+
import { OutboxAttachmentOperations, OutboxDetailOperations, OutboxOperations, } from "./outbox.js";
|
|
17
|
+
import { SyncOperations } from "./sync.js";
|
|
18
|
+
import { SystemOperations } from "./system-update.js";
|
|
19
|
+
import { ThreadDetailOperations, ThreadOperations } from "./thread.js";
|
|
20
|
+
import { UnifiedThreadOperations } from "./unified-threads.js";
|
|
21
|
+
// biome-ignore lint/suspicious/noExplicitAny: Types are narrowed downstream
|
|
22
|
+
export const handlers = {
|
|
23
|
+
...MeOperations,
|
|
24
|
+
...ConfigOperations,
|
|
25
|
+
...SystemOperations,
|
|
26
|
+
...AccountOperations,
|
|
27
|
+
...AccountDetailOperations,
|
|
28
|
+
...MicrosoftOAuthOperations,
|
|
29
|
+
...MailboxOperations,
|
|
30
|
+
...MailboxDetailOperations,
|
|
31
|
+
...FolderRoleOperations,
|
|
32
|
+
...FilterOperations,
|
|
33
|
+
...FilterDetailOperations,
|
|
34
|
+
...LabelOperations,
|
|
35
|
+
...LabelDetailOperations,
|
|
36
|
+
...OrganizeOperations,
|
|
37
|
+
...OrganizeJobDetailOperations,
|
|
38
|
+
...CalendarOperations,
|
|
39
|
+
...CalendarDetailOperations,
|
|
40
|
+
...CalendarFeedDetailOperations,
|
|
41
|
+
...CalendarFeedOperations,
|
|
42
|
+
...CalendarEventOperations,
|
|
43
|
+
...CalendarEventDetailOperations,
|
|
44
|
+
...CalendarFreeBusyOperations,
|
|
45
|
+
...TrashOperations,
|
|
46
|
+
...SyncOperations,
|
|
47
|
+
...ThreadDetailOperations,
|
|
48
|
+
...UnifiedThreadOperations,
|
|
49
|
+
...ThreadOperations,
|
|
50
|
+
...MessageOperations,
|
|
51
|
+
...MessageBulkOperations,
|
|
52
|
+
...OutboxOperations,
|
|
53
|
+
...OutboxDetailOperations,
|
|
54
|
+
...OutboxAttachmentOperations,
|
|
55
|
+
...AddressOperations,
|
|
56
|
+
...AddressDetailOperations,
|
|
57
|
+
...CalendarSuggestionOperations,
|
|
58
|
+
...MessageCalendarSuggestionOperations,
|
|
59
|
+
...CalendarSuggestionActionOperations,
|
|
60
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { CreateLabelInput, FilterItem, LabelItem, UpdateLabelInput } from "@remit/data-ports";
|
|
2
|
+
import type { LabelDetailOperationIds, LabelOperationIds, OperationHandler } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Minimal label-service surface the CRUD handlers need — declared as a `Pick`
|
|
5
|
+
* so tests can stub it without a live table.
|
|
6
|
+
*/
|
|
7
|
+
export interface LabelCrudDeps {
|
|
8
|
+
label: {
|
|
9
|
+
create(input: CreateLabelInput): Promise<LabelItem>;
|
|
10
|
+
get(accountConfigId: string, labelId: string): Promise<LabelItem>;
|
|
11
|
+
update(accountConfigId: string, labelId: string, input: UpdateLabelInput): Promise<LabelItem>;
|
|
12
|
+
delete(accountConfigId: string, labelId: string): Promise<void>;
|
|
13
|
+
listPageByAccountConfig(accountConfigId: string, options?: {
|
|
14
|
+
limit?: number;
|
|
15
|
+
continuationToken?: string;
|
|
16
|
+
}): Promise<{
|
|
17
|
+
items: LabelItem[];
|
|
18
|
+
continuationToken: string | undefined;
|
|
19
|
+
}>;
|
|
20
|
+
};
|
|
21
|
+
filter: {
|
|
22
|
+
listByAccountConfig(accountConfigId: string): Promise<FilterItem[]>;
|
|
23
|
+
delete(accountConfigId: string, filterId: string): Promise<void>;
|
|
24
|
+
};
|
|
25
|
+
messageLabel: {
|
|
26
|
+
removeAllByLabelId(accountConfigId: string, labelId: string): Promise<void>;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Every filter in the account whose action applies this label (issue #26). The
|
|
31
|
+
* count backs `LabelResponse.filterCount`; the list backs the cascade delete.
|
|
32
|
+
*/
|
|
33
|
+
export declare const findFiltersForLabel: (deps: Pick<LabelCrudDeps, "filter">, accountConfigId: string, labelId: string) => Promise<FilterItem[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Delete a label and cascade in both directions (issue #26): every filter
|
|
36
|
+
* whose `actionLabelId` is this label is deleted outright — never left
|
|
37
|
+
* dangling, never blocking the delete — and every `MessageLabel` row for it is
|
|
38
|
+
* removed. The label row itself is deleted last so a failure mid-cascade never
|
|
39
|
+
* leaves the label gone while its filters or applied-to-messages still exist.
|
|
40
|
+
*/
|
|
41
|
+
export declare const deleteLabelWithCascade: (deps: LabelCrudDeps, accountConfigId: string, labelId: string) => Promise<void>;
|
|
42
|
+
export declare const LabelOperations: Record<LabelOperationIds, OperationHandler<LabelOperationIds>>;
|
|
43
|
+
export declare const LabelDetailOperations: Record<LabelDetailOperationIds, OperationHandler<LabelDetailOperationIds>>;
|