@cosmicdrift/kumiko-samples 0.362.0 → 0.363.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 +17 -6
- package/packages/bundled-features/src/admin-shell/changes.json +6 -0
- package/packages/bundled-features/src/admin-shell/feature.ts +8 -1
- package/packages/bundled-features/src/agent-tools/agent-manifest.ts +3 -4
- package/packages/bundled-features/src/agent-tools/filter-ops.ts +1 -3
- package/packages/bundled-features/src/agent-tools/tool-catalog.ts +1 -3
- package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +2 -1
- package/packages/bundled-features/src/auth-email-password/changes.json +14 -0
- package/packages/bundled-features/src/auth-email-password/email-templates.ts +12 -138
- package/packages/bundled-features/src/auth-email-password/feature.ts +30 -9
- package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -5
- package/packages/bundled-features/src/auth-email-password/handlers/request-account-unlock.write.ts +2 -5
- package/packages/bundled-features/src/auth-email-password/handlers/request-email-verification.write.ts +2 -5
- package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts +2 -5
- package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +3 -4
- package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +3 -12
- package/packages/bundled-features/src/auth-email-password/index.ts +2 -17
- package/packages/bundled-features/src/auth-email-password/magic-link-mail.ts +30 -22
- package/packages/bundled-features/src/auth-email-password/mail-notifications.ts +158 -0
- package/packages/bundled-features/src/auth-email-password/signup-activation.ts +2 -3
- package/packages/bundled-features/src/auth-foundation/types.ts +8 -18
- package/packages/bundled-features/src/auth-mfa/feature.ts +8 -10
- package/packages/bundled-features/src/auth-mfa/hmac-token-codec.ts +4 -4
- package/packages/bundled-features/src/auth-mfa/mfa-challenge-token.ts +3 -3
- package/packages/bundled-features/src/auth-mfa/mfa-preauth-setup-token.ts +3 -3
- package/packages/bundled-features/src/auth-mfa/mfa-setup-token.ts +8 -8
- package/packages/bundled-features/src/billing-foundation/changes.json +7 -0
- package/packages/bundled-features/src/billing-foundation/constants.ts +5 -4
- package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +38 -44
- package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +3 -3
- package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts +115 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts +30 -30
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-notify.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-pages.ts +1 -4
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/handlers/create-portal-session.write.ts +4 -9
- package/packages/bundled-features/src/billing-foundation/handlers/list-subscriptions.query.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +3 -6
- package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +2 -1
- package/packages/bundled-features/src/cap-overview/web/cap-usage-cell.tsx +6 -4
- package/packages/bundled-features/src/channel-email/changes.json +19 -0
- package/packages/bundled-features/src/channel-email/email-channel.ts +122 -29
- package/packages/bundled-features/src/channel-email/feature.ts +6 -3
- package/packages/bundled-features/src/channel-email/index.ts +7 -1
- package/packages/bundled-features/src/channel-email/pii-guard.ts +13 -0
- package/packages/bundled-features/src/channel-email/smtp-transport.ts +15 -1
- package/packages/bundled-features/src/channel-email/types.ts +23 -0
- package/packages/bundled-features/src/channel-in-app/handlers/mark-read.write.ts +2 -2
- package/packages/bundled-features/src/config/handlers/set.write.ts +1 -5
- package/packages/bundled-features/src/custom-fields/db/queries/user-data-rights.ts +2 -2
- package/packages/bundled-features/src/custom-fields/lib/value-schema.ts +10 -11
- package/packages/bundled-features/src/custom-fields/wire-user-data-rights.ts +7 -6
- package/packages/bundled-features/src/data-retention/changes.json +6 -0
- package/packages/bundled-features/src/data-retention/index.ts +1 -0
- package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +8 -6
- package/packages/bundled-features/src/delivery/attachment-ref.ts +84 -0
- package/packages/bundled-features/src/delivery/changes.json +48 -0
- package/packages/bundled-features/src/delivery/channel-context.ts +129 -4
- package/packages/bundled-features/src/delivery/constants.ts +2 -0
- package/packages/bundled-features/src/delivery/delivery-service.ts +104 -26
- package/packages/bundled-features/src/delivery/feature.ts +1 -1
- package/packages/bundled-features/src/delivery/index.ts +17 -2
- package/packages/bundled-features/src/delivery/jobs.ts +48 -5
- package/packages/bundled-features/src/delivery/notify-factory.ts +14 -9
- package/packages/bundled-features/src/delivery/testing.ts +9 -1
- package/packages/bundled-features/src/delivery/types.ts +28 -18
- package/packages/bundled-features/src/delivery/unsubscribe.ts +5 -2
- package/packages/bundled-features/src/derivatives-sharp/render.ts +1 -22
- package/packages/bundled-features/src/document-ingest-foundation/pages.ts +6 -3
- package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +4 -4
- package/packages/bundled-features/src/file-foundation/index.ts +7 -0
- package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +86 -0
- package/packages/bundled-features/src/folders/web/folder-manager.tsx +5 -2
- package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +11 -11
- package/packages/bundled-features/src/form-draft/db/queries/draft-count.ts +2 -2
- package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +2 -2
- package/packages/bundled-features/src/form-draft/release-file-refs.ts +3 -2
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +6 -6
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +2 -2
- package/packages/bundled-features/src/inbound-mail-foundation/oauth-state.ts +7 -7
- package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +2 -6
- package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +5 -13
- package/packages/bundled-features/src/inbound-mail-foundation/types.ts +4 -6
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +40 -25
- package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +4 -3
- package/packages/bundled-features/src/jobs/handlers/catalog.query.ts +1 -1
- package/packages/bundled-features/src/ledger/recurring.ts +1 -3
- package/packages/bundled-features/src/managed-pages/branding.ts +33 -9
- package/packages/bundled-features/src/managed-pages/changes.json +6 -0
- package/packages/bundled-features/src/managed-pages/feature.ts +1 -0
- package/packages/bundled-features/src/managed-pages/i18n.ts +1 -0
- package/packages/bundled-features/src/managed-pages/screens/branding-screen.ts +8 -0
- package/packages/bundled-features/src/page-render/branding.ts +3 -0
- package/packages/bundled-features/src/page-render/markdown.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/feature.ts +2 -4
- package/packages/bundled-features/src/personal-access-tokens/handlers/list.query.ts +2 -1
- package/packages/bundled-features/src/personal-access-tokens/index.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/revoke-for-user.ts +2 -1
- package/packages/bundled-features/src/renderer-foundation/README.md +11 -10
- package/packages/bundled-features/src/renderer-foundation/api.ts +14 -14
- package/packages/bundled-features/src/renderer-foundation/changes.json +39 -0
- package/packages/bundled-features/src/renderer-foundation/constants.ts +6 -8
- package/packages/bundled-features/src/renderer-foundation/feature.ts +7 -7
- package/packages/bundled-features/src/renderer-foundation/index.ts +13 -2
- package/packages/bundled-features/src/renderer-foundation/types.ts +132 -35
- package/packages/bundled-features/src/renderer-pdf/feature.ts +7 -1
- package/packages/bundled-features/src/sessions/db/queries/cleanup.ts +2 -2
- package/packages/bundled-features/src/sessions/feature.ts +2 -4
- package/packages/bundled-features/src/sessions/handlers/list.query.ts +2 -1
- package/packages/bundled-features/src/shared/attachment-file-name.ts +39 -0
- package/packages/bundled-features/src/shared/index.ts +6 -0
- package/packages/bundled-features/src/shared/is-tenant-db.ts +1 -1
- package/packages/bundled-features/src/shared/mail-template-body.ts +25 -0
- package/packages/bundled-features/src/shared/sharp-input-limits.ts +26 -0
- package/packages/bundled-features/src/shared/signup-handover.ts +4 -4
- package/packages/bundled-features/src/tags/handlers/assign-tag.write.ts +5 -1
- package/packages/bundled-features/src/tags/web/tag-chip.tsx +2 -1
- package/packages/bundled-features/src/template-resolver/README.md +63 -7
- package/packages/bundled-features/src/template-resolver/api.ts +83 -8
- package/packages/bundled-features/src/template-resolver/changes.json +118 -0
- package/packages/bundled-features/src/template-resolver/compile-document-body.ts +214 -0
- package/packages/bundled-features/src/template-resolver/constants.ts +39 -11
- package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +335 -0
- package/packages/bundled-features/src/template-resolver/document-schema.ts +323 -0
- package/packages/bundled-features/src/template-resolver/feature.ts +56 -6
- package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +188 -0
- package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +30 -2
- package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +142 -0
- package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +72 -0
- package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +83 -0
- package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +3 -3
- package/packages/bundled-features/src/template-resolver/handlers/import-bundle-system.write.ts +42 -0
- package/packages/bundled-features/src/template-resolver/handlers/import-bundle.write.ts +22 -0
- package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +108 -0
- package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +5 -7
- package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +250 -0
- package/packages/bundled-features/src/template-resolver/handlers/preview.query.ts +25 -0
- package/packages/bundled-features/src/template-resolver/handlers/reset-to-system.write.ts +58 -0
- package/packages/bundled-features/src/template-resolver/handlers/resource-link-pdf.write.ts +34 -0
- package/packages/bundled-features/src/template-resolver/handlers/resource-remove.write.ts +50 -0
- package/packages/bundled-features/src/template-resolver/handlers/resource-shared.ts +24 -0
- package/packages/bundled-features/src/template-resolver/handlers/resource-upload.write.ts +135 -0
- package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +106 -0
- package/packages/bundled-features/src/template-resolver/handlers/set.write.ts +32 -3
- package/packages/bundled-features/src/template-resolver/handlers/shared.ts +7 -2
- package/packages/bundled-features/src/template-resolver/handlers/template-gates.ts +184 -0
- package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +183 -0
- package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +55 -0
- package/packages/bundled-features/src/template-resolver/handlers/toggle-status.write.ts +57 -10
- package/packages/bundled-features/src/template-resolver/handlers/upsert-system.write.ts +19 -71
- package/packages/bundled-features/src/template-resolver/handlers/upsert-tenant.write.ts +15 -76
- package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +17 -0
- package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +116 -0
- package/packages/bundled-features/src/template-resolver/index.ts +100 -0
- package/packages/bundled-features/src/template-resolver/layout-regions.ts +206 -0
- package/packages/bundled-features/src/template-resolver/linked-resources.ts +102 -0
- package/packages/bundled-features/src/template-resolver/preview-display-html.ts +36 -0
- package/packages/bundled-features/src/template-resolver/print-document.ts +58 -0
- package/packages/bundled-features/src/template-resolver/qualified-names.ts +30 -0
- package/packages/bundled-features/src/template-resolver/record-mapping.ts +26 -0
- package/packages/bundled-features/src/template-resolver/resource-image.ts +47 -0
- package/packages/bundled-features/src/template-resolver/seeding.ts +147 -17
- package/packages/bundled-features/src/template-resolver/store-template-resource.ts +416 -0
- package/packages/bundled-features/src/template-resolver/table.ts +14 -0
- package/packages/bundled-features/src/template-resolver/template-bundle.ts +79 -0
- package/packages/bundled-features/src/template-resolver/template-diff.ts +78 -0
- package/packages/bundled-features/src/template-resolver/template-meta.ts +129 -0
- package/packages/bundled-features/src/template-resolver/template-preview.ts +573 -0
- package/packages/bundled-features/src/template-resolver/template-resources.ts +74 -0
- package/packages/bundled-features/src/template-resolver/template-usage.ts +48 -0
- package/packages/bundled-features/src/template-resolver/template-validation.ts +229 -0
- package/packages/bundled-features/src/template-resolver/testing.ts +5 -5
- package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +9 -6
- package/packages/bundled-features/src/template-resolver/web/document-editor-extensions.ts +162 -0
- package/packages/bundled-features/src/template-resolver/web/document-editor.tsx +28 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +850 -0
- package/packages/bundled-features/src/template-resolver/web/document-toolbar-blocks.ts +61 -0
- package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +26 -0
- package/packages/bundled-features/src/template-resolver/web/i18n.ts +100 -0
- package/packages/bundled-features/src/template-resolver/web/index.ts +19 -0
- package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor-lazy.tsx +36 -0
- package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +169 -0
- package/packages/bundled-features/src/template-resolver/web/placeholder-text.ts +37 -0
- package/packages/bundled-features/src/template-resolver/web/use-stable-by-key.ts +14 -0
- package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +3 -2
- package/packages/bundled-features/src/tenant/seeding.ts +41 -7
- package/packages/bundled-features/src/tenant-lifecycle/boot-checks.ts +1 -1
- package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +2 -1
- package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +4 -4
- package/packages/bundled-features/src/user/handlers/detail.query.ts +3 -5
- package/packages/bundled-features/src/user/roles-input-schema.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/changes.json +7 -0
- package/packages/bundled-features/src/user-data-rights/db/queries/forget-cleanup.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/email-templates.ts +5 -149
- package/packages/bundled-features/src/user-data-rights/feature.ts +72 -35
- package/packages/bundled-features/src/user-data-rights/handlers/cancel-deletion.write.ts +2 -1
- package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +5 -11
- package/packages/bundled-features/src/user-data-rights/lib/default-mailers.ts +134 -53
- package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/mail-notifications.ts +157 -0
- package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +2 -1
- package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +2 -1
- package/packages/bundled-features/src/waitlist/changes.json +7 -0
- package/packages/bundled-features/src/waitlist/feature.ts +17 -0
- package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +4 -5
- package/packages/bundled-features/src/waitlist/mail.ts +69 -18
- package/packages/bundled-features/src/workflow-runner/event-trigger.ts +1 -2
- package/samples/apps/ui-walkthrough/package.json +2 -0
- package/samples/apps/ui-walkthrough/src/app/auth-constants.ts +9 -0
- package/samples/apps/ui-walkthrough/src/app/client.tsx +6 -0
- package/samples/apps/ui-walkthrough/src/app/seed-templates.ts +154 -0
- package/samples/apps/ui-walkthrough/src/app/server.ts +5 -1
- package/samples/apps/ui-walkthrough/src/app/shell.tsx +12 -1
- package/samples/apps/ui-walkthrough/src/app/template-seed-data.ts +354 -0
- package/samples/apps/ui-walkthrough/src/features/billing/feature.ts +68 -0
- package/samples/apps/ui-walkthrough/src/features/billing/i18n.ts +18 -0
- package/samples/apps/ui-walkthrough/src/features/billing/index.ts +2 -0
- package/samples/apps/ui-walkthrough/src/features/billing/web/index.ts +7 -0
- package/samples/apps/ui-walkthrough/src/run-config.ts +37 -1
- package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +0 -2
- package/samples/apps/use-all-bundled/feature-manifest.json +115 -20
- package/samples/apps/use-all-bundled/kumiko/migrations/0045_template-resource-meta.rebuild.json +4 -0
- package/samples/apps/use-all-bundled/kumiko/migrations/0046_pdf-rendered.rebuild.json +4 -0
- package/samples/apps/use-all-bundled/package.json +2 -0
- package/samples/apps/use-all-bundled/playwright.config.ts +38 -3
- package/samples/apps/use-all-bundled/schema/generate.ts +13 -3
- package/samples/apps/use-all-bundled/schema/schema.generated.ts +3 -0
- package/samples/apps/use-all-bundled/scripts/check-coverage.ts +3 -0
- package/samples/apps/use-all-bundled/src/app/auth-constants.ts +3 -0
- package/samples/apps/use-all-bundled/src/app/client.tsx +4 -0
- package/samples/apps/use-all-bundled/src/app/seed-documents.ts +128 -0
- package/samples/apps/use-all-bundled/src/app/seed.ts +4 -0
- package/samples/apps/use-all-bundled/src/run-config.ts +11 -11
- package/samples/recipes/delivery-notifications/README.md +26 -25
- package/samples/recipes/delivery-notifications/package.json +2 -1
- package/samples/recipes/delivery-notifications/src/feature.ts +64 -58
- package/samples/recipes/seed-migration/README.md +1 -1
- package/packages/bundled-features/src/renderer-simple/changes.json +0 -27
- package/packages/bundled-features/src/renderer-simple/feature.ts +0 -66
- package/packages/bundled-features/src/renderer-simple/index.ts +0 -8
- package/packages/bundled-features/src/renderer-simple/mail-blocks.ts +0 -138
- package/packages/bundled-features/src/renderer-simple/resolve-variables.ts +0 -57
- package/packages/bundled-features/src/renderer-simple/simple-renderer.ts +0 -304
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
defineEntityDetailHandler,
|
|
4
4
|
type HandlerContext,
|
|
5
5
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
6
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
6
7
|
import { userEntity } from "../schema/user.js";
|
|
7
8
|
import { attachTenantLabels, dbForList } from "./list.query.js";
|
|
8
9
|
|
|
@@ -24,12 +25,9 @@ export const detailQuery = {
|
|
|
24
25
|
ctx: HandlerContext,
|
|
25
26
|
) => {
|
|
26
27
|
const result = await baseDetail.handler?.(query, ctx);
|
|
27
|
-
if (result
|
|
28
|
+
if (!isPlainObject(result)) return result;
|
|
28
29
|
if (!ctx.registry.features.has("tenant")) return result;
|
|
29
|
-
const [enriched] = await attachTenantLabels(
|
|
30
|
-
[result as Record<string, unknown>],
|
|
31
|
-
dbForList(ctx),
|
|
32
|
-
);
|
|
30
|
+
const [enriched] = await attachTenantLabels([result], dbForList(ctx));
|
|
33
31
|
return enriched ?? result;
|
|
34
32
|
},
|
|
35
33
|
};
|
|
@@ -8,8 +8,8 @@ export const rolesInputSchema = z.union([
|
|
|
8
8
|
z.string().transform((raw, ctx) => {
|
|
9
9
|
try {
|
|
10
10
|
const parsed: unknown = JSON.parse(raw);
|
|
11
|
-
if (Array.isArray(parsed) && parsed.every((v) => typeof v === "string")) {
|
|
12
|
-
return parsed
|
|
11
|
+
if (Array.isArray(parsed) && parsed.every((v): v is string => typeof v === "string")) {
|
|
12
|
+
return parsed;
|
|
13
13
|
}
|
|
14
14
|
} catch {
|
|
15
15
|
/* fall through */
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.363.0",
|
|
4
|
+
"type": "breaking",
|
|
5
|
+
"title": "GDPR default mails are r.notification declarations, render*Email is gone",
|
|
6
|
+
"detail": "export-ready, export-failed, deletion-requested and deletion-executed are declared as trigger-less notifications (user-data-rights:notify:<name>) with variables and localized templates built from the GDPR translations, so a tenant can override the text. The default export-ready, export-failed and deletion-requested mails send through ctx.notify to the user (export mails with immediate so the plain magic-link token never lands in a job payload). deletion-executed renders the seeded template through the mail renderer and sends over the tenant mail transport without a delivery log, because the user is already gone. App-provided send*Email callbacks still win. The defaults now need delivery with channel-email; a boot warning is logged once when mail-foundation and a mail transport are mounted without them. New exports are GdprMailNotificationTypes, gdprMailLocalizedTemplate, gdprMailTemplateLocale, registerGdprMails and formatGdprMailTimestamp.",
|
|
7
|
+
"migration": "renderExportReadyEmail, renderExportFailedEmail, renderDeletionRequestedEmail, renderDeletionExecutedEmail and RenderedEmail are removed from user-data-rights/email-templates; the translations stay. An app that mounts it without includeBundled needs template-resolver, renderer-foundation and @cosmicdrift/kumiko-renderer-mail-html at boot (composeRendererStack() from dev-server mounts all of them), plus a delivery with the channel-email feature; apps without the template tables need a schema migration (`kumiko schema generate`). Delivery preferences that referenced the mails use the new user-data-rights:notify:<name> types."
|
|
8
|
+
},
|
|
2
9
|
{
|
|
3
10
|
"version": "0.362.0",
|
|
4
11
|
"type": "fix",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
|
-
import type {
|
|
2
|
+
import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
|
|
3
3
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
4
4
|
import { userTable } from "../../../user/index.js";
|
|
5
5
|
|
|
6
6
|
export async function selectUsersDueForForgetCleanup(
|
|
7
|
-
db:
|
|
7
|
+
db: DbRunner,
|
|
8
8
|
status: string,
|
|
9
9
|
gracePeriodEndCutoff: Temporal.Instant | string,
|
|
10
10
|
): Promise<readonly { id: string }[]> {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
// Translations for the GDPR notification mails (the templates in
|
|
2
|
+
// mail-notifications.ts are built from them). English ships here; other
|
|
3
|
+
// languages register via @cosmicdrift/kumiko-locale-* (localeDe() /
|
|
3
4
|
// localeEs() call registerMailTranslations). Apps that want their own
|
|
4
5
|
// branding pass send*Email callbacks and these defaults do not run.
|
|
5
6
|
|
|
6
|
-
import {
|
|
7
|
-
import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
|
|
7
|
+
import { registerMailTranslations } from "@cosmicdrift/kumiko-framework/i18n";
|
|
8
8
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
9
9
|
|
|
10
10
|
export type GdprMailLocale = string;
|
|
@@ -41,151 +41,7 @@ export const GDPR_MAIL_EN: Readonly<Record<string, string>> = {
|
|
|
41
41
|
|
|
42
42
|
registerMailTranslations("en", GDPR_MAIL_EN);
|
|
43
43
|
|
|
44
|
-
export
|
|
45
|
-
readonly subject: string;
|
|
46
|
-
readonly html: string;
|
|
47
|
-
readonly text: string;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export type RenderExportReadyEmailArgs = {
|
|
51
|
-
readonly downloadUrl: string;
|
|
52
|
-
readonly expiresAt: string;
|
|
53
|
-
readonly locale?: GdprMailLocale;
|
|
54
|
-
readonly appName?: string;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export type RenderExportFailedEmailArgs = {
|
|
58
|
-
readonly locale?: GdprMailLocale;
|
|
59
|
-
readonly appName?: string;
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
export type RenderDeletionRequestedEmailArgs = {
|
|
63
|
-
readonly gracePeriodEnd: string;
|
|
64
|
-
readonly locale?: GdprMailLocale;
|
|
65
|
-
readonly appName?: string;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
export type RenderDeletionExecutedEmailArgs = {
|
|
69
|
-
readonly executedAt: string;
|
|
70
|
-
readonly locale?: GdprMailLocale;
|
|
71
|
-
readonly appName?: string;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
function t(locale: string, key: string, params?: Readonly<Record<string, string>>): string {
|
|
75
|
-
return mailT(locale, key, params);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function appNameFor(args: { locale?: GdprMailLocale; appName?: string }): string {
|
|
79
|
-
const locale = args.locale ?? "en";
|
|
80
|
-
return args.appName ?? t(locale, "gdpr.mail.appNameDefault");
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function renderExportReadyEmail(args: RenderExportReadyEmailArgs): RenderedEmail {
|
|
84
|
-
const locale = args.locale ?? "en";
|
|
85
|
-
const app = appNameFor(args);
|
|
86
|
-
const subject = t(locale, "gdpr.mail.exportReady.subject", { app });
|
|
87
|
-
const body = `
|
|
88
|
-
<p style="margin: 0 0 16px; font-size: 16px;">${escapeHtml(t(locale, "gdpr.mail.greeting"))}</p>
|
|
89
|
-
<p style="margin: 0 0 24px; font-size: 14px; line-height: 1.5;">${escapeHtml(t(locale, "gdpr.mail.exportReady.intro", { app }))}</p>
|
|
90
|
-
<p style="margin: 0 0 24px;">${renderButton({ url: args.downloadUrl, label: t(locale, "gdpr.mail.exportReady.button") })}</p>
|
|
91
|
-
<p style="margin: 0 0 8px; font-size: 13px; color: #555;">${escapeHtml(t(locale, "gdpr.mail.exportReady.expiry", { when: formatTimestamp(args.expiresAt) }))}</p>
|
|
92
|
-
${renderFallbackUrl({ url: args.downloadUrl, label: t(locale, "gdpr.mail.fallbackUrl") })}`;
|
|
93
|
-
const text = plainTextBody([
|
|
94
|
-
t(locale, "gdpr.mail.greeting"),
|
|
95
|
-
t(locale, "gdpr.mail.exportReady.intro", { app }),
|
|
96
|
-
`${t(locale, "gdpr.mail.exportReady.button")}: ${args.downloadUrl}`,
|
|
97
|
-
t(locale, "gdpr.mail.exportReady.expiry", { when: formatTimestamp(args.expiresAt) }),
|
|
98
|
-
]);
|
|
99
|
-
return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }), text };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function renderExportFailedEmail(args: RenderExportFailedEmailArgs): RenderedEmail {
|
|
103
|
-
const locale = args.locale ?? "en";
|
|
104
|
-
const app = appNameFor(args);
|
|
105
|
-
const subject = t(locale, "gdpr.mail.exportFailed.subject", { app });
|
|
106
|
-
const body = `
|
|
107
|
-
<p style="margin: 0 0 16px; font-size: 16px;">${escapeHtml(t(locale, "gdpr.mail.greeting"))}</p>
|
|
108
|
-
<p style="margin: 0; font-size: 14px; line-height: 1.5;">${escapeHtml(t(locale, "gdpr.mail.exportFailed.intro", { app }))}</p>`;
|
|
109
|
-
const text = plainTextBody([
|
|
110
|
-
t(locale, "gdpr.mail.greeting"),
|
|
111
|
-
t(locale, "gdpr.mail.exportFailed.intro", { app }),
|
|
112
|
-
]);
|
|
113
|
-
return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }), text };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export function renderDeletionRequestedEmail(
|
|
117
|
-
args: RenderDeletionRequestedEmailArgs,
|
|
118
|
-
): RenderedEmail {
|
|
119
|
-
const locale = args.locale ?? "en";
|
|
120
|
-
const app = appNameFor(args);
|
|
121
|
-
const when = formatTimestamp(args.gracePeriodEnd);
|
|
122
|
-
const subject = t(locale, "gdpr.mail.deletionRequested.subject", { app });
|
|
123
|
-
const body = `
|
|
124
|
-
<p style="margin: 0 0 16px; font-size: 16px;">${escapeHtml(t(locale, "gdpr.mail.greeting"))}</p>
|
|
125
|
-
<p style="margin: 0 0 16px; font-size: 14px; line-height: 1.5;">${escapeHtml(t(locale, "gdpr.mail.deletionRequested.intro", { app, when }))}</p>
|
|
126
|
-
<p style="margin: 0; font-size: 13px; color: #555;">${escapeHtml(t(locale, "gdpr.mail.deletionRequested.cancel"))}</p>`;
|
|
127
|
-
const text = plainTextBody([
|
|
128
|
-
t(locale, "gdpr.mail.greeting"),
|
|
129
|
-
t(locale, "gdpr.mail.deletionRequested.intro", { app, when }),
|
|
130
|
-
t(locale, "gdpr.mail.deletionRequested.cancel"),
|
|
131
|
-
]);
|
|
132
|
-
return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }), text };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export function renderDeletionExecutedEmail(args: RenderDeletionExecutedEmailArgs): RenderedEmail {
|
|
136
|
-
const locale = args.locale ?? "en";
|
|
137
|
-
const app = appNameFor(args);
|
|
138
|
-
const when = formatTimestamp(args.executedAt);
|
|
139
|
-
const subject = t(locale, "gdpr.mail.deletionExecuted.subject", { app });
|
|
140
|
-
const body = `
|
|
141
|
-
<p style="margin: 0 0 16px; font-size: 16px;">${escapeHtml(t(locale, "gdpr.mail.greeting"))}</p>
|
|
142
|
-
<p style="margin: 0; font-size: 14px; line-height: 1.5;">${escapeHtml(t(locale, "gdpr.mail.deletionExecuted.intro", { app, when }))}</p>`;
|
|
143
|
-
const text = plainTextBody([
|
|
144
|
-
t(locale, "gdpr.mail.greeting"),
|
|
145
|
-
t(locale, "gdpr.mail.deletionExecuted.intro", { app, when }),
|
|
146
|
-
]);
|
|
147
|
-
return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }), text };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function plainTextBody(paragraphs: readonly string[]): string {
|
|
151
|
-
return paragraphs.join("\n\n");
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function wrapCell(bodyHtml: string): string {
|
|
155
|
-
return `<tr><td>${bodyHtml}</td></tr>`;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function renderShell(args: { title: string; bodyHtml: string; locale: GdprMailLocale }): string {
|
|
159
|
-
return `<!DOCTYPE html>
|
|
160
|
-
<html lang="${escapeHtmlAttr(args.locale)}">
|
|
161
|
-
<head>
|
|
162
|
-
<meta charset="utf-8" />
|
|
163
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
164
|
-
<title>${escapeHtml(args.title)}</title>
|
|
165
|
-
</head>
|
|
166
|
-
<body style="margin: 0; padding: 0; background: #f7f7f7; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #1a1a1a;">
|
|
167
|
-
<table width="100%" cellpadding="0" cellspacing="0" style="padding: 24px 0;">
|
|
168
|
-
<tr>
|
|
169
|
-
<td align="center">
|
|
170
|
-
<table width="560" cellpadding="0" cellspacing="0" style="max-width: 560px; background: #ffffff; border-radius: 8px; padding: 32px;">
|
|
171
|
-
${args.bodyHtml}
|
|
172
|
-
</table>
|
|
173
|
-
</td>
|
|
174
|
-
</tr>
|
|
175
|
-
</table>
|
|
176
|
-
</body>
|
|
177
|
-
</html>`;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function renderButton(args: { url: string; label: string }): string {
|
|
181
|
-
return `<a href="${escapeHtmlAttr(args.url)}" style="display: inline-block; background: #1a1a1a; color: #ffffff; padding: 12px 24px; border-radius: 6px; text-decoration: none; font-weight: 500;">${escapeHtml(args.label)}</a>`;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function renderFallbackUrl(args: { url: string; label: string }): string {
|
|
185
|
-
return `<p style="margin: 24px 0 0; font-size: 12px; color: #666;">${escapeHtml(args.label)}<br /><a href="${escapeHtmlAttr(args.url)}" style="color: #1a1a1a; word-break: break-all;">${escapeHtml(args.url)}</a></p>`;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function formatTimestamp(iso: string): string {
|
|
44
|
+
export function formatGdprMailTimestamp(iso: string): string {
|
|
189
45
|
try {
|
|
190
46
|
const z = Temporal.Instant.from(iso).toZonedDateTimeISO("UTC");
|
|
191
47
|
return `${z.year}-${pad2(z.month)}-${pad2(z.day)} ${pad2(z.hour)}:${pad2(z.minute)} UTC`;
|
|
@@ -2,16 +2,21 @@
|
|
|
2
2
|
import { UNKNOWN_CLIENT_IP } from "@cosmicdrift/kumiko-framework/api";
|
|
3
3
|
import {
|
|
4
4
|
createSystemConfig,
|
|
5
|
+
createSystemUser,
|
|
5
6
|
defineFeature,
|
|
6
7
|
EXT_USER_DATA,
|
|
7
8
|
type FeatureDefinition,
|
|
8
9
|
i18nKey,
|
|
9
10
|
SYSTEM_USER_ID,
|
|
11
|
+
type TenantId,
|
|
10
12
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
13
|
+
import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
|
|
11
14
|
import {
|
|
12
15
|
createMetricsHandle,
|
|
13
16
|
createNoopMetricsHandle,
|
|
14
17
|
} from "@cosmicdrift/kumiko-framework/observability";
|
|
18
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
19
|
+
import { createConfigAccessor } from "../config/index.js";
|
|
15
20
|
import { validateGdprHookCompleteness, validateGdprPiiHookCoverage } from "./boot-checks.js";
|
|
16
21
|
import {
|
|
17
22
|
EXPORT_SECTION_EXTENSION_NAME,
|
|
@@ -45,6 +50,8 @@ import { createRunForgetCleanupHandler } from "./handlers/run-forget-cleanup.wri
|
|
|
45
50
|
import { USER_DATA_RIGHTS_I18N } from "./i18n.js";
|
|
46
51
|
import {
|
|
47
52
|
type GdprMailDefaults,
|
|
53
|
+
isGdprMailDeliveryAvailable,
|
|
54
|
+
isMailRendererAvailable,
|
|
48
55
|
isMailTransportAvailable,
|
|
49
56
|
makeDefaultDeletionExecutedEmail,
|
|
50
57
|
makeDefaultExportFailedEmail,
|
|
@@ -53,6 +60,7 @@ import {
|
|
|
53
60
|
import { makeTenantMailTransportResolver } from "./lib/mail-transport-resolver.js";
|
|
54
61
|
import { resolveAppTenantModel } from "./lib/resolve-tenant-model.js";
|
|
55
62
|
import { makeTenantStorageProviderResolver } from "./lib/storage-provider-resolver.js";
|
|
63
|
+
import { registerGdprMails } from "./mail-notifications.js";
|
|
56
64
|
import {
|
|
57
65
|
EXPORT_CLEANUP_BACKLOG_AGE_METRIC,
|
|
58
66
|
EXPORT_CLEANUP_LAST_RUN_TIMESTAMP_METRIC,
|
|
@@ -163,11 +171,22 @@ export type UserDataRightsOptions = {
|
|
|
163
171
|
readonly privacyCenterShowDeletion?: boolean;
|
|
164
172
|
};
|
|
165
173
|
|
|
174
|
+
function isMailTransportMountedWithoutDelivery(features: readonly FeatureDefinition[]): boolean {
|
|
175
|
+
const names = new Set(features.map((feature) => feature.name));
|
|
176
|
+
const hasTransport = [...names].some((name) => name.startsWith("mail-transport-"));
|
|
177
|
+
return (
|
|
178
|
+
names.has("mail-foundation") &&
|
|
179
|
+
hasTransport &&
|
|
180
|
+
!(names.has("delivery") && names.has("channel-email"))
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
166
184
|
export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): FeatureDefinition {
|
|
167
185
|
// One-shot operator warning (the export cron fires daily — warn once per
|
|
168
186
|
// process, not every run). Lives in the factory scope so the cron closure
|
|
169
187
|
// shares it across runs.
|
|
170
188
|
let warnedMissingExportUrl = false;
|
|
189
|
+
let warnedMissingMailDelivery = false;
|
|
171
190
|
const showDeletion = opts.privacyCenterShowDeletion ?? true;
|
|
172
191
|
return defineFeature("user-data-rights", (r) => {
|
|
173
192
|
r.describe(
|
|
@@ -197,7 +216,15 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
197
216
|
// EXT_USER_DATA channel owner: any r.useExtension(EXT_USER_DATA, ...)
|
|
198
217
|
// usage already requires user-data-rights to be mounted.
|
|
199
218
|
r.bootCheck(({ features }) => validateGdprHookCompleteness(features));
|
|
200
|
-
r.bootCheck(({ features }) =>
|
|
219
|
+
r.bootCheck(({ features }) => {
|
|
220
|
+
validateGdprPiiHookCoverage(features);
|
|
221
|
+
if (!warnedMissingMailDelivery && isMailTransportMountedWithoutDelivery(features)) {
|
|
222
|
+
warnedMissingMailDelivery = true;
|
|
223
|
+
createFallbackLogger("user-data-rights").warn(
|
|
224
|
+
"mail-foundation and a mail transport are mounted but delivery/channel-email are not — the default export and deletion-request mails need them and will not be sent",
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
201
228
|
|
|
202
229
|
// App-level tenant-occupancy model. Default "multi-user" → tenant-scoped
|
|
203
230
|
// contributors (e.g. credit) NEVER erase tenant data on a per-user forget
|
|
@@ -312,6 +339,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
312
339
|
});
|
|
313
340
|
|
|
314
341
|
r.translations({ keys: USER_DATA_RIGHTS_I18N });
|
|
342
|
+
registerGdprMails(r);
|
|
315
343
|
// Privacy-center dot-form labels (i18nKey()-marked below), duplicated in
|
|
316
344
|
// web/i18n.ts's client bundle since privacy-center-screen.tsx renders
|
|
317
345
|
// them client-side.
|
|
@@ -520,10 +548,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
520
548
|
handler: async (c, { app, clientIp }) => {
|
|
521
549
|
const url = new URL(c.req.url);
|
|
522
550
|
const parsedBody = await c.req.json().catch(() => null);
|
|
523
|
-
const token =
|
|
524
|
-
parsedBody && typeof parsedBody === "object" && "token" in parsedBody
|
|
525
|
-
? (parsedBody as { token?: unknown }).token
|
|
526
|
-
: undefined;
|
|
551
|
+
const token = isPlainObject(parsedBody) ? parsedBody["token"] : undefined;
|
|
527
552
|
if (typeof token !== "string" || token.length === 0) {
|
|
528
553
|
return c.json({ error: "missing_token" }, 400);
|
|
529
554
|
}
|
|
@@ -605,28 +630,25 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
605
630
|
// versendet der Cron die Export-Notifications selbst (Default-Templates).
|
|
606
631
|
// Der Resolver baut den per-Tenant-Transport aus configResolver — gleiche
|
|
607
632
|
// Bruecke wie buildStorageProvider.
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
handlerName: "user-data-rights:run-export-jobs",
|
|
616
|
-
})
|
|
617
|
-
: undefined;
|
|
633
|
+
// The cron runs across tenants: each mail needs a notify bound to the
|
|
634
|
+
// recipient's tenant (template override, channel config).
|
|
635
|
+
const notifyFactory = ctx._notifyFactory;
|
|
636
|
+
const exportNotifyFor =
|
|
637
|
+
notifyFactory !== undefined && isGdprMailDeliveryAvailable(exportRegistry)
|
|
638
|
+
? (tenantId: TenantId) => notifyFactory(createSystemUser(tenantId), tenantId)
|
|
639
|
+
: undefined;
|
|
618
640
|
const sendExportReadyEmail =
|
|
619
641
|
opts.sendExportReadyEmail ??
|
|
620
|
-
(
|
|
621
|
-
? makeDefaultExportReadyEmail(
|
|
642
|
+
(exportNotifyFor && opts.appExportDownloadUrl !== undefined
|
|
643
|
+
? makeDefaultExportReadyEmail(exportNotifyFor, opts.mailDefaults)
|
|
622
644
|
: undefined);
|
|
623
645
|
const sendExportFailedEmail =
|
|
624
646
|
opts.sendExportFailedEmail ??
|
|
625
|
-
(
|
|
626
|
-
? makeDefaultExportFailedEmail(
|
|
647
|
+
(exportNotifyFor
|
|
648
|
+
? makeDefaultExportFailedEmail(exportNotifyFor, opts.mailDefaults)
|
|
627
649
|
: undefined);
|
|
628
650
|
if (
|
|
629
|
-
|
|
651
|
+
exportNotifyFor &&
|
|
630
652
|
!opts.sendExportReadyEmail &&
|
|
631
653
|
opts.appExportDownloadUrl === undefined &&
|
|
632
654
|
!warnedMissingExportUrl
|
|
@@ -634,7 +656,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
634
656
|
warnedMissingExportUrl = true;
|
|
635
657
|
// biome-ignore lint/suspicious/noConsole: one-shot operator visibility for misconfig
|
|
636
658
|
console.warn(
|
|
637
|
-
"[user-data-rights:run-export-jobs] mail
|
|
659
|
+
"[user-data-rights:run-export-jobs] mail delivery mounted but appExportDownloadUrl unset — default export-ready emails disabled (export-failed + deletion mails still send)",
|
|
638
660
|
);
|
|
639
661
|
}
|
|
640
662
|
|
|
@@ -697,22 +719,37 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
697
719
|
userId: forgetUserId,
|
|
698
720
|
});
|
|
699
721
|
|
|
700
|
-
//
|
|
701
|
-
// mail
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
registry: forgetRegistry,
|
|
705
|
-
configResolver: ctx.configResolver,
|
|
706
|
-
secrets: ctx.secrets,
|
|
707
|
-
db: forgetDb,
|
|
708
|
-
userId: forgetUserId,
|
|
709
|
-
handlerName: "user-data-rights:run-forget-cleanup",
|
|
710
|
-
})
|
|
711
|
-
: undefined;
|
|
722
|
+
// Without an app callback the default mail goes out when a mail transport and a
|
|
723
|
+
// mail renderer are mounted. The user is already erased here, so it bypasses the
|
|
724
|
+
// delivery log: an attempt row would be new personal data.
|
|
725
|
+
const forgetConfigResolver = ctx.configResolver;
|
|
712
726
|
const sendDeletionExecutedEmail =
|
|
713
727
|
opts.sendDeletionExecutedEmail ??
|
|
714
|
-
(
|
|
715
|
-
? makeDefaultDeletionExecutedEmail(
|
|
728
|
+
(isMailTransportAvailable(forgetRegistry) && isMailRendererAvailable(forgetRegistry)
|
|
729
|
+
? makeDefaultDeletionExecutedEmail(
|
|
730
|
+
{
|
|
731
|
+
registry: forgetRegistry,
|
|
732
|
+
db: forgetDb,
|
|
733
|
+
resolveTransport: makeTenantMailTransportResolver({
|
|
734
|
+
registry: forgetRegistry,
|
|
735
|
+
configResolver: forgetConfigResolver,
|
|
736
|
+
secrets: ctx.secrets,
|
|
737
|
+
db: forgetDb,
|
|
738
|
+
userId: forgetUserId,
|
|
739
|
+
handlerName: "user-data-rights:run-forget-cleanup",
|
|
740
|
+
}),
|
|
741
|
+
configFor: (tenantId) =>
|
|
742
|
+
forgetConfigResolver &&
|
|
743
|
+
createConfigAccessor(
|
|
744
|
+
forgetRegistry,
|
|
745
|
+
forgetConfigResolver,
|
|
746
|
+
tenantId,
|
|
747
|
+
forgetUserId,
|
|
748
|
+
forgetDb,
|
|
749
|
+
),
|
|
750
|
+
},
|
|
751
|
+
opts.mailDefaults,
|
|
752
|
+
)
|
|
716
753
|
: undefined);
|
|
717
754
|
|
|
718
755
|
const forgetResult = await runForgetCleanup({
|
|
@@ -71,12 +71,13 @@ export const cancelDeletionWrite = defineWriteHandler({
|
|
|
71
71
|
|
|
72
72
|
// gracePeriodEnd=null im Response symmetrisch zu request-deletion's
|
|
73
73
|
// ISO-Timestamp — Frontend kann beide Endpoints uniform behandeln.
|
|
74
|
+
const gracePeriodEnd: string | null = null;
|
|
74
75
|
return {
|
|
75
76
|
isSuccess: true as const,
|
|
76
77
|
data: {
|
|
77
78
|
userId: event.user.id,
|
|
78
79
|
status: USER_STATUS.Active,
|
|
79
|
-
gracePeriodEnd
|
|
80
|
+
gracePeriodEnd,
|
|
80
81
|
},
|
|
81
82
|
};
|
|
82
83
|
},
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { createTransportForTenant } from "@cosmicdrift/kumiko-bundled-features/mail-foundation";
|
|
2
1
|
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
2
|
import { writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
4
3
|
import * as z from "zod";
|
|
5
4
|
import { USER_STATUS } from "../../user/index.js";
|
|
6
5
|
import {
|
|
7
6
|
type GdprMailDefaults,
|
|
8
|
-
|
|
7
|
+
isGdprMailDeliveryAvailable,
|
|
9
8
|
makeDefaultDeletionRequestedEmail,
|
|
10
9
|
} from "../lib/default-mailers.js";
|
|
11
10
|
import { resolveGracePeriod, startDeletionGracePeriod } from "./deletion-grace-period.js";
|
|
@@ -71,17 +70,12 @@ export function createRequestDeletionHandler(opts: RequestDeletionOptions = {})
|
|
|
71
70
|
if (!res.ok) return writeFailure(res.error);
|
|
72
71
|
const { gracePeriodEnd, userEmail, userLocale } = res;
|
|
73
72
|
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
// Ohne gemounteten mail-transport bleibt send undefined → keine Mail.
|
|
73
|
+
// The app callback wins; otherwise the default mail goes through ctx.notify after
|
|
74
|
+
// the commit. Without delivery and an email channel there is no sender and no mail.
|
|
77
75
|
const send =
|
|
78
76
|
opts.sendDeletionRequestedEmail ??
|
|
79
|
-
(
|
|
80
|
-
? makeDefaultDeletionRequestedEmail(
|
|
81
|
-
(tenantId) =>
|
|
82
|
-
createTransportForTenant(ctx, tenantId, "user-data-rights:request-deletion"),
|
|
83
|
-
opts.mailDefaults,
|
|
84
|
-
)
|
|
77
|
+
(ctx.notify !== undefined && isGdprMailDeliveryAvailable(ctx.registry)
|
|
78
|
+
? makeDefaultDeletionRequestedEmail(ctx.notify, opts.mailDefaults)
|
|
85
79
|
: undefined);
|
|
86
80
|
|
|
87
81
|
// Best-effort Email-Notification. Send-Failure darf das Write nicht
|