@cosmicdrift/kumiko-samples 0.393.0 → 0.395.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/packages/bundled-features/package.json +15 -5
- package/packages/bundled-features/src/account-security/README.md +1 -1
- package/packages/bundled-features/src/account-security/changes.json +6 -0
- package/packages/bundled-features/src/account-security/feature.ts +9 -1
- package/packages/bundled-features/src/audit/event-classification.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/changes.json +6 -0
- package/packages/bundled-features/src/auth-email-password/web/auth-client.ts +10 -52
- package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +4 -4
- package/packages/bundled-features/src/auth-email-password/web/auth-redirect.ts +3 -12
- package/packages/bundled-features/src/auth-email-password/web/index.ts +2 -0
- package/packages/bundled-features/src/auth-email-password/web/login-failure.ts +54 -0
- package/packages/bundled-features/src/auth-email-password/web/login-url.ts +19 -0
- package/packages/bundled-features/src/auth-email-password/web/static-login.ts +183 -0
- package/packages/bundled-features/src/auth-mfa/web/mfa-client.ts +7 -63
- package/packages/bundled-features/src/billing-foundation/changes.json +30 -0
- package/packages/bundled-features/src/billing-foundation/checkout-core.ts +8 -16
- package/packages/bundled-features/src/billing-foundation/events.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/feature.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/handlers/create-checkout-session.write.ts +9 -2
- package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/index.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/one-off-checkout-gate.ts +44 -0
- package/packages/bundled-features/src/billing-foundation/one-off-customer.ts +17 -0
- package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +1 -0
- package/packages/bundled-features/src/billing-foundation/types.ts +26 -0
- package/packages/bundled-features/src/billing-foundation/web/billing-plans-panel.tsx +1 -0
- package/packages/bundled-features/src/billing-foundation/web/plan-switch-confirm-dialog.tsx +8 -2
- package/packages/bundled-features/src/billing-foundation/web/plan-switch-recurring.ts +23 -0
- package/packages/bundled-features/src/billing-foundation/webhook-handler.ts +1 -0
- package/packages/bundled-features/src/cap-overview/changes.json +6 -0
- package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +3 -3
- package/packages/bundled-features/src/cap-overview/types.ts +2 -0
- package/packages/bundled-features/src/compliance-profiles/web/compliance-profile-catalog.tsx +10 -1
- package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +12 -3
- package/packages/bundled-features/src/files/changes.json +18 -0
- package/packages/bundled-features/src/inbound-mail-foundation/account-health.ts +146 -0
- package/packages/bundled-features/src/inbound-mail-foundation/account-poll-schedule.ts +74 -0
- package/packages/bundled-features/src/inbound-mail-foundation/account-poll.ts +238 -0
- package/packages/bundled-features/src/inbound-mail-foundation/account-record.ts +2 -0
- package/packages/bundled-features/src/inbound-mail-foundation/account-wake.ts +21 -0
- package/packages/bundled-features/src/inbound-mail-foundation/changes.json +143 -0
- package/packages/bundled-features/src/inbound-mail-foundation/connect-callback-verify.ts +65 -0
- package/packages/bundled-features/src/inbound-mail-foundation/connect-route-responses.ts +26 -0
- package/packages/bundled-features/src/inbound-mail-foundation/connect-routes.ts +93 -62
- package/packages/bundled-features/src/inbound-mail-foundation/constants.ts +66 -3
- package/packages/bundled-features/src/inbound-mail-foundation/content-refetch.ts +137 -0
- package/packages/bundled-features/src/inbound-mail-foundation/describe-error.ts +5 -0
- package/packages/bundled-features/src/inbound-mail-foundation/entities.ts +45 -2
- package/packages/bundled-features/src/inbound-mail-foundation/events.ts +69 -2
- package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +133 -118
- package/packages/bundled-features/src/inbound-mail-foundation/file-storage-failure.ts +23 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/account-address.ts +79 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/acknowledge-quarantine.write.ts +55 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/attach-message-content.write.ts +184 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +137 -84
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +65 -45
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message-prepare.ts +177 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message-schema.ts +42 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +79 -160
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +93 -30
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +46 -30
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-quarantine.query.ts +106 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/mark-credential-stored.write.ts +47 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/message-content.query.ts +99 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/message-content.ts +229 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/message-detail.query.ts +34 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/reactivate-account.write.ts +129 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/retry-quarantined-message.write.ts +49 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +48 -9
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/set-trusted-authserv-id.write.ts +66 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/thread-rollup.ts +136 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/trusted-authserv-id.ts +4 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +82 -60
- package/packages/bundled-features/src/inbound-mail-foundation/i18n.ts +80 -2
- package/packages/bundled-features/src/inbound-mail-foundation/inbound-oauth-flow.ts +48 -0
- package/packages/bundled-features/src/inbound-mail-foundation/index.ts +93 -15
- package/packages/bundled-features/src/inbound-mail-foundation/ingest-failures.ts +137 -0
- package/packages/bundled-features/src/inbound-mail-foundation/ingest-payload.ts +46 -0
- package/packages/bundled-features/src/inbound-mail-foundation/mail-roles.ts +132 -0
- package/packages/bundled-features/src/inbound-mail-foundation/normalize-inbound-message.ts +119 -0
- package/packages/bundled-features/src/inbound-mail-foundation/oauth-access-token.ts +26 -8
- package/packages/bundled-features/src/inbound-mail-foundation/outbound-attachment-access.ts +28 -0
- package/packages/bundled-features/src/inbound-mail-foundation/outbound-attachments.ts +111 -0
- package/packages/bundled-features/src/inbound-mail-foundation/outbound-mail.ts +19 -1
- package/packages/bundled-features/src/inbound-mail-foundation/probe.ts +2 -1
- package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +33 -2
- package/packages/bundled-features/src/inbound-mail-foundation/provider-errors.ts +74 -0
- package/packages/bundled-features/src/inbound-mail-foundation/quarantine-entities.ts +56 -0
- package/packages/bundled-features/src/inbound-mail-foundation/quarantine-header-import.ts +63 -0
- package/packages/bundled-features/src/inbound-mail-foundation/quarantine-retry.ts +137 -0
- package/packages/bundled-features/src/inbound-mail-foundation/quarantine-screens.ts +66 -0
- package/packages/bundled-features/src/inbound-mail-foundation/quarantine.ts +72 -0
- package/packages/bundled-features/src/inbound-mail-foundation/retention-job.ts +78 -0
- package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +43 -8
- package/packages/bundled-features/src/inbound-mail-foundation/screens.ts +238 -89
- package/packages/bundled-features/src/inbound-mail-foundation/send-from-account.ts +24 -5
- package/packages/bundled-features/src/inbound-mail-foundation/sender-auth.ts +215 -0
- package/packages/bundled-features/src/inbound-mail-foundation/supervisor-ingest.ts +311 -0
- package/packages/bundled-features/src/inbound-mail-foundation/sync-account-now.ts +61 -0
- package/packages/bundled-features/src/inbound-mail-foundation/sync-cursor-store.ts +79 -0
- package/packages/bundled-features/src/inbound-mail-foundation/syncable-accounts.ts +123 -0
- package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +5 -1
- package/packages/bundled-features/src/inbound-mail-foundation/types.ts +76 -105
- package/packages/bundled-features/src/inbound-mail-foundation/wake-consumer.ts +33 -0
- package/packages/bundled-features/src/inbound-mail-foundation/watch-catch-up.ts +68 -0
- package/packages/bundled-features/src/inbound-mail-foundation/watch-lease.ts +124 -0
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor-types.ts +87 -0
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +143 -433
- package/packages/bundled-features/src/inbound-mail-foundation/web/client-plugin.ts +73 -0
- package/packages/bundled-features/src/inbound-mail-foundation/web/constants.ts +7 -0
- package/packages/bundled-features/src/inbound-mail-foundation/web/index.ts +5 -0
- package/packages/bundled-features/src/inbound-provider-imap/changes.json +36 -0
- package/packages/bundled-features/src/inbound-provider-imap/connect-mailbox.ts +124 -41
- package/packages/bundled-features/src/inbound-provider-imap/connect-my-mailbox.ts +45 -0
- package/packages/bundled-features/src/inbound-provider-imap/constants.ts +2 -1
- package/packages/bundled-features/src/inbound-provider-imap/credential-document.ts +2 -1
- package/packages/bundled-features/src/inbound-provider-imap/download-batch.ts +38 -0
- package/packages/bundled-features/src/inbound-provider-imap/feature.ts +61 -136
- package/packages/bundled-features/src/inbound-provider-imap/fetch-content.ts +84 -0
- package/packages/bundled-features/src/inbound-provider-imap/i18n.ts +9 -0
- package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +35 -84
- package/packages/bundled-features/src/inbound-provider-imap/index.ts +1 -2
- package/packages/bundled-features/src/inbound-provider-imap/mime-content.ts +85 -0
- package/packages/bundled-features/src/inbound-provider-imap/parse-message.ts +149 -0
- package/packages/bundled-features/src/inbound-provider-imap/reconnect-account.ts +4 -6
- package/packages/bundled-features/src/inbound-provider-imap/reconnect-mailbox.ts +75 -45
- package/packages/bundled-features/src/inbound-provider-imap/reconnect-prefill.query.ts +19 -8
- package/packages/bundled-features/src/inbound-provider-imap/screens.ts +203 -109
- package/packages/bundled-features/src/inbound-provider-imap/send-mail.ts +11 -2
- package/packages/bundled-features/src/inbound-provider-imap/sender-auth-headers.ts +36 -0
- package/packages/bundled-features/src/inbound-provider-imap/watch-mailbox.ts +109 -0
- package/packages/bundled-features/src/inbound-provider-inmemory/feature.ts +75 -3
- package/packages/bundled-features/src/inbound-provider-inmemory/index.ts +6 -0
- package/packages/bundled-features/src/jobs/changes.json +18 -0
- package/packages/bundled-features/src/managed-pages/changes.json +7 -0
- package/packages/bundled-features/src/managed-pages/constants.ts +7 -0
- package/packages/bundled-features/src/managed-pages/feature.ts +6 -4
- package/packages/bundled-features/src/managed-pages/handlers/by-slug.query.ts +2 -0
- package/packages/bundled-features/src/managed-pages/handlers/page-body-guard.ts +61 -0
- package/packages/bundled-features/src/managed-pages/handlers/page-body-issues.ts +26 -0
- package/packages/bundled-features/src/managed-pages/handlers/page-crud.ts +17 -10
- package/packages/bundled-features/src/managed-pages/handlers/set.write.ts +15 -4
- package/packages/bundled-features/src/managed-pages/render-page-body.ts +28 -0
- package/packages/bundled-features/src/managed-pages/screens/page-screens.ts +6 -2
- package/packages/bundled-features/src/managed-pages/seeding.ts +13 -2
- package/packages/bundled-features/src/managed-pages/table.ts +6 -2
- package/packages/bundled-features/src/managed-pages/web/client-plugin.tsx +6 -4
- package/packages/bundled-features/src/managed-pages/web/constants.ts +14 -0
- package/packages/bundled-features/src/managed-pages/web/i18n.ts +14 -0
- package/packages/bundled-features/src/managed-pages/web/lossy-markdown.ts +71 -0
- package/packages/bundled-features/src/managed-pages/web/markdown-to-document.ts +27 -0
- package/packages/bundled-features/src/managed-pages/web/page-body-section.tsx +73 -0
- package/packages/bundled-features/src/subscription-paddle/changes.json +15 -0
- package/packages/bundled-features/src/subscription-paddle/checkout-config.query.ts +17 -0
- package/packages/bundled-features/src/subscription-paddle/checkout-methods.ts +107 -0
- package/packages/bundled-features/src/subscription-paddle/checkout-page.ts +154 -0
- package/packages/bundled-features/src/subscription-paddle/client.ts +78 -0
- package/packages/bundled-features/src/subscription-paddle/constants.ts +32 -0
- package/packages/bundled-features/src/subscription-paddle/feature.ts +265 -0
- package/packages/bundled-features/src/subscription-paddle/index.ts +17 -0
- package/packages/bundled-features/src/subscription-paddle/plan-switch-methods.ts +135 -0
- package/packages/bundled-features/src/subscription-paddle/price-methods.ts +148 -0
- package/packages/bundled-features/src/subscription-paddle/runtime.ts +203 -0
- package/packages/bundled-features/src/subscription-paddle/subscription-state.ts +73 -0
- package/packages/bundled-features/src/subscription-paddle/tenant-binding.ts +34 -0
- package/packages/bundled-features/src/subscription-paddle/verify-webhook.ts +61 -0
- package/packages/bundled-features/src/subscription-paddle/webhook-events.ts +172 -0
- package/packages/bundled-features/src/template-resolver/README.md +2 -2
- package/packages/bundled-features/src/template-resolver/changes.json +20 -0
- package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +16 -16
- package/packages/bundled-features/src/template-resolver/feature.ts +14 -1
- package/packages/bundled-features/src/template-resolver/handlers/collection-item.query.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/collection-list.query.ts +6 -2
- package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +24 -6
- package/packages/bundled-features/src/template-resolver/handlers/collection-shared.ts +32 -0
- package/packages/bundled-features/src/template-resolver/handlers/set.write.ts +1 -0
- package/packages/bundled-features/src/template-resolver/handlers/shared.ts +9 -3
- package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +13 -480
- package/packages/bundled-features/src/template-resolver/web/collection-entry-create-form.tsx +105 -0
- package/packages/bundled-features/src/template-resolver/web/collection-lookup.ts +33 -0
- package/packages/bundled-features/src/template-resolver/web/constants.ts +3 -0
- package/packages/bundled-features/src/template-resolver/web/content-field.tsx +61 -0
- package/packages/bundled-features/src/template-resolver/web/content-tree-provider.ts +256 -0
- package/packages/bundled-features/src/template-resolver/web/document-reference-name.ts +12 -0
- package/packages/bundled-features/src/template-resolver/web/document-schema.ts +3 -11
- package/packages/bundled-features/src/template-resolver/web/i18n.ts +9 -2
- package/packages/bundled-features/src/template-resolver/web/legacy-variable-text.ts +30 -0
- package/packages/bundled-features/src/template-resolver/web/tenant-override.ts +7 -0
- package/packages/bundled-features/src/template-resolver/web/text-block-editor.tsx +92 -0
- package/packages/bundled-features/src/template-resolver/web/use-collection-editor-context.ts +42 -0
- package/packages/bundled-features/src/template-resolver/web/use-collection-entry-create.ts +92 -0
- package/packages/bundled-features/src/template-resolver/web/use-content-write.ts +81 -0
- package/packages/bundled-features/src/template-resolver/web/use-text-block-editor.ts +127 -0
- package/packages/bundled-features/src/tier-engine/changes.json +6 -0
- package/packages/bundled-features/src/tier-engine/feature.ts +9 -1
- package/packages/bundled-features/src/user-data-rights-defaults/hooks/file-ref.userdata-hook.ts +2 -1
- package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-constants.ts +4 -0
- package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +11 -5
- package/samples/apps/use-all-bundled/e2e-billing-gallery/server.ts +2 -2
- package/samples/apps/use-all-bundled/feature-manifest.json +89 -40
- package/samples/apps/use-all-bundled/kumiko/migrations/0056_inbound-mail-quarantine-retry.rebuild.json +4 -0
- package/samples/apps/use-all-bundled/package.json +1 -0
- package/samples/apps/use-all-bundled/playwright.billing-docs.config.ts +15 -0
- package/samples/apps/use-all-bundled/src/app/cap-overview-caps.ts +16 -2
- package/samples/apps/use-all-bundled/src/app/cap-overview-labels-feature.ts +2 -1
- package/samples/apps/use-all-bundled/src/app/client.tsx +8 -1
- package/samples/apps/use-all-bundled/src/app/screens-feature.ts +14 -2
- package/samples/apps/use-all-bundled/src/app/seed.ts +17 -2
- package/samples/apps/use-all-bundled/src/run-config.ts +19 -11
- package/samples/recipes/cross-feature-events/README.md +10 -0
- package/samples/recipes/cross-feature-events/src/feature.ts +14 -0
- package/samples/recipes/dashboard-stats/README.md +8 -0
- package/samples/recipes/dashboard-stats/src/feature.ts +91 -1
- package/samples/recipes/env-composition/README.md +2 -2
- package/samples/recipes/env-composition/src/env.ts +2 -2
- package/samples/recipes/inbound-mail-triage/README.md +8 -0
- package/samples/recipes/pdf-audit-export/src/feature.ts +71 -48
- package/packages/bundled-features/src/subscription-dodo/changes.json +0 -14
- package/packages/bundled-features/src/subscription-dodo/client.ts +0 -45
- package/packages/bundled-features/src/subscription-dodo/constants.ts +0 -24
- package/packages/bundled-features/src/subscription-dodo/feature.ts +0 -207
- package/packages/bundled-features/src/subscription-dodo/index.ts +0 -16
- package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +0 -345
- package/packages/bundled-features/src/subscription-dodo/runtime.ts +0 -126
- package/packages/bundled-features/src/subscription-dodo/verify-webhook.ts +0 -262
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// attach-message-content — stores the content of a message that was imported as snippet only, after
|
|
2
|
+
// the supervisor re-downloaded it. Appends one content-fetched event on the existing stream; no
|
|
3
|
+
// thread rollup, no new message row. A header-only import from quarantine takes its content the
|
|
4
|
+
// same way. Nested dispatches skip the pre-transaction upload and store in their own transaction.
|
|
5
|
+
|
|
6
|
+
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
7
|
+
import { ConflictError, failNotFound, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
8
|
+
import type {
|
|
9
|
+
BeforeTransactionContext,
|
|
10
|
+
BeforeTransactionResult,
|
|
11
|
+
WriteResult,
|
|
12
|
+
} from "@cosmicdrift/kumiko-types/handlers";
|
|
13
|
+
import * as z from "zod";
|
|
14
|
+
import { inboundMessageAggregateId } from "../aggregate-id.ts";
|
|
15
|
+
import {
|
|
16
|
+
INBOUND_MESSAGE_AGGREGATE_TYPE,
|
|
17
|
+
INBOUND_MESSAGE_CONTENT_FETCHED_EVENT_QN,
|
|
18
|
+
INBOUND_MESSAGE_RECEIVED_EVENT_QN,
|
|
19
|
+
type InboundContentState,
|
|
20
|
+
type InboundMessageContentFetchedEventPayload,
|
|
21
|
+
} from "../events.ts";
|
|
22
|
+
import { inboundMessagesProjectionTable } from "../projection.ts";
|
|
23
|
+
import {
|
|
24
|
+
finalizePreparedContent,
|
|
25
|
+
type PreparedInboundContent,
|
|
26
|
+
prepareInboundContent,
|
|
27
|
+
} from "./ingest-message-prepare.ts";
|
|
28
|
+
import {
|
|
29
|
+
contentStateOf,
|
|
30
|
+
encryptMessageBodyText,
|
|
31
|
+
inboundContentPayloadShape,
|
|
32
|
+
MESSAGE_LOCK_NAMESPACE,
|
|
33
|
+
storeInboundMessageContent,
|
|
34
|
+
} from "./message-content.ts";
|
|
35
|
+
|
|
36
|
+
export const attachMessageContentSchema = z.object({
|
|
37
|
+
accountId: z.uuid(),
|
|
38
|
+
providerMessageId: z.string().min(1).max(500),
|
|
39
|
+
/** "unavailable" is terminal: the mail is gone from the server or cannot be parsed. */
|
|
40
|
+
outcome: z.enum(["fetched", "unavailable"]),
|
|
41
|
+
...inboundContentPayloadShape,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
type AttachMessageContentPayload = z.infer<typeof attachMessageContentSchema>;
|
|
45
|
+
|
|
46
|
+
const COMPLETE: InboundContentState = "complete";
|
|
47
|
+
|
|
48
|
+
function hasContentAlready(events: readonly { type: string; payload: unknown }[]): boolean {
|
|
49
|
+
return events.some((e) => {
|
|
50
|
+
if (e.type === INBOUND_MESSAGE_CONTENT_FETCHED_EVENT_QN) return true;
|
|
51
|
+
const state = z.object({ contentState: z.string() }).safeParse(e.payload);
|
|
52
|
+
return state.success && state.data.contentState === COMPLETE;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const receivedOwnerSchema = z.object({ ownerUserId: z.string().nullable() });
|
|
57
|
+
|
|
58
|
+
function ownerUserIdOfStream(events: readonly { type: string; payload: unknown }[]): string | null {
|
|
59
|
+
const received = events.find((e) => e.type === INBOUND_MESSAGE_RECEIVED_EVENT_QN);
|
|
60
|
+
const parsed = receivedOwnerSchema.safeParse(received?.payload);
|
|
61
|
+
return parsed.success ? parsed.data.ownerUserId : null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Read before the transaction, so it can only skip work: a row that already shows attached content
|
|
65
|
+
// spares parallel refetches the upload. The event check under the message lock stays authoritative.
|
|
66
|
+
async function showsAttachedContent(
|
|
67
|
+
db: BeforeTransactionContext["db"],
|
|
68
|
+
messageAggId: string,
|
|
69
|
+
): Promise<boolean> {
|
|
70
|
+
const [row] = await db.selectMany(
|
|
71
|
+
inboundMessagesProjectionTable,
|
|
72
|
+
{ id: messageAggId },
|
|
73
|
+
{ fields: ["contentState", "contentUnavailableReason"] },
|
|
74
|
+
);
|
|
75
|
+
if (row === undefined) return false;
|
|
76
|
+
if (row["contentState"] === COMPLETE) return true;
|
|
77
|
+
// "quarantined" is the header-only import still waiting for its content.
|
|
78
|
+
return row["contentState"] === "unavailable" && row["contentUnavailableReason"] !== "quarantined";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function contentInputOf(payload: AttachMessageContentPayload, messageAggId: string) {
|
|
82
|
+
return {
|
|
83
|
+
accountId: payload.accountId,
|
|
84
|
+
messageAggId,
|
|
85
|
+
bodyRef: "",
|
|
86
|
+
rawMime: payload.rawMime,
|
|
87
|
+
bodyHtml: payload.bodyHtml,
|
|
88
|
+
attachments: payload.attachments,
|
|
89
|
+
skippedAttachments: payload.skippedAttachments,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function contentEventPayloadOf(
|
|
94
|
+
payload: AttachMessageContentPayload,
|
|
95
|
+
content: Awaited<ReturnType<typeof storeInboundMessageContent>> | undefined,
|
|
96
|
+
tenantId: Parameters<typeof encryptMessageBodyText>[0],
|
|
97
|
+
messageAggId: string,
|
|
98
|
+
ownerUserId: string | null,
|
|
99
|
+
): Promise<InboundMessageContentFetchedEventPayload> {
|
|
100
|
+
const fetched = payload.outcome === "fetched";
|
|
101
|
+
return {
|
|
102
|
+
accountId: payload.accountId,
|
|
103
|
+
ownerUserId,
|
|
104
|
+
bodyRef: content?.bodyRef ?? "",
|
|
105
|
+
bodyText: await encryptMessageBodyText(tenantId, messageAggId, fetched ? payload.bodyText : ""),
|
|
106
|
+
bodyTextTruncated: fetched && payload.bodyTextTruncated,
|
|
107
|
+
attachmentFileIds: content?.attachmentFileIds ?? [],
|
|
108
|
+
skippedAttachments: content?.skippedAttachments ?? [],
|
|
109
|
+
contentState: content
|
|
110
|
+
? contentStateOf({
|
|
111
|
+
bodyText: payload.bodyText,
|
|
112
|
+
hasOriginal: payload.rawMime !== undefined || payload.bodyHtml !== undefined,
|
|
113
|
+
contentStorage: content.contentStorage,
|
|
114
|
+
})
|
|
115
|
+
: "unavailable",
|
|
116
|
+
contentUnavailableReason: !content
|
|
117
|
+
? "gone_from_server"
|
|
118
|
+
: content.contentStorage === "unavailable"
|
|
119
|
+
? "no_file_storage"
|
|
120
|
+
: null,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const attachMessageContentHandler = defineWriteHandler({
|
|
125
|
+
name: "attach-message-content",
|
|
126
|
+
schema: attachMessageContentSchema,
|
|
127
|
+
access: { roles: ["SystemAdmin"] },
|
|
128
|
+
agent: { expose: false },
|
|
129
|
+
description:
|
|
130
|
+
"Stores the full text, original and attachments of an already imported snippet-only or quarantined mail, or marks its content as unavailable when the provider no longer has it; called by the sync supervisor, a second call for the same mail is a no-op.",
|
|
131
|
+
beforeTransactionWhenNested: "skip",
|
|
132
|
+
beforeTransaction: async (
|
|
133
|
+
event,
|
|
134
|
+
ctx,
|
|
135
|
+
): Promise<BeforeTransactionResult<PreparedInboundContent | undefined>> => {
|
|
136
|
+
const { payload } = event;
|
|
137
|
+
if (payload.outcome !== "fetched") return { isSuccess: true, data: undefined };
|
|
138
|
+
const messageAggId = inboundMessageAggregateId(payload.accountId, payload.providerMessageId);
|
|
139
|
+
if (await showsAttachedContent(ctx.db, messageAggId))
|
|
140
|
+
return { isSuccess: true, data: undefined };
|
|
141
|
+
return prepareInboundContent(ctx, contentInputOf(payload, messageAggId));
|
|
142
|
+
},
|
|
143
|
+
handler: async (event, ctx): Promise<WriteResult<{ readonly duplicate: boolean }>> => {
|
|
144
|
+
const { payload } = event;
|
|
145
|
+
const prepared: PreparedInboundContent | undefined = event.prepared;
|
|
146
|
+
const tenantId = event.user.tenantId;
|
|
147
|
+
const messageAggId = inboundMessageAggregateId(payload.accountId, payload.providerMessageId);
|
|
148
|
+
|
|
149
|
+
await ctx.db.advisoryXactLock(MESSAGE_LOCK_NAMESPACE, messageAggId);
|
|
150
|
+
const events = await ctx.events.load(messageAggId);
|
|
151
|
+
if (events.length === 0) return failNotFound("inbound-message", messageAggId);
|
|
152
|
+
if (hasContentAlready(events)) {
|
|
153
|
+
return { isSuccess: true as const, data: { duplicate: true as const } };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const content =
|
|
157
|
+
payload.outcome !== "fetched"
|
|
158
|
+
? undefined
|
|
159
|
+
: prepared
|
|
160
|
+
? await finalizePreparedContent(ctx, prepared)
|
|
161
|
+
: await storeInboundMessageContent(ctx, contentInputOf(payload, messageAggId));
|
|
162
|
+
const eventPayload = await contentEventPayloadOf(
|
|
163
|
+
payload,
|
|
164
|
+
content,
|
|
165
|
+
tenantId,
|
|
166
|
+
messageAggId,
|
|
167
|
+
ownerUserIdOfStream(events),
|
|
168
|
+
);
|
|
169
|
+
const appended = await ctx.events.tryAppend({
|
|
170
|
+
aggregateId: messageAggId,
|
|
171
|
+
aggregateType: INBOUND_MESSAGE_AGGREGATE_TYPE,
|
|
172
|
+
type: INBOUND_MESSAGE_CONTENT_FETCHED_EVENT_QN,
|
|
173
|
+
payload: eventPayload,
|
|
174
|
+
headers: { providerMessageId: payload.providerMessageId, outcome: payload.outcome },
|
|
175
|
+
});
|
|
176
|
+
// Losing the version race must roll back the stored attachments instead of reporting success.
|
|
177
|
+
if (!appended.ok) {
|
|
178
|
+
return writeFailure(
|
|
179
|
+
new ConflictError({ message: "inbound message content changed concurrently" }),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
return { isSuccess: true as const, data: { duplicate: false as const } };
|
|
183
|
+
},
|
|
184
|
+
});
|
package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// connect-account — A tenant admin connects a mailbox. Creates the
|
|
1
|
+
// connect-account — A tenant admin connects a mailbox (a member's personal one goes through the provider form, which dispatches here as the system user). Creates the
|
|
2
2
|
// mail-account stream (aggregateId = random uuid = accountId =
|
|
3
3
|
// secret-slot key for the IMAP password/OAuth refresh token).
|
|
4
4
|
//
|
|
@@ -11,15 +11,28 @@
|
|
|
11
11
|
// and switches to auth_error via update-account on failure—the connect
|
|
12
12
|
// stays fast and offline-capable (the test result arrives asynchronously
|
|
13
13
|
// as a status transition).
|
|
14
|
+
// - No second account for one address: a live or disconnected address is
|
|
15
|
+
// refused (409 reasons in InboundMailConnectErrors); only a disconnected or
|
|
16
|
+
// sign-in-failed OAuth mailbox is reopened in place, keeping its account id and cursor.
|
|
17
|
+
// The reopen returns the existing id and changes nothing yet; the callback re-arms it.
|
|
14
18
|
|
|
15
19
|
import {
|
|
16
20
|
configuredPiiSubjectKms,
|
|
17
21
|
encryptPiiFieldValues,
|
|
18
22
|
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
19
23
|
import type { WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
20
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
failUnprocessable,
|
|
26
|
+
UnprocessableError,
|
|
27
|
+
writeFailure,
|
|
28
|
+
} from "@cosmicdrift/kumiko-framework/errors";
|
|
21
29
|
import * as z from "zod";
|
|
22
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
InboundMailAccountStatuses,
|
|
32
|
+
InboundMailAuthMethods,
|
|
33
|
+
InboundMailConnectErrors,
|
|
34
|
+
InboundMailFoundationHandlers,
|
|
35
|
+
} from "../constants.ts";
|
|
23
36
|
import { MAIL_ACCOUNT_PII_FIELDS, mailAccountEntity } from "../entities.ts";
|
|
24
37
|
import {
|
|
25
38
|
MAIL_ACCOUNT_AGGREGATE_TYPE,
|
|
@@ -27,91 +40,131 @@ import {
|
|
|
27
40
|
type MailAccountEventHeaders,
|
|
28
41
|
type MailAccountEventPayload,
|
|
29
42
|
} from "../events.ts";
|
|
43
|
+
import { accessRolesOf, type InboundMailRoles } from "../mail-roles.ts";
|
|
44
|
+
import { connectConflictOf, findMailboxByAddress } from "./account-address.ts";
|
|
30
45
|
|
|
31
|
-
export const connectAccountSchema = z
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
export const connectAccountSchema = z
|
|
47
|
+
.object({
|
|
48
|
+
/** Provider key registered with the extension ("imap",
|
|
49
|
+
* "m365-graph", "gmail-rest"). */
|
|
50
|
+
provider: z.string().min(1).max(50),
|
|
51
|
+
authMethod: z.enum([
|
|
52
|
+
InboundMailAuthMethods.password,
|
|
53
|
+
InboundMailAuthMethods.xoauth2,
|
|
54
|
+
InboundMailAuthMethods.oauth,
|
|
55
|
+
]),
|
|
56
|
+
displayName: z.string().trim().max(200),
|
|
57
|
+
/** Mailbox address in plaintext; it is encrypted here. */
|
|
58
|
+
address: z.string().min(1).max(320),
|
|
59
|
+
/** "shared" = shared across the tenant (info@) · "user" = the caller's
|
|
60
|
+
* personal mailbox (ownerUserId is taken from the session, never from
|
|
61
|
+
* the payload—preventing claims on another user's mailbox). */
|
|
62
|
+
scope: z.enum(["shared", "user"]),
|
|
63
|
+
/** Only for the programmatic OAuth callback path (SystemAdmin): there,
|
|
64
|
+
* the dispatcher user is a SystemUser and the actual owner is in the
|
|
65
|
+
* HMAC-verified state. Tenant callers must not set this field (the
|
|
66
|
+
* handler prevents claims on another user's mailbox). */
|
|
67
|
+
ownerUserIdOverride: z.string().max(36).nullable().optional(),
|
|
68
|
+
})
|
|
69
|
+
// trustedAuthservId is deliberately not accepted here: it decides which Authentication-Results header
|
|
70
|
+
// is believed, so it only changes through set-trusted-authserv-id.
|
|
71
|
+
.strict();
|
|
53
72
|
type ConnectAccountPayload = z.infer<typeof connectAccountSchema>;
|
|
54
73
|
|
|
55
|
-
export
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
74
|
+
export function createConnectAccountHandler({ adminRoles }: InboundMailRoles): WriteHandlerDef {
|
|
75
|
+
return {
|
|
76
|
+
name: "connect-account",
|
|
77
|
+
schema: connectAccountSchema,
|
|
78
|
+
access: { roles: accessRolesOf(InboundMailFoundationHandlers.connectAccount, adminRoles) },
|
|
79
|
+
// Connects a mailbox, like disconnect-account (risk high), which the model must not trigger on its own.
|
|
80
|
+
agent: { expose: false },
|
|
81
|
+
description:
|
|
82
|
+
"Registers a mailbox for the tenant (provider, auth method, display name, encrypted address, shared or personal) and returns the new accountId that also keys its credential slot; use it to start a mail connection, credentials and the first connection test happen outside this call.",
|
|
83
|
+
handler: async (event, ctx) => {
|
|
84
|
+
// @cast-boundary engine-payload — dispatcher-zod-validated payload
|
|
85
|
+
const payload = event.payload as ConnectAccountPayload;
|
|
86
|
+
const tenantId = event.user.tenantId;
|
|
87
|
+
const accountId = crypto.randomUUID();
|
|
88
|
+
|
|
89
|
+
if (payload.ownerUserIdOverride !== undefined && !event.user.roles.includes("SystemAdmin")) {
|
|
90
|
+
return failUnprocessable("owner_override_requires_system_admin");
|
|
91
|
+
}
|
|
92
|
+
const ownerUserId =
|
|
93
|
+
payload.ownerUserIdOverride !== undefined
|
|
94
|
+
? payload.ownerUserIdOverride
|
|
95
|
+
: payload.scope === "user"
|
|
96
|
+
? event.user.id
|
|
97
|
+
: null;
|
|
66
98
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
99
|
+
const existing = await findMailboxByAddress(ctx, tenantId, payload.address);
|
|
100
|
+
if (existing) {
|
|
101
|
+
const matched = connectConflictOf(existing, {
|
|
102
|
+
authMethod: payload.authMethod,
|
|
103
|
+
provider: payload.provider,
|
|
104
|
+
ownerUserId,
|
|
105
|
+
});
|
|
106
|
+
// Only the OAuth callback (it passes ownerUserIdOverride) reopens: it stores the fresh token and
|
|
107
|
+
// only then switches the account on via reactivate-account. Everyone else gets the plain conflict.
|
|
108
|
+
const conflict =
|
|
109
|
+
matched === "reopen_in_place" && payload.ownerUserIdOverride === undefined
|
|
110
|
+
? existing.status === InboundMailAccountStatuses.disconnected
|
|
111
|
+
? "address_disconnected"
|
|
112
|
+
: "already_connected"
|
|
113
|
+
: matched;
|
|
114
|
+
if (conflict === "reopen_in_place") {
|
|
115
|
+
return {
|
|
116
|
+
isSuccess: true as const,
|
|
117
|
+
data: { accountId: existing.id, reconnected: true as const },
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const { reason, i18nKey } =
|
|
121
|
+
conflict === "address_disconnected"
|
|
122
|
+
? InboundMailConnectErrors.addressDisconnected
|
|
123
|
+
: InboundMailConnectErrors.addressAlreadyConnected;
|
|
124
|
+
return writeFailure(new UnprocessableError(reason, { i18nKey }));
|
|
125
|
+
}
|
|
76
126
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
127
|
+
// PII-encrypt the address before the append. This is the only write path to
|
|
128
|
+
// the stream, so it covers both the event log and the projection.
|
|
129
|
+
const piiKms = configuredPiiSubjectKms();
|
|
130
|
+
const plainPii = { tenantId, address: payload.address };
|
|
131
|
+
const encryptedFields = piiKms
|
|
132
|
+
? await encryptPiiFieldValues(
|
|
133
|
+
plainPii,
|
|
134
|
+
mailAccountEntity,
|
|
135
|
+
MAIL_ACCOUNT_PII_FIELDS,
|
|
136
|
+
piiKms,
|
|
137
|
+
{ requestId: `inbound-mail-foundation:connect-account:${accountId}`, tenantId },
|
|
138
|
+
{ tenantId, entityName: MAIL_ACCOUNT_AGGREGATE_TYPE },
|
|
139
|
+
)
|
|
140
|
+
: plainPii;
|
|
91
141
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
142
|
+
const eventPayload: MailAccountEventPayload = {
|
|
143
|
+
provider: payload.provider,
|
|
144
|
+
authMethod: payload.authMethod,
|
|
145
|
+
ownerUserId,
|
|
146
|
+
displayName: payload.displayName,
|
|
147
|
+
address: encryptedFields.address,
|
|
148
|
+
// The account is active upon connection; the supervisor picks it up on
|
|
149
|
+
// the next tick and downgrades it when credentials fail.
|
|
150
|
+
status: InboundMailAccountStatuses.active,
|
|
151
|
+
watchState: "idle",
|
|
152
|
+
lastError: null,
|
|
153
|
+
trustedAuthservId: null,
|
|
154
|
+
};
|
|
155
|
+
const headers: MailAccountEventHeaders = {
|
|
156
|
+
providerName: payload.provider,
|
|
157
|
+
reason: "connect_flow",
|
|
158
|
+
};
|
|
159
|
+
await ctx.events.unsafeAppend({
|
|
160
|
+
aggregateId: accountId,
|
|
161
|
+
aggregateType: MAIL_ACCOUNT_AGGREGATE_TYPE,
|
|
162
|
+
type: MAIL_ACCOUNT_CONNECTED_EVENT_QN,
|
|
163
|
+
payload: eventPayload,
|
|
164
|
+
headers,
|
|
165
|
+
});
|
|
114
166
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
};
|
|
167
|
+
return { isSuccess: true as const, data: { accountId, reconnected: false as const } };
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts
CHANGED
|
@@ -1,72 +1,92 @@
|
|
|
1
1
|
// disconnect-account — A TenantAdmin disconnects a mailbox. The stream is retained
|
|
2
2
|
// for audit history, the status becomes disconnected, and the watch supervisor
|
|
3
|
-
// skips the account starting with the next tick.
|
|
4
|
-
// refresh token in the accountId slot) is
|
|
5
|
-
//
|
|
3
|
+
// skips the account starting with the next tick. The credential secret (IMAP password/
|
|
4
|
+
// refresh token in the accountId slot) is deleted in the same transaction, so a
|
|
5
|
+
// disconnected mailbox cannot be re-armed with a stale credential.
|
|
6
6
|
//
|
|
7
7
|
// Idempotent: disconnecting an already disconnected account is a success no-op
|
|
8
8
|
// with no second event, making double-clicks and retries safe.
|
|
9
9
|
|
|
10
|
+
import { requireSecretsContext } from "@cosmicdrift/kumiko-bundled-features/secrets";
|
|
10
11
|
import type { WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
11
12
|
import { failNotFound } from "@cosmicdrift/kumiko-framework/errors";
|
|
12
13
|
import * as z from "zod";
|
|
13
|
-
import { InboundMailAccountStatuses } from "../constants.ts";
|
|
14
|
+
import { InboundMailAccountStatuses, inboundCredentialSecretKey } from "../constants.ts";
|
|
14
15
|
import {
|
|
15
16
|
MAIL_ACCOUNT_AGGREGATE_TYPE,
|
|
16
17
|
MAIL_ACCOUNT_DISCONNECTED_EVENT_QN,
|
|
17
18
|
type MailAccountEventHeaders,
|
|
18
19
|
type MailAccountEventPayload,
|
|
19
20
|
} from "../events.ts";
|
|
21
|
+
import type { InboundMailRoles } from "../mail-roles.ts";
|
|
20
22
|
import { loadCurrentMailAccountPayload } from "./account-state.ts";
|
|
21
23
|
|
|
22
24
|
export const disconnectAccountSchema = z.object({
|
|
23
25
|
accountId: z.uuid(),
|
|
24
26
|
reason: z.string().min(1).max(100).default("tenant_admin"),
|
|
25
27
|
});
|
|
28
|
+
const CREDENTIAL_DELETE_REASON =
|
|
29
|
+
"the credential secret is deleted on the request transaction so a failed disconnect leaves the secret in place and the account status stays consistent with it";
|
|
30
|
+
|
|
26
31
|
type DisconnectAccountPayload = z.infer<typeof disconnectAccountSchema>;
|
|
27
32
|
|
|
28
|
-
export
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
export function createDisconnectAccountHandler({ adminRoles }: InboundMailRoles): WriteHandlerDef {
|
|
34
|
+
return {
|
|
35
|
+
name: "disconnect-account",
|
|
36
|
+
schema: disconnectAccountSchema,
|
|
37
|
+
access: { roles: [...adminRoles] },
|
|
38
|
+
description:
|
|
39
|
+
"Puts a connected mailbox into the final disconnected state so the watch supervisor stops fetching it, keeping the stream for audit and requiring a fresh connect to resume; use it to stop mail ingestion for one mailbox.",
|
|
40
|
+
agent: { risk: "high" },
|
|
41
|
+
escapeHatch: { grants: ["unsafeRaw"], reason: CREDENTIAL_DELETE_REASON },
|
|
42
|
+
handler: async (event, ctx) => {
|
|
43
|
+
// @cast-boundary engine-payload — dispatcher-zod-validated payload
|
|
44
|
+
const payload = event.payload as DisconnectAccountPayload;
|
|
38
45
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
const current = await loadCurrentMailAccountPayload(ctx, payload.accountId);
|
|
47
|
+
if (!current) {
|
|
48
|
+
return failNotFound("mail-account", payload.accountId);
|
|
49
|
+
}
|
|
50
|
+
if (current.status === InboundMailAccountStatuses.disconnected) {
|
|
51
|
+
return {
|
|
52
|
+
isSuccess: true as const,
|
|
53
|
+
data: { accountId: payload.accountId, alreadyDisconnected: true as const },
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const eventPayload: MailAccountEventPayload = {
|
|
58
|
+
...current,
|
|
59
|
+
status: InboundMailAccountStatuses.disconnected,
|
|
60
|
+
watchState: "idle",
|
|
47
61
|
};
|
|
48
|
-
|
|
62
|
+
const headers: MailAccountEventHeaders = {
|
|
63
|
+
providerName: current.provider,
|
|
64
|
+
reason: payload.reason,
|
|
65
|
+
};
|
|
66
|
+
await ctx.events.unsafeAppend({
|
|
67
|
+
aggregateId: payload.accountId,
|
|
68
|
+
aggregateType: MAIL_ACCOUNT_AGGREGATE_TYPE,
|
|
69
|
+
type: MAIL_ACCOUNT_DISCONNECTED_EVENT_QN,
|
|
70
|
+
payload: eventPayload,
|
|
71
|
+
headers,
|
|
72
|
+
});
|
|
49
73
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
aggregateType: MAIL_ACCOUNT_AGGREGATE_TYPE,
|
|
62
|
-
type: MAIL_ACCOUNT_DISCONNECTED_EVENT_QN,
|
|
63
|
-
payload: eventPayload,
|
|
64
|
-
headers,
|
|
65
|
-
});
|
|
74
|
+
// Tests and apps without a secrets store have no credential to remove.
|
|
75
|
+
if (ctx.services.secrets) {
|
|
76
|
+
await requireSecretsContext(
|
|
77
|
+
{ secrets: ctx.services.secrets, user: ctx.user },
|
|
78
|
+
"disconnect-account",
|
|
79
|
+
)
|
|
80
|
+
.withRunner(ctx.db.unsafeRaw())
|
|
81
|
+
.delete(event.user.tenantId, inboundCredentialSecretKey(payload.accountId), {
|
|
82
|
+
deletedBy: event.user.id,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
66
85
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
};
|
|
86
|
+
return {
|
|
87
|
+
isSuccess: true as const,
|
|
88
|
+
data: { accountId: payload.accountId, alreadyDisconnected: false as const },
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|