@remit/backend 0.0.143 → 0.0.145

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.
Files changed (279) hide show
  1. package/dist/dev-server/content-handler.d.ts +38 -0
  2. package/dist/dev-server/content-handler.js +46 -0
  3. package/dist/src/api.d.ts +5 -0
  4. package/dist/src/api.js +73 -0
  5. package/dist/src/auth.d.ts +24 -0
  6. package/dist/src/auth.js +81 -0
  7. package/dist/src/config/api-config.d.ts +20 -0
  8. package/dist/src/config/api-config.js +1 -0
  9. package/dist/src/config/from-env.d.ts +2 -0
  10. package/dist/src/config/from-env.js +65 -0
  11. package/dist/src/config/msoauth-secrets-manager.d.ts +2 -0
  12. package/dist/src/config/msoauth-secrets-manager.js +28 -0
  13. package/dist/src/config/msoauth.d.ts +19 -0
  14. package/dist/src/config/msoauth.js +6 -0
  15. package/{src/derive/autoMoved.ts → dist/src/derive/autoMoved.d.ts} +1 -29
  16. package/dist/src/derive/autoMoved.js +49 -0
  17. package/dist/src/derive/contentSignature.d.ts +40 -0
  18. package/{src/derive/contentSignature.ts → dist/src/derive/contentSignature.js} +23 -77
  19. package/dist/src/derive/contentUrl.d.ts +33 -0
  20. package/dist/src/derive/contentUrl.js +27 -0
  21. package/{src/derive/deriveMuted.ts → dist/src/derive/deriveMuted.d.ts} +1 -3
  22. package/dist/src/derive/deriveMuted.js +7 -0
  23. package/dist/src/derive/enrichThreadRows.d.ts +118 -0
  24. package/dist/src/derive/enrichThreadRows.js +151 -0
  25. package/dist/src/derive/filterThreadCriteria.d.ts +23 -0
  26. package/dist/src/derive/filterThreadCriteria.js +24 -0
  27. package/{src/derive/pendingMoveCounts.ts → dist/src/derive/pendingMoveCounts.d.ts} +5 -53
  28. package/dist/src/derive/pendingMoveCounts.js +53 -0
  29. package/{src/derive/senderTrust.ts → dist/src/derive/senderTrust.d.ts} +1 -9
  30. package/dist/src/derive/senderTrust.js +19 -0
  31. package/dist/src/error.d.ts +21 -0
  32. package/dist/src/error.js +85 -0
  33. package/dist/src/handlers/account-guards.d.ts +36 -0
  34. package/dist/src/handlers/account-guards.js +107 -0
  35. package/dist/src/handlers/account-oauth.d.ts +15 -0
  36. package/dist/src/handlers/account-oauth.js +334 -0
  37. package/dist/src/handlers/account-overrides.d.ts +53 -0
  38. package/dist/src/handlers/account-overrides.js +233 -0
  39. package/dist/src/handlers/account-signature.d.ts +28 -0
  40. package/dist/src/handlers/account-signature.js +85 -0
  41. package/dist/src/handlers/account.d.ts +7 -0
  42. package/dist/src/handlers/account.js +337 -0
  43. package/dist/src/handlers/address.d.ts +35 -0
  44. package/dist/src/handlers/address.js +133 -0
  45. package/dist/src/handlers/calendar-event.d.ts +70 -0
  46. package/dist/src/handlers/calendar-event.js +417 -0
  47. package/dist/src/handlers/calendar-feed.d.ts +47 -0
  48. package/dist/src/handlers/calendar-feed.js +184 -0
  49. package/dist/src/handlers/calendar-suggestion.d.ts +47 -0
  50. package/dist/src/handlers/calendar-suggestion.js +204 -0
  51. package/dist/src/handlers/calendar.d.ts +116 -0
  52. package/dist/src/handlers/calendar.js +323 -0
  53. package/dist/src/handlers/config.d.ts +43 -0
  54. package/dist/src/handlers/config.js +164 -0
  55. package/dist/src/handlers/ensure-account-config.d.ts +9 -0
  56. package/dist/src/handlers/ensure-account-config.js +27 -0
  57. package/dist/src/handlers/filter.d.ts +3 -0
  58. package/dist/src/handlers/filter.js +88 -0
  59. package/dist/src/handlers/folder-role-appointments.d.ts +68 -0
  60. package/dist/src/handlers/folder-role-appointments.js +168 -0
  61. package/dist/src/handlers/folder-role-labels.d.ts +26 -0
  62. package/dist/src/handlers/folder-role-labels.js +40 -0
  63. package/dist/src/handlers/folder-role.d.ts +9 -0
  64. package/dist/src/handlers/folder-role.js +37 -0
  65. package/dist/src/handlers/index.d.ts +88 -0
  66. package/dist/src/handlers/index.js +62 -0
  67. package/dist/src/handlers/label.d.ts +43 -0
  68. package/dist/src/handlers/label.js +107 -0
  69. package/dist/src/handlers/mailbox-bindings.d.ts +41 -0
  70. package/dist/src/handlers/mailbox-bindings.js +61 -0
  71. package/dist/src/handlers/mailbox.d.ts +23 -0
  72. package/dist/src/handlers/mailbox.js +211 -0
  73. package/dist/src/handlers/me.d.ts +2 -0
  74. package/dist/src/handlers/me.js +117 -0
  75. package/dist/src/handlers/message.d.ts +82 -0
  76. package/dist/src/handlers/message.js +595 -0
  77. package/dist/src/handlers/organize.d.ts +10 -0
  78. package/dist/src/handlers/organize.js +103 -0
  79. package/dist/src/handlers/outbox-attachment.d.ts +25 -0
  80. package/dist/src/handlers/outbox-attachment.js +77 -0
  81. package/dist/src/handlers/outbox.d.ts +4 -0
  82. package/dist/src/handlers/outbox.js +136 -0
  83. package/dist/src/handlers/sync-progress.d.ts +46 -0
  84. package/dist/src/handlers/sync-progress.js +63 -0
  85. package/dist/src/handlers/sync.d.ts +28 -0
  86. package/dist/src/handlers/sync.js +73 -0
  87. package/dist/src/handlers/system-update.d.ts +2 -0
  88. package/dist/src/handlers/system-update.js +159 -0
  89. package/dist/src/handlers/thread.d.ts +148 -0
  90. package/dist/src/handlers/thread.js +228 -0
  91. package/dist/src/handlers/unified-threads.d.ts +247 -0
  92. package/dist/src/handlers/unified-threads.js +406 -0
  93. package/{src/handlers/vip-suggestions.ts → dist/src/handlers/vip-suggestions.d.ts} +1 -13
  94. package/dist/src/handlers/vip-suggestions.js +16 -0
  95. package/dist/src/index.d.ts +5 -0
  96. package/dist/src/index.js +53 -0
  97. package/{src/json.ts → dist/src/json.d.ts} +1 -4
  98. package/dist/src/json.js +10 -0
  99. package/dist/src/jwt-auth.d.ts +15 -0
  100. package/dist/src/jwt-auth.js +45 -0
  101. package/dist/src/lambda.d.ts +13 -0
  102. package/dist/src/lambda.js +33 -0
  103. package/dist/src/request.d.ts +9 -0
  104. package/dist/src/request.js +17 -0
  105. package/dist/src/response.d.ts +44 -0
  106. package/dist/src/response.js +80 -0
  107. package/dist/src/serve.d.ts +31 -0
  108. package/dist/src/serve.js +117 -0
  109. package/dist/src/service/account-deletion.d.ts +20 -0
  110. package/dist/src/service/account-deletion.js +33 -0
  111. package/dist/src/service/compose-sqlite.d.ts +3 -0
  112. package/dist/src/service/compose-sqlite.js +13 -0
  113. package/dist/src/service/create-remit-client.d.ts +109 -0
  114. package/dist/src/service/create-remit-client.js +188 -0
  115. package/dist/src/service/data-client.d.ts +12 -0
  116. package/dist/src/service/data-client.js +42 -0
  117. package/dist/src/service/export-identity.d.ts +4 -0
  118. package/dist/src/service/export-identity.js +33 -0
  119. package/dist/src/service/fire-and-forget.d.ts +54 -0
  120. package/dist/src/service/fire-and-forget.js +37 -0
  121. package/dist/src/service/organize.d.ts +181 -0
  122. package/dist/src/service/organize.js +367 -0
  123. package/dist/src/service/queues.d.ts +12 -0
  124. package/dist/src/service/queues.js +1 -0
  125. package/dist/src/service/refusal.d.ts +4 -0
  126. package/dist/src/service/refusal.js +20 -0
  127. package/dist/src/service/sender-category.d.ts +51 -0
  128. package/{src/service/sender-category.ts → dist/src/service/sender-category.js} +40 -81
  129. package/dist/src/service/shared-deps.d.ts +6 -0
  130. package/dist/src/service/shared-deps.js +24 -0
  131. package/dist/src/service/trigger-sync.d.ts +57 -0
  132. package/dist/src/service/trigger-sync.js +31 -0
  133. package/dist/src/types.d.ts +72 -0
  134. package/dist/src/types.js +1 -0
  135. package/package.json +125 -98
  136. package/.env.test +0 -4
  137. package/dev-server/content-auth.test.ts +0 -113
  138. package/dev-server/content-auth.ts +0 -54
  139. package/dev-server/content-handler.test.ts +0 -105
  140. package/dev-server/content-handler.ts +0 -79
  141. package/dev-server/content-path.test.ts +0 -37
  142. package/dev-server/content-path.ts +0 -27
  143. package/dev-server/cors.test.ts +0 -48
  144. package/dev-server/cors.ts +0 -25
  145. package/dev-server/keep-alive.test.ts +0 -24
  146. package/dev-server/lambda-helpers.ts +0 -69
  147. package/dev-server/log-output.test.ts +0 -173
  148. package/dev-server/oauth-callback-route.test.ts +0 -66
  149. package/dev-server/relational-health.test.ts +0 -83
  150. package/dev-server/relational-health.ts +0 -39
  151. package/dev-server/server.ts +0 -384
  152. package/dev-server/sync-age.test.ts +0 -91
  153. package/dev-server/sync-age.ts +0 -55
  154. package/dev-server/upload-handler.ts +0 -132
  155. package/dev-server/upload-route.test.ts +0 -196
  156. package/scripts/backfill-classification.ts +0 -91
  157. package/scripts/backfill-list-id.ts +0 -83
  158. package/scripts/config-save.ts +0 -128
  159. package/src/auth.test.ts +0 -12
  160. package/src/auth.ts +0 -92
  161. package/src/config/msoauth.test.ts +0 -48
  162. package/src/config/msoauth.ts +0 -48
  163. package/src/data-backend.test.ts +0 -23
  164. package/src/data-backend.ts +0 -14
  165. package/src/derive/autoMoved.test.ts +0 -104
  166. package/src/derive/contentSignature.test.ts +0 -161
  167. package/src/derive/contentUrl.test.ts +0 -172
  168. package/src/derive/contentUrl.ts +0 -86
  169. package/src/derive/deriveMuted.test.ts +0 -34
  170. package/src/derive/enrichThreadRows.test.ts +0 -360
  171. package/src/derive/enrichThreadRows.ts +0 -234
  172. package/src/derive/filterThreadCriteria.test.ts +0 -126
  173. package/src/derive/filterThreadCriteria.ts +0 -51
  174. package/src/derive/senderTrust.test.ts +0 -67
  175. package/src/error.test.ts +0 -201
  176. package/src/error.ts +0 -123
  177. package/src/handlers/account-guards.test.ts +0 -67
  178. package/src/handlers/account-guards.ts +0 -169
  179. package/src/handlers/account-oauth-callback.test.ts +0 -155
  180. package/src/handlers/account-oauth-consent.test.ts +0 -336
  181. package/src/handlers/account-oauth-start.test.ts +0 -191
  182. package/src/handlers/account-oauth.test.ts +0 -439
  183. package/src/handlers/account-oauth.ts +0 -635
  184. package/src/handlers/account-overrides.test.ts +0 -88
  185. package/src/handlers/account-overrides.ts +0 -323
  186. package/src/handlers/account-ownership.ts +0 -25
  187. package/src/handlers/account-signature.ts +0 -117
  188. package/src/handlers/account-synced-services.test.ts +0 -245
  189. package/src/handlers/account.ts +0 -552
  190. package/src/handlers/address.test.ts +0 -381
  191. package/src/handlers/address.ts +0 -200
  192. package/src/handlers/calendar-event.test.ts +0 -685
  193. package/src/handlers/calendar-event.ts +0 -738
  194. package/src/handlers/calendar-feed-validators.test.ts +0 -228
  195. package/src/handlers/calendar-feed.test.ts +0 -439
  196. package/src/handlers/calendar-feed.ts +0 -291
  197. package/src/handlers/calendar-sqlite-fixture.ts +0 -36
  198. package/src/handlers/calendar-suggestion.test.ts +0 -1269
  199. package/src/handlers/calendar-suggestion.ts +0 -425
  200. package/src/handlers/calendar.test.ts +0 -1080
  201. package/src/handlers/calendar.ts +0 -517
  202. package/src/handlers/config.test.ts +0 -245
  203. package/src/handlers/config.ts +0 -332
  204. package/src/handlers/ensure-account-config.ts +0 -30
  205. package/src/handlers/filter.test.ts +0 -644
  206. package/src/handlers/filter.ts +0 -518
  207. package/src/handlers/folder-role-appointments.test.ts +0 -438
  208. package/src/handlers/folder-role-appointments.ts +0 -249
  209. package/src/handlers/folder-role-labels.ts +0 -63
  210. package/src/handlers/folder-role.test.ts +0 -209
  211. package/src/handlers/folder-role.ts +0 -80
  212. package/src/handlers/index.ts +0 -83
  213. package/src/handlers/label.test.ts +0 -112
  214. package/src/handlers/label.ts +0 -210
  215. package/src/handlers/mailbox-bindings.ts +0 -111
  216. package/src/handlers/mailbox-rename-refusal.test.ts +0 -247
  217. package/src/handlers/mailbox-settled-target.test.ts +0 -251
  218. package/src/handlers/mailbox.test.ts +0 -280
  219. package/src/handlers/mailbox.ts +0 -581
  220. package/src/handlers/me.test.ts +0 -81
  221. package/src/handlers/me.ts +0 -203
  222. package/src/handlers/message-read-backfill.test.ts +0 -103
  223. package/src/handlers/message.test.ts +0 -131
  224. package/src/handlers/message.ts +0 -1131
  225. package/src/handlers/organize.test.ts +0 -277
  226. package/src/handlers/organize.ts +0 -197
  227. package/src/handlers/outbox-attachment.test.ts +0 -765
  228. package/src/handlers/outbox-attachment.ts +0 -126
  229. package/src/handlers/outbox-list.test.ts +0 -179
  230. package/src/handlers/outbox.test.ts +0 -772
  231. package/src/handlers/outbox.ts +0 -258
  232. package/src/handlers/search.test.ts +0 -161
  233. package/src/handlers/search.ts +0 -105
  234. package/src/handlers/sync-progress.test.ts +0 -205
  235. package/src/handlers/sync-progress.ts +0 -99
  236. package/src/handlers/sync.test.ts +0 -168
  237. package/src/handlers/sync.ts +0 -118
  238. package/src/handlers/system-update.test.ts +0 -497
  239. package/src/handlers/system-update.ts +0 -230
  240. package/src/handlers/thread.test.ts +0 -396
  241. package/src/handlers/thread.ts +0 -427
  242. package/src/handlers/unified-threads-mail-sources.sqlite.test.ts +0 -197
  243. package/src/handlers/unified-threads.test.ts +0 -785
  244. package/src/handlers/unified-threads.ts +0 -669
  245. package/src/index.test.ts +0 -83
  246. package/src/index.ts +0 -228
  247. package/src/jwt-auth.test.ts +0 -152
  248. package/src/jwt-auth.ts +0 -132
  249. package/src/log-redaction.test.ts +0 -144
  250. package/src/openapi-spec.test.ts +0 -118
  251. package/src/request-context.test.ts +0 -61
  252. package/src/request-context.ts +0 -33
  253. package/src/request-pipeline.test.ts +0 -88
  254. package/src/request.ts +0 -10
  255. package/src/response.test.ts +0 -130
  256. package/src/response.ts +0 -144
  257. package/src/service/compose-sqlite.ts +0 -98
  258. package/src/service/config-import.ts +0 -39
  259. package/src/service/create-remit-client.ts +0 -522
  260. package/src/service/data-client.test.ts +0 -97
  261. package/src/service/data-client.ts +0 -58
  262. package/src/service/export-identity.ts +0 -39
  263. package/src/service/filter.ts +0 -40
  264. package/src/service/fire-and-forget.test.ts +0 -126
  265. package/src/service/fire-and-forget.ts +0 -79
  266. package/src/service/localhost-env-config.test.ts +0 -36
  267. package/src/service/organize-queue.ts +0 -98
  268. package/src/service/organize.test.ts +0 -1451
  269. package/src/service/organize.ts +0 -1025
  270. package/src/service/read-path-filter-config.test.ts +0 -243
  271. package/src/service/semantic-capability.test.ts +0 -78
  272. package/src/service/semantic-capability.ts +0 -95
  273. package/src/service/sender-category.test.ts +0 -235
  274. package/src/service/sqs.ts +0 -4
  275. package/src/service/trigger-sync.test.ts +0 -205
  276. package/src/service/trigger-sync.ts +0 -100
  277. package/src/stdout-capture-fixture.ts +0 -40
  278. package/src/types.ts +0 -271
  279. package/tsconfig.json +0 -7
