@remit/backend 0.0.1
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/.env.test +7 -0
- package/dev-server/content-auth.test.ts +113 -0
- package/dev-server/content-auth.ts +54 -0
- package/dev-server/content-handler.test.ts +105 -0
- package/dev-server/content-handler.ts +79 -0
- package/dev-server/content-path.test.ts +37 -0
- package/dev-server/content-path.ts +27 -0
- package/dev-server/cors.test.ts +48 -0
- package/dev-server/cors.ts +25 -0
- package/dev-server/lambda-helpers.ts +69 -0
- package/dev-server/server.ts +289 -0
- package/package.json +82 -0
- package/src/auth.ts +92 -0
- package/src/config/msoauth.ts +60 -0
- package/src/data-backend.test.ts +25 -0
- package/src/data-backend.ts +20 -0
- package/src/derive/autoMoved.ts +28 -0
- package/src/derive/contentSignature.test.ts +153 -0
- package/src/derive/contentSignature.ts +139 -0
- package/src/derive/contentUrl.test.ts +156 -0
- package/src/derive/contentUrl.ts +70 -0
- package/src/derive/enrichThreadRows.ts +155 -0
- package/src/derive/filterThreadCriteria.test.ts +119 -0
- package/src/derive/filterThreadCriteria.ts +60 -0
- package/src/derive/pendingMoveCounts.ts +90 -0
- package/src/derive/senderTrust.test.ts +67 -0
- package/src/derive/senderTrust.ts +23 -0
- package/src/error.ts +55 -0
- package/src/handlers/account-guards.ts +137 -0
- package/src/handlers/account-oauth.test.ts +383 -0
- package/src/handlers/account-oauth.ts +452 -0
- package/src/handlers/account-overrides.test.ts +69 -0
- package/src/handlers/account-overrides.ts +286 -0
- package/src/handlers/account-ownership.ts +25 -0
- package/src/handlers/account-signature.ts +129 -0
- package/src/handlers/account.ts +524 -0
- package/src/handlers/address.ts +136 -0
- package/src/handlers/config.test.ts +184 -0
- package/src/handlers/config.ts +219 -0
- package/src/handlers/ensure-account-config.ts +30 -0
- package/src/handlers/filter.ts +294 -0
- package/src/handlers/folder-role-appointments.test.ts +250 -0
- package/src/handlers/folder-role-appointments.ts +272 -0
- package/src/handlers/folder-role.ts +76 -0
- package/src/handlers/index.ts +48 -0
- package/src/handlers/mailbox.ts +411 -0
- package/src/handlers/me.ts +190 -0
- package/src/handlers/message.ts +886 -0
- package/src/handlers/organize.test.ts +43 -0
- package/src/handlers/organize.ts +196 -0
- package/src/handlers/outbox.ts +218 -0
- package/src/handlers/search.test.ts +182 -0
- package/src/handlers/search.ts +105 -0
- package/src/handlers/sync-progress.test.ts +158 -0
- package/src/handlers/sync-progress.ts +64 -0
- package/src/handlers/sync.test.ts +146 -0
- package/src/handlers/sync.ts +119 -0
- package/src/handlers/thread.ts +361 -0
- package/src/handlers/unified-threads.ts +196 -0
- package/src/handlers/vip-suggestions.ts +21 -0
- package/src/index.ts +170 -0
- package/src/json.ts +11 -0
- package/src/jwt-auth.test.ts +89 -0
- package/src/jwt-auth.ts +95 -0
- package/src/request-context.test.ts +61 -0
- package/src/request-context.ts +29 -0
- package/src/request.ts +10 -0
- package/src/response.test.ts +107 -0
- package/src/response.ts +80 -0
- package/src/service/compose-postgres.ts +72 -0
- package/src/service/compose-sqlite.ts +80 -0
- package/src/service/create-remit-client.ts +358 -0
- package/src/service/dynamodb.test.ts +100 -0
- package/src/service/dynamodb.ts +58 -0
- package/src/service/filter.ts +55 -0
- package/src/service/fire-and-forget.test.ts +126 -0
- package/src/service/fire-and-forget.ts +79 -0
- package/src/service/localhost-env-config.test.ts +40 -0
- package/src/service/organize.test.ts +384 -0
- package/src/service/organize.ts +354 -0
- package/src/service/semantic-capability.test.ts +64 -0
- package/src/service/semantic-capability.ts +62 -0
- package/src/service/sqs.ts +4 -0
- package/src/service/trigger-sync.test.ts +211 -0
- package/src/service/trigger-sync.ts +102 -0
- package/src/types.ts +161 -0
- package/tsconfig.json +7 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CanonicalMailboxRole as CanonicalMailboxRoleValue,
|
|
3
|
+
FolderAppointment,
|
|
4
|
+
} from "@remit/api-openapi-types";
|
|
5
|
+
import type {
|
|
6
|
+
AccountSettingItem,
|
|
7
|
+
IAccountSettingRepository,
|
|
8
|
+
IMailboxRepository,
|
|
9
|
+
} from "@remit/data-ports";
|
|
10
|
+
import {
|
|
11
|
+
baseSettingName,
|
|
12
|
+
composeSettingName,
|
|
13
|
+
SETTING_NAME_SEPARATOR,
|
|
14
|
+
} from "@remit/data-ports/account-settings";
|
|
15
|
+
import {
|
|
16
|
+
AccountSettingName,
|
|
17
|
+
CanonicalMailboxRole,
|
|
18
|
+
MailboxSpecialUse,
|
|
19
|
+
} from "@remit/domain-enums";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* RFC 032 exclusive-folder-appointment (#976): a per-account role→mailbox map.
|
|
23
|
+
* Each row is a `FolderRoleAppointment#<accountId>#<role>` AccountSetting (RFC
|
|
24
|
+
* 032 settings tiers), so a role can never be persisted twice for one account —
|
|
25
|
+
* writing it replaces whichever mailbox previously held it. This module owns
|
|
26
|
+
* that persistence plus the read-side proposal (`findFolderForRole`) that fills
|
|
27
|
+
* any role the user hasn't appointed yet.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const FOLDER_ROLE_APPOINTMENT = AccountSettingName.FolderRoleAppointment;
|
|
31
|
+
|
|
32
|
+
/** The fixed anchor set, in the RFC's canonical display order. */
|
|
33
|
+
export const CANONICAL_ROLES: readonly CanonicalMailboxRoleValue[] =
|
|
34
|
+
Object.values(CanonicalMailboxRole);
|
|
35
|
+
|
|
36
|
+
const composeAppointmentName = (
|
|
37
|
+
accountId: string,
|
|
38
|
+
role: CanonicalMailboxRoleValue,
|
|
39
|
+
): string =>
|
|
40
|
+
composeSettingName(
|
|
41
|
+
FOLDER_ROLE_APPOINTMENT,
|
|
42
|
+
`${accountId}${SETTING_NAME_SEPARATOR}${role}`,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Split a stored `FolderRoleAppointment#<accountId>#<role>` name back into its
|
|
47
|
+
* two-part target. Unlike the single-target composites (`MailboxRole#<id>`),
|
|
48
|
+
* this setting composes two ids after the base, so it parses the suffix itself
|
|
49
|
+
* rather than reusing `targetIdOf`.
|
|
50
|
+
*/
|
|
51
|
+
const parseAppointmentTarget = (
|
|
52
|
+
name: string,
|
|
53
|
+
): { accountId: string; role: string } | undefined => {
|
|
54
|
+
if (baseSettingName(name) !== FOLDER_ROLE_APPOINTMENT) return undefined;
|
|
55
|
+
const idx = name.indexOf(SETTING_NAME_SEPARATOR);
|
|
56
|
+
if (idx === -1) return undefined;
|
|
57
|
+
const rest = name.slice(idx + SETTING_NAME_SEPARATOR.length);
|
|
58
|
+
const roleIdx = rest.lastIndexOf(SETTING_NAME_SEPARATOR);
|
|
59
|
+
if (roleIdx === -1) return undefined;
|
|
60
|
+
const accountId = rest.slice(0, roleIdx);
|
|
61
|
+
const role = rest.slice(roleIdx + SETTING_NAME_SEPARATOR.length);
|
|
62
|
+
if (!accountId || !role) return undefined;
|
|
63
|
+
return { accountId, role };
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const stringValueOf = (item: AccountSettingItem): string | undefined => {
|
|
67
|
+
const { value } = item;
|
|
68
|
+
return value.kind === "String" ? value.value : undefined;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Group every persisted folder-role appointment in one config-wide settings
|
|
73
|
+
* read (e.g. GET /config's already-loaded `listByAccountConfig` result) by
|
|
74
|
+
* accountId, then by role. Mirrors `groupAccountOverrides` / `groupMailboxOverrides`.
|
|
75
|
+
*/
|
|
76
|
+
export const groupFolderAppointmentsByAccount = (
|
|
77
|
+
settings: AccountSettingItem[],
|
|
78
|
+
): Map<string, Map<string, string>> => {
|
|
79
|
+
const byAccount = new Map<string, Map<string, string>>();
|
|
80
|
+
for (const setting of settings) {
|
|
81
|
+
const target = parseAppointmentTarget(setting.name);
|
|
82
|
+
if (!target) continue;
|
|
83
|
+
const mailboxId = stringValueOf(setting);
|
|
84
|
+
if (mailboxId === undefined) continue;
|
|
85
|
+
const roles = byAccount.get(target.accountId) ?? new Map<string, string>();
|
|
86
|
+
roles.set(target.role, mailboxId);
|
|
87
|
+
byAccount.set(target.accountId, roles);
|
|
88
|
+
}
|
|
89
|
+
return byAccount;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Load one account's persisted appointments by reading each role's row
|
|
94
|
+
* directly (mirrors `loadMailboxOverrides`'s three-get pattern). Used by the
|
|
95
|
+
* create/update account handlers, which only ever need a single account.
|
|
96
|
+
*/
|
|
97
|
+
export const loadFolderAppointmentsForAccount = async (
|
|
98
|
+
accountSetting: Pick<IAccountSettingRepository, "get">,
|
|
99
|
+
accountConfigId: string,
|
|
100
|
+
accountId: string,
|
|
101
|
+
): Promise<Map<string, string>> => {
|
|
102
|
+
const entries = await Promise.all(
|
|
103
|
+
CANONICAL_ROLES.map(async (role) => {
|
|
104
|
+
const item = await accountSetting.get(
|
|
105
|
+
accountConfigId,
|
|
106
|
+
composeAppointmentName(accountId, role),
|
|
107
|
+
);
|
|
108
|
+
return [role, item ? stringValueOf(item) : undefined] as const;
|
|
109
|
+
}),
|
|
110
|
+
);
|
|
111
|
+
const roles = new Map<string, string>();
|
|
112
|
+
for (const [role, mailboxId] of entries) {
|
|
113
|
+
if (mailboxId !== undefined) roles.set(role, mailboxId);
|
|
114
|
+
}
|
|
115
|
+
return roles;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Persist (or clear) one role's appointment. `mailboxId: null` deletes the row
|
|
120
|
+
* — the role goes back to unfilled (RFC 032 settings tiers: absence is unset).
|
|
121
|
+
* A value upserts it, replacing whatever mailbox the role pointed at before;
|
|
122
|
+
* there is no second row a duplicate could live in.
|
|
123
|
+
*/
|
|
124
|
+
export const writeFolderRoleAppointment = (
|
|
125
|
+
accountSetting: Pick<IAccountSettingRepository, "upsert" | "delete">,
|
|
126
|
+
accountConfigId: string,
|
|
127
|
+
accountId: string,
|
|
128
|
+
role: CanonicalMailboxRoleValue,
|
|
129
|
+
mailboxId: string | null,
|
|
130
|
+
): Promise<unknown> => {
|
|
131
|
+
const name = composeAppointmentName(accountId, role);
|
|
132
|
+
if (mailboxId === null) {
|
|
133
|
+
return accountSetting.delete(accountConfigId, name);
|
|
134
|
+
}
|
|
135
|
+
return accountSetting.upsert({
|
|
136
|
+
accountConfigId,
|
|
137
|
+
name,
|
|
138
|
+
value: { kind: "String", value: mailboxId },
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/** The minimal folder shape `findFolderForRole` needs to detect a role. */
|
|
143
|
+
export interface FolderCandidate {
|
|
144
|
+
mailboxId: string;
|
|
145
|
+
fullPath: string;
|
|
146
|
+
specialUse?: readonly string[];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// RFC 6154 SPECIAL-USE flag per role. Inbox has no SPECIAL-USE flag (RFC 3501
|
|
150
|
+
// reserves the name itself); a role with no entry here is matched by name hint
|
|
151
|
+
// only.
|
|
152
|
+
const ROLE_TO_SPECIAL_USE: Partial<Record<CanonicalMailboxRoleValue, string>> =
|
|
153
|
+
{
|
|
154
|
+
[CanonicalMailboxRole.Drafts]: MailboxSpecialUse.Drafts,
|
|
155
|
+
[CanonicalMailboxRole.Sent]: MailboxSpecialUse.Sent,
|
|
156
|
+
[CanonicalMailboxRole.Archive]: MailboxSpecialUse.Archive,
|
|
157
|
+
[CanonicalMailboxRole.Junk]: MailboxSpecialUse.Junk,
|
|
158
|
+
[CanonicalMailboxRole.Trash]: MailboxSpecialUse.Trash,
|
|
159
|
+
[CanonicalMailboxRole.All]: MailboxSpecialUse.All,
|
|
160
|
+
[CanonicalMailboxRole.Flagged]: MailboxSpecialUse.Flagged,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Weak name hints (RFC 032's tier 3 of `findFolderForRole`): used solely to
|
|
164
|
+
// seed a PROPOSAL a human confirms, never to persist a role by itself. Kept
|
|
165
|
+
// intentionally small — this is a fallback for providers with no SPECIAL-USE
|
|
166
|
+
// support, not a substitute for server truth.
|
|
167
|
+
const ROLE_NAME_HINTS: Partial<
|
|
168
|
+
Record<CanonicalMailboxRoleValue, readonly string[]>
|
|
169
|
+
> = {
|
|
170
|
+
[CanonicalMailboxRole.Drafts]: ["drafts", "draft", "concepten"],
|
|
171
|
+
[CanonicalMailboxRole.Sent]: [
|
|
172
|
+
"sent",
|
|
173
|
+
"sent mail",
|
|
174
|
+
"sent items",
|
|
175
|
+
"sent messages",
|
|
176
|
+
],
|
|
177
|
+
[CanonicalMailboxRole.Archive]: ["archive", "archives"],
|
|
178
|
+
[CanonicalMailboxRole.Junk]: ["junk", "spam"],
|
|
179
|
+
[CanonicalMailboxRole.Trash]: [
|
|
180
|
+
"trash",
|
|
181
|
+
"bin",
|
|
182
|
+
"deleted",
|
|
183
|
+
"deleted items",
|
|
184
|
+
"deleted messages",
|
|
185
|
+
],
|
|
186
|
+
[CanonicalMailboxRole.All]: ["all mail", "all"],
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const leafName = (fullPath: string): string => {
|
|
190
|
+
const parts = fullPath.split("/");
|
|
191
|
+
return (parts[parts.length - 1] || fullPath).toLowerCase();
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* The single best EXISTING folder for a canonical role (RFC 032
|
|
196
|
+
* exclusive-folder-appointment): the IMAP SPECIAL-USE flag first (server
|
|
197
|
+
* truth, language-independent), then — for Inbox only — the reserved `INBOX`
|
|
198
|
+
* name (RFC 3501), then a weak name hint used solely to seed a proposal a
|
|
199
|
+
* human confirms. `null` when nothing matches; the role stays unfilled.
|
|
200
|
+
*/
|
|
201
|
+
export const findFolderForRole = (
|
|
202
|
+
role: CanonicalMailboxRoleValue,
|
|
203
|
+
folders: readonly FolderCandidate[],
|
|
204
|
+
): string | null => {
|
|
205
|
+
const specialUse = ROLE_TO_SPECIAL_USE[role];
|
|
206
|
+
if (specialUse) {
|
|
207
|
+
const flagged = folders.find((f) => f.specialUse?.includes(specialUse));
|
|
208
|
+
if (flagged) return flagged.mailboxId;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (role === CanonicalMailboxRole.Inbox) {
|
|
212
|
+
const inbox = folders.find((f) => f.fullPath.toUpperCase() === "INBOX");
|
|
213
|
+
if (inbox) return inbox.mailboxId;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const hints = ROLE_NAME_HINTS[role];
|
|
217
|
+
if (hints) {
|
|
218
|
+
const match = folders.find((f) => hints.includes(leafName(f.fullPath)));
|
|
219
|
+
if (match) return match.mailboxId;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return null;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Resolve the full appointment set for one account: the user's persisted
|
|
227
|
+
* choice when set (and still a real mailbox — a deleted mailbox's stale
|
|
228
|
+
* appointment is treated as unfilled and re-proposed), else a server-proposed
|
|
229
|
+
* `findFolderForRole` guess. Always returns one entry per `CANONICAL_ROLES`
|
|
230
|
+
* member (RFC 032 settings tiers: total, never a sparse array), so the map is
|
|
231
|
+
* never empty for a normal provider.
|
|
232
|
+
*/
|
|
233
|
+
export const resolveFolderAppointments = (
|
|
234
|
+
persisted: ReadonlyMap<string, string>,
|
|
235
|
+
mailboxes: readonly FolderCandidate[],
|
|
236
|
+
): FolderAppointment[] =>
|
|
237
|
+
CANONICAL_ROLES.map((role) => {
|
|
238
|
+
const persistedId = persisted.get(role);
|
|
239
|
+
const validPersisted =
|
|
240
|
+
persistedId && mailboxes.some((m) => m.mailboxId === persistedId)
|
|
241
|
+
? persistedId
|
|
242
|
+
: undefined;
|
|
243
|
+
const mailboxId =
|
|
244
|
+
validPersisted ?? findFolderForRole(role, mailboxes) ?? undefined;
|
|
245
|
+
return mailboxId ? { role, mailboxId } : { role };
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* End-to-end resolution for one account: load its persisted appointments and
|
|
250
|
+
* its mailboxes, then resolve. The single entry point every handler that
|
|
251
|
+
* builds an AccountResponse calls (create/update account, GET /config's
|
|
252
|
+
* per-account fan-out mirrors this with a pre-loaded settings batch instead —
|
|
253
|
+
* see `groupFolderAppointmentsByAccount` — to avoid an N+1 settings read).
|
|
254
|
+
*/
|
|
255
|
+
export const resolveAccountFolderAppointments = async (
|
|
256
|
+
client: {
|
|
257
|
+
mailbox: Pick<IMailboxRepository, "listAllByAccount">;
|
|
258
|
+
accountSetting: Pick<IAccountSettingRepository, "get">;
|
|
259
|
+
},
|
|
260
|
+
accountConfigId: string,
|
|
261
|
+
accountId: string,
|
|
262
|
+
): Promise<FolderAppointment[]> => {
|
|
263
|
+
const [persisted, mailboxes] = await Promise.all([
|
|
264
|
+
loadFolderAppointmentsForAccount(
|
|
265
|
+
client.accountSetting,
|
|
266
|
+
accountConfigId,
|
|
267
|
+
accountId,
|
|
268
|
+
),
|
|
269
|
+
client.mailbox.listAllByAccount(accountId),
|
|
270
|
+
]);
|
|
271
|
+
return resolveFolderAppointments(persisted, mailboxes);
|
|
272
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AppointFolderRoleInput,
|
|
3
|
+
CanonicalMailboxRole,
|
|
4
|
+
} from "@remit/api-openapi-types";
|
|
5
|
+
import type { APIGatewayProxyEvent } from "aws-lambda";
|
|
6
|
+
import { getAccountConfigIdFromEvent } from "../auth.js";
|
|
7
|
+
import { getClient } from "../service/dynamodb.js";
|
|
8
|
+
import type { FolderRoleOperationIds, OperationHandler } from "../types.js";
|
|
9
|
+
import { toAccountResponse } from "./account-guards.js";
|
|
10
|
+
import { loadAccountOverrides } from "./account-overrides.js";
|
|
11
|
+
import { assertAccountOwnership } from "./account-ownership.js";
|
|
12
|
+
import { loadSignatureForAccount } from "./account-signature.js";
|
|
13
|
+
import {
|
|
14
|
+
resolveAccountFolderAppointments,
|
|
15
|
+
writeFolderRoleAppointment,
|
|
16
|
+
} from "./folder-role-appointments.js";
|
|
17
|
+
import { assertMailboxInAccount } from "./mailbox.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* RFC 032 exclusive-folder-appointment (#976): the single write operation for
|
|
21
|
+
* the per-account role map. `appoint(role, mailboxId)` sets `map[role]` —
|
|
22
|
+
* there is no second place a duplicate appointment could live, so writing a
|
|
23
|
+
* role replaces whichever mailbox previously held it. `mailboxId: null` clears
|
|
24
|
+
* the role back to unfilled.
|
|
25
|
+
*/
|
|
26
|
+
export const FolderRoleOperations: Record<
|
|
27
|
+
FolderRoleOperationIds,
|
|
28
|
+
OperationHandler<FolderRoleOperationIds>
|
|
29
|
+
> = {
|
|
30
|
+
FolderRoleOperations_appointFolderRole: async (
|
|
31
|
+
context,
|
|
32
|
+
...args: unknown[]
|
|
33
|
+
) => {
|
|
34
|
+
const event = args[0] as APIGatewayProxyEvent;
|
|
35
|
+
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
36
|
+
const { accountId, role } = context.request.params as {
|
|
37
|
+
accountId: string;
|
|
38
|
+
role: CanonicalMailboxRole;
|
|
39
|
+
};
|
|
40
|
+
const body = context.request.requestBody as AppointFolderRoleInput;
|
|
41
|
+
|
|
42
|
+
const { account, accountSetting, mailbox } = await getClient();
|
|
43
|
+
const existing = await account.get(accountId);
|
|
44
|
+
assertAccountOwnership(existing, accountConfigId, "act");
|
|
45
|
+
|
|
46
|
+
if (body.mailboxId) {
|
|
47
|
+
const target = await mailbox.get(accountId, body.mailboxId);
|
|
48
|
+
assertMailboxInAccount(target, accountId, "act");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await writeFolderRoleAppointment(
|
|
52
|
+
accountSetting,
|
|
53
|
+
accountConfigId,
|
|
54
|
+
accountId,
|
|
55
|
+
role,
|
|
56
|
+
body.mailboxId ?? null,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const [signature, overrides, folderAppointments] = await Promise.all([
|
|
60
|
+
loadSignatureForAccount(accountSetting, accountConfigId, accountId),
|
|
61
|
+
loadAccountOverrides(accountSetting, accountConfigId, accountId),
|
|
62
|
+
resolveAccountFolderAppointments(
|
|
63
|
+
{ mailbox, accountSetting },
|
|
64
|
+
accountConfigId,
|
|
65
|
+
accountId,
|
|
66
|
+
),
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
return toAccountResponse(
|
|
70
|
+
existing,
|
|
71
|
+
signature,
|
|
72
|
+
overrides,
|
|
73
|
+
folderAppointments,
|
|
74
|
+
);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { OperationHandler, OperationIds } from "../types.js";
|
|
2
|
+
import { AccountDetailOperations, AccountOperations } from "./account.js";
|
|
3
|
+
import { MicrosoftOAuthOperations } from "./account-oauth.js";
|
|
4
|
+
import { AddressDetailOperations, AddressOperations } from "./address.js";
|
|
5
|
+
import { ConfigOperations } from "./config.js";
|
|
6
|
+
import { FilterDetailOperations, FilterOperations } from "./filter.js";
|
|
7
|
+
import { FolderRoleOperations } from "./folder-role.js";
|
|
8
|
+
import {
|
|
9
|
+
MailboxDetailOperations,
|
|
10
|
+
MailboxOperations,
|
|
11
|
+
TrashOperations,
|
|
12
|
+
} 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 { OutboxDetailOperations, OutboxOperations } from "./outbox.js";
|
|
17
|
+
import { SemanticSearchOperations } from "./search.js";
|
|
18
|
+
import { SyncOperations } from "./sync.js";
|
|
19
|
+
import { ThreadDetailOperations, ThreadOperations } from "./thread.js";
|
|
20
|
+
import { UnifiedThreadOperations } from "./unified-threads.js";
|
|
21
|
+
|
|
22
|
+
// biome-ignore lint/suspicious/noExplicitAny: Types are narrowed downstream
|
|
23
|
+
export const handlers: Record<OperationIds, OperationHandler<any>> = {
|
|
24
|
+
...MeOperations,
|
|
25
|
+
...ConfigOperations,
|
|
26
|
+
...AccountOperations,
|
|
27
|
+
...AccountDetailOperations,
|
|
28
|
+
...MicrosoftOAuthOperations,
|
|
29
|
+
...MailboxOperations,
|
|
30
|
+
...MailboxDetailOperations,
|
|
31
|
+
...FolderRoleOperations,
|
|
32
|
+
...FilterOperations,
|
|
33
|
+
...FilterDetailOperations,
|
|
34
|
+
...OrganizeOperations,
|
|
35
|
+
...OrganizeJobDetailOperations,
|
|
36
|
+
...TrashOperations,
|
|
37
|
+
...SyncOperations,
|
|
38
|
+
...ThreadDetailOperations,
|
|
39
|
+
...UnifiedThreadOperations,
|
|
40
|
+
...ThreadOperations,
|
|
41
|
+
...MessageOperations,
|
|
42
|
+
...MessageBulkOperations,
|
|
43
|
+
...OutboxOperations,
|
|
44
|
+
...OutboxDetailOperations,
|
|
45
|
+
...AddressOperations,
|
|
46
|
+
...AddressDetailOperations,
|
|
47
|
+
...SemanticSearchOperations,
|
|
48
|
+
};
|