@@ -1,88 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { describe, it } from "node:test";
3
- import type { AccountSettingItem } from "@remit/data-ports";
4
- import {
5
- groupAccountOverrides,
6
- groupMailboxOverrides,
7
- } from "./account-overrides.js";
8
-
9
- const setting = (
10
- name: string,
11
- value: AccountSettingItem["value"],
12
- ): AccountSettingItem =>
13
- ({
14
- accountSettingId: `s-${name}`,
15
- accountConfigId: "cfg-1",
16
- name,
17
- value,
18
- createdAt: 0,
19
- updatedAt: 0,
20
- }) as AccountSettingItem;
21
-
22
- const stringSetting = (name: string, value: string): AccountSettingItem =>
23
- setting(name, { kind: "String", value });
24
-
25
- describe("groupAccountOverrides", () => {
26
- it("groups displayName and muted overrides by accountId", () => {
27
- const settings = [
28
- stringSetting("AccountDisplayName#acc-1", "Alice"),
29
- stringSetting("AccountDisplayName#acc-2", "Bob"),
30
- ];
31
- const grouped = groupAccountOverrides(settings);
32
- assert.equal(grouped.get("acc-1")?.displayName, "Alice");
33
- assert.equal(grouped.get("acc-2")?.displayName, "Bob");
34
- });
35
-
36
- it("groups the compose-language list by accountId", () => {
37
- const settings = [
38
- setting("AccountComposeLanguages#acc-1", {
39
- kind: "StringList",
40
- value: ["nl", "en"],
41
- }),
42
- stringSetting("AccountDisplayName#acc-1", "Alice"),
43
- ];
44
- const grouped = groupAccountOverrides(settings);
45
- assert.deepEqual(grouped.get("acc-1")?.composeLanguages, ["nl", "en"]);
46
- assert.equal(grouped.get("acc-1")?.displayName, "Alice");
47
- });
48
-
49
- it("ignores a compose-language row stored under the wrong value kind", () => {
50
- const settings = [stringSetting("AccountComposeLanguages#acc-1", "nl")];
51
- const grouped = groupAccountOverrides(settings);
52
- assert.equal(grouped.get("acc-1"), undefined);
53
- });
54
-
55
- it("ignores a leftover MailboxRole#<mailboxId> row instead of throwing", () => {
56
- // Leftover rows from the #963/#964 backfill (superseded by
57
- // FolderRoleAppointment, RFC 032 exclusive-folder-appointment #976) must
58
- // not make GET /config throw — groupAccountOverrides only reacts to
59
- // AccountDisplayName/AccountMuted bases, so this row is simply skipped.
60
- const settings = [
61
- stringSetting("AccountDisplayName#acc-1", "Alice"),
62
- stringSetting("MailboxRole#mb-legacy", "custom"),
63
- ];
64
- assert.doesNotThrow(() => groupAccountOverrides(settings));
65
- const grouped = groupAccountOverrides(settings);
66
- assert.equal(grouped.get("acc-1")?.displayName, "Alice");
67
- assert.equal(grouped.size, 1);
68
- });
69
- });
70
-
71
- describe("groupMailboxOverrides", () => {
72
- it("groups displayNameOverride and muted overrides by mailboxId", () => {
73
- const settings = [stringSetting("MailboxDisplayName#mb-1", "Work")];
74
- const grouped = groupMailboxOverrides(settings);
75
- assert.equal(grouped.get("mb-1")?.displayNameOverride, "Work");
76
- });
77
-
78
- it("ignores a leftover MailboxRole#<mailboxId> row instead of throwing", () => {
79
- const settings = [
80
- stringSetting("MailboxDisplayName#mb-1", "Work"),
81
- stringSetting("MailboxRole#mb-legacy", "custom"),
82
- ];
83
- assert.doesNotThrow(() => groupMailboxOverrides(settings));
84
- const grouped = groupMailboxOverrides(settings);
85
- assert.equal(grouped.get("mb-1")?.displayNameOverride, "Work");
86
- assert.equal(grouped.size, 1);
87
- });
88
- });
@@ -1,323 +0,0 @@
1
- import type { MutedFlag } from "@remit/api-openapi-types";
2
- import type {
3
- AccountSettingItem,
4
- IAccountSettingRepository,
5
- } from "@remit/data-ports";
6
- import {
7
- baseSettingName,
8
- composeSettingName,
9
- SETTING_NAME_SEPARATOR,
10
- } from "@remit/data-ports/account-settings";
11
-
12
- /**
13
- * RFC 032 Tier 1: the per-account display name and mute flag, and the
14
- * per-mailbox display-name override and mute flag, live in AccountSetting
15
- * rows — not on the Account/Mailbox entities. They are stored as composite-named
16
- * rows keyed by the target id (`AccountDisplayName#<accountId>`,
17
- * `MailboxMuted#<mailboxId>`, …), so one `listByAccountConfig` query yields every
18
- * target's overrides; readers group them by the `#<targetId>` suffix. This mirrors
19
- * the signature helper (`account-signature.ts`). The per-folder role override
20
- * this module used to carry is superseded by the per-account role map — see
21
- * `folder-role-appointments.ts` (RFC 032 exclusive-folder-appointment, #976).
22
- */
23
-
24
- const ACCOUNT_OVERRIDE_NAMES = {
25
- displayName: "AccountDisplayName",
26
- muted: "AccountMuted",
27
- composeLanguages: "AccountComposeLanguages",
28
- } as const;
29
-
30
- const MAILBOX_OVERRIDE_NAMES = {
31
- displayName: "MailboxDisplayName",
32
- muted: "MailboxMuted",
33
- } as const;
34
-
35
- export interface AccountOverrides {
36
- displayName?: string;
37
- muted?: MutedFlag;
38
- composeLanguages?: string[];
39
- }
40
-
41
- export interface MailboxOverrides {
42
- displayNameOverride?: string;
43
- muted?: MutedFlag;
44
- }
45
-
46
- const stringValueOf = (item: AccountSettingItem): string | undefined => {
47
- const { value } = item;
48
- if (value.kind === "String") return value.value;
49
- return undefined;
50
- };
51
-
52
- const mutedValueOf = (item: AccountSettingItem): MutedFlag | undefined => {
53
- const { value } = item;
54
- if (value.kind === "MutedFlag") return value.value;
55
- return undefined;
56
- };
57
-
58
- const stringListValueOf = (item: AccountSettingItem): string[] | undefined => {
59
- const { value } = item;
60
- if (value.kind === "StringList") return value.value;
61
- return undefined;
62
- };
63
-
64
- const targetIdOf = (name: string): string | undefined => {
65
- const idx = name.indexOf(SETTING_NAME_SEPARATOR);
66
- if (idx === -1) return undefined;
67
- return name.slice(idx + SETTING_NAME_SEPARATOR.length) || undefined;
68
- };
69
-
70
- // ============================================
71
- // Account overrides (displayName + muted)
72
- // ============================================
73
-
74
- export const groupAccountOverrides = (
75
- settings: AccountSettingItem[],
76
- ): Map<string, AccountOverrides> => {
77
- const byAccount = new Map<string, AccountOverrides>();
78
- for (const setting of settings) {
79
- const accountId = targetIdOf(setting.name);
80
- if (!accountId) continue;
81
- const base = baseSettingName(setting.name);
82
- if (base === ACCOUNT_OVERRIDE_NAMES.displayName) {
83
- const displayName = stringValueOf(setting);
84
- if (displayName === undefined) continue;
85
- const current = byAccount.get(accountId) ?? {};
86
- current.displayName = displayName;
87
- byAccount.set(accountId, current);
88
- continue;
89
- }
90
- if (base === ACCOUNT_OVERRIDE_NAMES.muted) {
91
- const muted = mutedValueOf(setting);
92
- if (muted === undefined) continue;
93
- const current = byAccount.get(accountId) ?? {};
94
- current.muted = muted;
95
- byAccount.set(accountId, current);
96
- continue;
97
- }
98
- if (base === ACCOUNT_OVERRIDE_NAMES.composeLanguages) {
99
- const composeLanguages = stringListValueOf(setting);
100
- if (composeLanguages === undefined) continue;
101
- const current = byAccount.get(accountId) ?? {};
102
- current.composeLanguages = composeLanguages;
103
- byAccount.set(accountId, current);
104
- }
105
- }
106
- return byAccount;
107
- };
108
-
109
- /**
110
- * Resolve one account's display-name/mute overrides by reading just its two
111
- * composite rows. Used by the account create/update handlers.
112
- */
113
- export const loadAccountOverrides = async (
114
- accountSetting: IAccountSettingRepository,
115
- accountConfigId: string,
116
- accountId: string,
117
- ): Promise<AccountOverrides> => {
118
- const [displayName, muted, composeLanguages] = await Promise.all([
119
- accountSetting.get(
120
- accountConfigId,
121
- composeSettingName(ACCOUNT_OVERRIDE_NAMES.displayName, accountId),
122
- ),
123
- accountSetting.get(
124
- accountConfigId,
125
- composeSettingName(ACCOUNT_OVERRIDE_NAMES.muted, accountId),
126
- ),
127
- accountSetting.get(
128
- accountConfigId,
129
- composeSettingName(ACCOUNT_OVERRIDE_NAMES.composeLanguages, accountId),
130
- ),
131
- ]);
132
- const overrides: AccountOverrides = {};
133
- const displayNameValue = displayName ? stringValueOf(displayName) : undefined;
134
- const mutedValue = muted ? mutedValueOf(muted) : undefined;
135
- const languagesValue = composeLanguages
136
- ? stringListValueOf(composeLanguages)
137
- : undefined;
138
- if (displayNameValue !== undefined) overrides.displayName = displayNameValue;
139
- if (mutedValue !== undefined) overrides.muted = mutedValue;
140
- if (languagesValue !== undefined) overrides.composeLanguages = languagesValue;
141
- return overrides;
142
- };
143
-
144
- export const upsertAccountDisplayName = (
145
- accountSetting: IAccountSettingRepository,
146
- accountConfigId: string,
147
- accountId: string,
148
- displayName: string,
149
- ): Promise<unknown> =>
150
- accountSetting.upsert({
151
- accountConfigId,
152
- name: composeSettingName(ACCOUNT_OVERRIDE_NAMES.displayName, accountId),
153
- value: { kind: "String", value: displayName },
154
- });
155
-
156
- /**
157
- * Replace the account's compose-language list. An empty array deletes the row:
158
- * the client falls back to the browser's own languages when the setting is
159
- * absent, and an account that offers no language at all has nothing to pick
160
- * from (RFC 032: absence is "unset").
161
- */
162
- export const writeAccountComposeLanguages = (
163
- accountSetting: IAccountSettingRepository,
164
- accountConfigId: string,
165
- accountId: string,
166
- languages: string[],
167
- ): Promise<unknown> => {
168
- const name = composeSettingName(
169
- ACCOUNT_OVERRIDE_NAMES.composeLanguages,
170
- accountId,
171
- );
172
- if (languages.length === 0) {
173
- return accountSetting.delete(accountConfigId, name);
174
- }
175
- return accountSetting.upsert({
176
- accountConfigId,
177
- name,
178
- value: { kind: "StringList", value: languages },
179
- });
180
- };
181
-
182
- /**
183
- * Set or clear the account mute flag. A `MutedFlag` upserts the row; `null`
184
- * deletes it (RFC 032: absence is "unset"). Mirrors the address-flag
185
- * null→remove, object→set semantics.
186
- */
187
- export const writeAccountMuted = (
188
- accountSetting: IAccountSettingRepository,
189
- accountConfigId: string,
190
- accountId: string,
191
- muted: MutedFlag | null,
192
- ): Promise<unknown> => {
193
- const name = composeSettingName(ACCOUNT_OVERRIDE_NAMES.muted, accountId);
194
- if (muted === null) {
195
- return accountSetting.delete(accountConfigId, name);
196
- }
197
- return accountSetting.upsert({
198
- accountConfigId,
199
- name,
200
- value: { kind: "MutedFlag", value: muted },
201
- });
202
- };
203
-
204
- // ============================================
205
- // Mailbox overrides (displayNameOverride + muted)
206
- // ============================================
207
-
208
- export const groupMailboxOverrides = (
209
- settings: AccountSettingItem[],
210
- ): Map<string, MailboxOverrides> => {
211
- const byMailbox = new Map<string, MailboxOverrides>();
212
- for (const setting of settings) {
213
- const mailboxId = targetIdOf(setting.name);
214
- if (!mailboxId) continue;
215
- const base = baseSettingName(setting.name);
216
- if (base === MAILBOX_OVERRIDE_NAMES.displayName) {
217
- const displayName = stringValueOf(setting);
218
- if (displayName === undefined) continue;
219
- const current = byMailbox.get(mailboxId) ?? {};
220
- current.displayNameOverride = displayName;
221
- byMailbox.set(mailboxId, current);
222
- continue;
223
- }
224
- if (base === MAILBOX_OVERRIDE_NAMES.muted) {
225
- const muted = mutedValueOf(setting);
226
- if (muted === undefined) continue;
227
- const current = byMailbox.get(mailboxId) ?? {};
228
- current.muted = muted;
229
- byMailbox.set(mailboxId, current);
230
- }
231
- }
232
- return byMailbox;
233
- };
234
-
235
- /**
236
- * Load every mailbox's overrides for an account configuration in one query,
237
- * grouped by mailboxId. listMailboxes uses this once to avoid an N+1 per mailbox.
238
- */
239
- export const loadMailboxOverridesForConfig = async (
240
- accountSetting: IAccountSettingRepository,
241
- accountConfigId: string,
242
- ): Promise<Map<string, MailboxOverrides>> => {
243
- const settings = await accountSetting.listByAccountConfig(accountConfigId);
244
- return groupMailboxOverrides(settings);
245
- };
246
-
247
- /**
248
- * Resolve one mailbox's overrides by reading just its two composite rows.
249
- * Used by getMailbox and the rename/PATCH handler.
250
- */
251
- export const loadMailboxOverrides = async (
252
- accountSetting: IAccountSettingRepository,
253
- accountConfigId: string,
254
- mailboxId: string,
255
- ): Promise<MailboxOverrides> => {
256
- const [displayName, muted] = await Promise.all([
257
- accountSetting.get(
258
- accountConfigId,
259
- composeSettingName(MAILBOX_OVERRIDE_NAMES.displayName, mailboxId),
260
- ),
261
- accountSetting.get(
262
- accountConfigId,
263
- composeSettingName(MAILBOX_OVERRIDE_NAMES.muted, mailboxId),
264
- ),
265
- ]);
266
- const overrides: MailboxOverrides = {};
267
- const displayNameValue = displayName ? stringValueOf(displayName) : undefined;
268
- const mutedValue = muted ? mutedValueOf(muted) : undefined;
269
- if (displayNameValue !== undefined)
270
- overrides.displayNameOverride = displayNameValue;
271
- if (mutedValue !== undefined) overrides.muted = mutedValue;
272
- return overrides;
273
- };
274
-
275
- /**
276
- * Apply a mailbox PATCH's override changes (displayNameOverride / muted) to
277
- * AccountSetting rows. For each field: `null` deletes the row, a value upserts
278
- * it, `undefined`/absent is a no-op — the same semantics the entity-backed
279
- * version used. The canonical role a folder fills is written separately via
280
- * `writeFolderRoleAppointment` (RFC 032 exclusive-folder-appointment, #976).
281
- */
282
- export const applyMailboxOverrideChanges = async (
283
- accountSetting: Pick<IAccountSettingRepository, "upsert" | "delete">,
284
- accountConfigId: string,
285
- mailboxId: string,
286
- changes: {
287
- displayNameOverride?: string | null;
288
- muted?: MutedFlag | null;
289
- },
290
- ): Promise<void> => {
291
- const writes: Promise<unknown>[] = [];
292
-
293
- if (changes.displayNameOverride !== undefined) {
294
- const name = composeSettingName(
295
- MAILBOX_OVERRIDE_NAMES.displayName,
296
- mailboxId,
297
- );
298
- writes.push(
299
- changes.displayNameOverride === null
300
- ? accountSetting.delete(accountConfigId, name)
301
- : accountSetting.upsert({
302
- accountConfigId,
303
- name,
304
- value: { kind: "String", value: changes.displayNameOverride },
305
- }),
306
- );
307
- }
308
-
309
- if (changes.muted !== undefined) {
310
- const name = composeSettingName(MAILBOX_OVERRIDE_NAMES.muted, mailboxId);
311
- writes.push(
312
- changes.muted === null
313
- ? accountSetting.delete(accountConfigId, name)
314
- : accountSetting.upsert({
315
- accountConfigId,
316
- name,
317
- value: { kind: "MutedFlag", value: changes.muted },
318
- }),
319
- );
320
- }
321
-
322
- await Promise.all(writes);
323
- };
@@ -1,25 +0,0 @@
1
- import type { AccountItem } from "@remit/data-ports";
2
- import { ForbiddenError, NotFoundError } from "@remit/data-ports/errors";
3
-
4
- /**
5
- * Cross-tenant ownership guard for accounts.
6
- *
7
- * `mode: "read"` throws NotFoundError on mismatch (404) so we don't leak the
8
- * existence of another tenant's account on a GET. `mode: "act"` throws
9
- * ForbiddenError on mismatch (403) for action verbs (PATCH/POST/DELETE) where
10
- * the caller has already named the resource and the API contract says we
11
- * explicitly deny rather than feign 404.
12
- */
13
- export const assertAccountOwnership = (
14
- account: Pick<AccountItem, "accountId" | "accountConfigId">,
15
- callerAccountConfigId: string,
16
- mode: "read" | "act",
17
- ): void => {
18
- if (account.accountConfigId === callerAccountConfigId) return;
19
- if (mode === "read") {
20
- throw new NotFoundError(`Account not found: ${account.accountId}`);
21
- }
22
- throw new ForbiddenError(
23
- `Account ${account.accountId} not in account config`,
24
- );
25
- };
@@ -1,117 +0,0 @@
1
- import type {
2
- AccountSettingItem,
3
- IAccountSettingRepository,
4
- } from "@remit/data-ports";
5
- import {
6
- baseSettingName,
7
- composeSettingName,
8
- SETTING_NAME_SEPARATOR,
9
- } from "@remit/data-ports/account-settings";
10
-
11
- export interface AccountSignature {
12
- plainText?: string;
13
- html?: string;
14
- }
15
-
16
- const SIGNATURE_NAMES = {
17
- plainText: "AccountSignaturePlainText",
18
- html: "AccountSignatureHtml",
19
- } as const;
20
-
21
- const stringValueOf = (item: AccountSettingItem): string | undefined => {
22
- const { value } = item;
23
- if (value.kind === "String") return value.value;
24
- return undefined;
25
- };
26
-
27
- const accountIdOf = (name: string): string | undefined => {
28
- const idx = name.indexOf(SETTING_NAME_SEPARATOR);
29
- if (idx === -1) return undefined;
30
- return name.slice(idx + SETTING_NAME_SEPARATOR.length) || undefined;
31
- };
32
-
33
- /**
34
- * Build a per-account signature lookup from the whole settings set of one
35
- * account configuration. Signatures are stored as composite-named rows
36
- * (`AccountSignaturePlainText#<accountId>` / `AccountSignatureHtml#<accountId>`),
37
- * so one `listByAccountConfig` query yields every account's signature; this
38
- * groups them by the `#<accountId>` suffix.
39
- */
40
- export const groupSignaturesByAccount = (
41
- settings: AccountSettingItem[],
42
- ): Map<string, AccountSignature> => {
43
- const byAccount = new Map<string, AccountSignature>();
44
- for (const setting of settings) {
45
- const accountId = accountIdOf(setting.name);
46
- if (!accountId) continue;
47
- const base = baseSettingName(setting.name);
48
- const text = stringValueOf(setting);
49
- if (text === undefined) continue;
50
- const current = byAccount.get(accountId) ?? {};
51
- if (base === SIGNATURE_NAMES.plainText) current.plainText = text;
52
- if (base === SIGNATURE_NAMES.html) current.html = text;
53
- byAccount.set(accountId, current);
54
- }
55
- return byAccount;
56
- };
57
-
58
- /**
59
- * Resolve the signature for a single account by reading just its two composite
60
- * rows. Used by the account create/update handlers, which act on one account.
61
- */
62
- export const loadSignatureForAccount = async (
63
- accountSetting: IAccountSettingRepository,
64
- accountConfigId: string,
65
- accountId: string,
66
- ): Promise<AccountSignature> => {
67
- const [plain, html] = await Promise.all([
68
- accountSetting.get(
69
- accountConfigId,
70
- composeSettingName(SIGNATURE_NAMES.plainText, accountId),
71
- ),
72
- accountSetting.get(
73
- accountConfigId,
74
- composeSettingName(SIGNATURE_NAMES.html, accountId),
75
- ),
76
- ]);
77
- const signature: AccountSignature = {};
78
- const plainText = plain ? stringValueOf(plain) : undefined;
79
- const htmlText = html ? stringValueOf(html) : undefined;
80
- if (plainText !== undefined) signature.plainText = plainText;
81
- if (htmlText !== undefined) signature.html = htmlText;
82
- return signature;
83
- };
84
-
85
- /**
86
- * Idempotent upsert of an account's signature fields. Only writes the parts the
87
- * caller supplied — an empty string is a valid stored value (it clears the
88
- * displayed signature without removing the row), matching the prior PATCH
89
- * semantics where setting `""` stored `""`.
90
- */
91
- export const upsertAccountSignature = async (
92
- accountSetting: IAccountSettingRepository,
93
- accountConfigId: string,
94
- accountId: string,
95
- signature: { plainText?: string; html?: string },
96
- ): Promise<void> => {
97
- const writes: Promise<unknown>[] = [];
98
- if (signature.plainText !== undefined) {
99
- writes.push(
100
- accountSetting.upsert({
101
- accountConfigId,
102
- name: composeSettingName(SIGNATURE_NAMES.plainText, accountId),
103
- value: { kind: "String", value: signature.plainText },
104
- }),
105
- );
106
- }
107
- if (signature.html !== undefined) {
108
- writes.push(
109
- accountSetting.upsert({
110
- accountConfigId,
111
- name: composeSettingName(SIGNATURE_NAMES.html, accountId),
112
- value: { kind: "String", value: signature.html },
113
- }),
114
- );
115
- }
116
- await Promise.all(writes);
117
- };