@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
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
createSelectField,
|
|
6
6
|
createTextField,
|
|
7
7
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
8
|
+
import type { EntityEventMigration } from "@cosmicdrift/kumiko-types/fields";
|
|
8
9
|
import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
9
10
|
import {
|
|
10
11
|
CONTENT_FORMATS,
|
|
@@ -13,6 +14,13 @@ import {
|
|
|
13
14
|
TEMPLATE_STATUSES,
|
|
14
15
|
} from "./constants.js";
|
|
15
16
|
|
|
17
|
+
// Stored v1 events name the kind "mail-html", which is now "mail".
|
|
18
|
+
const renameMailHtmlKind: EntityEventMigration = {
|
|
19
|
+
fromVersion: 1,
|
|
20
|
+
toVersion: 2,
|
|
21
|
+
transform: (fields) => (fields["kind"] === "mail-html" ? { ...fields, kind: "mail" } : fields),
|
|
22
|
+
};
|
|
23
|
+
|
|
16
24
|
// TemplateResource — strukturierte Template-Definition mit Tenant-
|
|
17
25
|
// Override-Hierarchie, Locale-Fallback und Resource-Linking via
|
|
18
26
|
// file-foundation. Pro (tenantId, slug, kind, locale) genau eine Row;
|
|
@@ -44,6 +52,9 @@ export const templateResourceEntity = createEntity({
|
|
|
44
52
|
contentFormat: createSelectField({ required: true, options: [...CONTENT_FORMATS] }),
|
|
45
53
|
variableSchema: createLongTextField({ personal: false, reason: "technical_reference" }),
|
|
46
54
|
linkedResources: createLongTextField({ personal: false, reason: "technical_reference" }),
|
|
55
|
+
// Per-kind JSON (mail: subject, preview text, layout slug, ...), validated
|
|
56
|
+
// against template-meta.ts on write. Authored by TenantAdmin/Operator.
|
|
57
|
+
meta: createLongTextField({ personal: false, reason: "is_business_data" }),
|
|
47
58
|
scope: createSelectField({ required: true, options: [...TEMPLATE_SCOPES] }),
|
|
48
59
|
parentTemplateId: createTextField({ personal: false, reason: "technical_reference" }),
|
|
49
60
|
status: createSelectField({ required: true, options: [...TEMPLATE_STATUSES] }),
|
|
@@ -55,6 +66,8 @@ export const templateResourceEntity = createEntity({
|
|
|
55
66
|
name: "read_template_resources_unique",
|
|
56
67
|
},
|
|
57
68
|
],
|
|
69
|
+
eventVersion: 2,
|
|
70
|
+
eventMigrations: [renameMailHtmlKind],
|
|
58
71
|
});
|
|
59
72
|
|
|
60
73
|
export const templateResourcesTable = buildEntityTable("template-resource", templateResourceEntity);
|
|
@@ -84,6 +97,7 @@ export type TemplateResourceRow = {
|
|
|
84
97
|
readonly contentFormat: string;
|
|
85
98
|
readonly variableSchema: string | null;
|
|
86
99
|
readonly linkedResources: string | null;
|
|
100
|
+
readonly meta: string | null;
|
|
87
101
|
readonly scope: string;
|
|
88
102
|
readonly parentTemplateId: string | null;
|
|
89
103
|
readonly status: string;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
import { CONTENT_FORMATS, UPSERT_KINDS } from "./constants.js";
|
|
3
|
+
import { isDocumentResourceKey } from "./document-schema.js";
|
|
4
|
+
import { templatePdfAttachmentLinkSchema } from "./linked-resources.js";
|
|
5
|
+
import { templateSlugSchema } from "./template-meta.js";
|
|
6
|
+
|
|
7
|
+
const MAX_RESOURCES = 20;
|
|
8
|
+
const MAX_RESOURCE_BYTES = 2 * 1024 * 1024;
|
|
9
|
+
const MAX_BASE64_LENGTH = Math.ceil(MAX_RESOURCE_BYTES / 3) * 4;
|
|
10
|
+
|
|
11
|
+
const STRICT_BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
12
|
+
const CONTENT_TYPE = /^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/i;
|
|
13
|
+
const UNSAFE_FILE_NAME_CHARACTERS = /[/\\";]|\p{Cc}/u;
|
|
14
|
+
|
|
15
|
+
const bundleLocaleSchema = z
|
|
16
|
+
.string()
|
|
17
|
+
.min(2)
|
|
18
|
+
.max(8)
|
|
19
|
+
.regex(/^[a-z]{2}(-[a-z]{2})?$/i, "locale must be ISO 639-1 (e.g. de, en, en-us)");
|
|
20
|
+
|
|
21
|
+
const bundleFileNameSchema = z
|
|
22
|
+
.string()
|
|
23
|
+
.min(1)
|
|
24
|
+
.max(200)
|
|
25
|
+
.refine((name) => !UNSAFE_FILE_NAME_CHARACTERS.test(name) && !name.includes(".."), {
|
|
26
|
+
message: 'file name must not contain / \\ " ; control characters or ".."',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const bundleResourceSchema = z
|
|
30
|
+
.object({
|
|
31
|
+
fileName: bundleFileNameSchema,
|
|
32
|
+
contentType: z.string().max(100).regex(CONTENT_TYPE, "content type must be type/subtype"),
|
|
33
|
+
base64: z.string().max(MAX_BASE64_LENGTH).regex(STRICT_BASE64, "base64 is not valid"),
|
|
34
|
+
// Absent means an image the body embeds; "attachment" is a file attached to the mail.
|
|
35
|
+
purpose: z.enum(["attachment"]).optional(),
|
|
36
|
+
})
|
|
37
|
+
.strict();
|
|
38
|
+
|
|
39
|
+
const pdfAttachmentLinkBodySchema = templatePdfAttachmentLinkSchema.omit({ kind: true });
|
|
40
|
+
|
|
41
|
+
const bundleLocaleEntrySchema = z
|
|
42
|
+
.object({ body: z.string().max(200_000), meta: z.unknown().optional() })
|
|
43
|
+
.strict();
|
|
44
|
+
|
|
45
|
+
export const templateBundleSchema = z
|
|
46
|
+
.object({
|
|
47
|
+
slug: templateSlugSchema,
|
|
48
|
+
kind: z.enum(UPSERT_KINDS),
|
|
49
|
+
contentFormat: z.enum(CONTENT_FORMATS),
|
|
50
|
+
variableSchema: z.record(z.string(), z.unknown()).optional(),
|
|
51
|
+
locales: z
|
|
52
|
+
.record(bundleLocaleSchema, bundleLocaleEntrySchema)
|
|
53
|
+
.refine((locales) => Object.keys(locales).length >= 1, {
|
|
54
|
+
message: "a bundle needs at least one locale",
|
|
55
|
+
}),
|
|
56
|
+
resources: z
|
|
57
|
+
.record(
|
|
58
|
+
z.string().refine(isDocumentResourceKey, { message: "not a valid resource key" }),
|
|
59
|
+
bundleResourceSchema,
|
|
60
|
+
)
|
|
61
|
+
.refine((resources) => Object.keys(resources).length <= MAX_RESOURCES, {
|
|
62
|
+
message: `a bundle holds at most ${MAX_RESOURCES} resources`,
|
|
63
|
+
})
|
|
64
|
+
.default({}),
|
|
65
|
+
// PDFs rendered from document templates at send time; nothing to store, so no bytes.
|
|
66
|
+
pdfAttachments: z
|
|
67
|
+
.record(
|
|
68
|
+
z.string().refine(isDocumentResourceKey, { message: "not a valid resource key" }),
|
|
69
|
+
pdfAttachmentLinkBodySchema,
|
|
70
|
+
)
|
|
71
|
+
.default({}),
|
|
72
|
+
})
|
|
73
|
+
.strict()
|
|
74
|
+
.refine(
|
|
75
|
+
(bundle) => Object.keys(bundle.pdfAttachments).every((key) => !(key in bundle.resources)),
|
|
76
|
+
{ message: "a key is used by a resource and a PDF attachment", path: ["pdfAttachments"] },
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
export type TemplateBundle = z.infer<typeof templateBundleSchema>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { withParsedDocument } from "./document-sanitizer.js";
|
|
2
|
+
|
|
3
|
+
export type TemplateDiffChange = "same" | "added" | "removed";
|
|
4
|
+
|
|
5
|
+
export type TemplateDiffBlock = { readonly change: TemplateDiffChange; readonly content: string };
|
|
6
|
+
|
|
7
|
+
// Beyond this the quadratic LCS table is not worth it for a preview.
|
|
8
|
+
const MAX_LCS_CELLS = 1_000_000;
|
|
9
|
+
|
|
10
|
+
function splitHtmlBlocks(content: string): string[] {
|
|
11
|
+
return withParsedDocument(content, (body) => {
|
|
12
|
+
const blocks: string[] = [];
|
|
13
|
+
for (const node of Array.from(body.childNodes)) {
|
|
14
|
+
const markup =
|
|
15
|
+
"outerHTML" in node && typeof node.outerHTML === "string" ? node.outerHTML : "";
|
|
16
|
+
const text = markup === "" ? (node.textContent ?? "").trim() : markup;
|
|
17
|
+
if (text !== "") blocks.push(text);
|
|
18
|
+
}
|
|
19
|
+
return blocks;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function splitTemplateBlocks(content: string, contentFormat: string): string[] {
|
|
24
|
+
if (contentFormat === "html") return splitHtmlBlocks(content);
|
|
25
|
+
return content
|
|
26
|
+
.split(/\r?\n[ \t]*\r?\n/)
|
|
27
|
+
.map((block) => block.trim())
|
|
28
|
+
.filter((block) => block !== "");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// "removed" = only in the system template, "added" = only in the tenant version.
|
|
32
|
+
export function diffTemplateBlocks(
|
|
33
|
+
system: readonly string[],
|
|
34
|
+
tenant: readonly string[],
|
|
35
|
+
): TemplateDiffBlock[] {
|
|
36
|
+
if (system.length * tenant.length > MAX_LCS_CELLS) {
|
|
37
|
+
return [
|
|
38
|
+
...system.map((content) => ({ change: "removed" as const, content })),
|
|
39
|
+
...tenant.map((content) => ({ change: "added" as const, content })),
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
const rows = system.length;
|
|
43
|
+
const cols = tenant.length;
|
|
44
|
+
const width = cols + 1;
|
|
45
|
+
// lengths[i * width + j] = LCS length of system[i..] and tenant[j..]
|
|
46
|
+
const lengths = new Uint32Array((rows + 1) * width);
|
|
47
|
+
const lengthAt = (i: number, j: number): number => lengths[i * width + j] ?? 0;
|
|
48
|
+
for (let i = rows - 1; i >= 0; i--) {
|
|
49
|
+
for (let j = cols - 1; j >= 0; j--) {
|
|
50
|
+
lengths[i * width + j] =
|
|
51
|
+
system[i] === tenant[j]
|
|
52
|
+
? lengthAt(i + 1, j + 1) + 1
|
|
53
|
+
: Math.max(lengthAt(i + 1, j), lengthAt(i, j + 1));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const result: TemplateDiffBlock[] = [];
|
|
57
|
+
let i = 0;
|
|
58
|
+
let j = 0;
|
|
59
|
+
while (i < rows || j < cols) {
|
|
60
|
+
const systemBlock = system[i];
|
|
61
|
+
const tenantBlock = tenant[j];
|
|
62
|
+
if (systemBlock !== undefined && systemBlock === tenantBlock) {
|
|
63
|
+
result.push({ change: "same", content: systemBlock });
|
|
64
|
+
i++;
|
|
65
|
+
j++;
|
|
66
|
+
} else if (
|
|
67
|
+
systemBlock !== undefined &&
|
|
68
|
+
(tenantBlock === undefined || lengthAt(i + 1, j) >= lengthAt(i, j + 1))
|
|
69
|
+
) {
|
|
70
|
+
result.push({ change: "removed", content: systemBlock });
|
|
71
|
+
i++;
|
|
72
|
+
} else if (tenantBlock !== undefined) {
|
|
73
|
+
result.push({ change: "added", content: tenantBlock });
|
|
74
|
+
j++;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
import { PDF_PAGE_FORMATS, type TemplateKind } from "./constants.js";
|
|
3
|
+
|
|
4
|
+
// One or more kebab segments joined by ":" — notification templates are seeded
|
|
5
|
+
// under their qualified name `feature:notify:name`.
|
|
6
|
+
export const templateSlugSchema = z
|
|
7
|
+
.string()
|
|
8
|
+
.min(1)
|
|
9
|
+
.max(120)
|
|
10
|
+
.regex(
|
|
11
|
+
/^[a-z0-9]+(-[a-z0-9]+)*(:[a-z0-9]+(-[a-z0-9]+)*)*$/,
|
|
12
|
+
"slug must be kebab-case segments joined by ':'",
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export const MAIL_CATEGORIES = ["transactional", "marketing"] as const;
|
|
16
|
+
export type MailCategory = (typeof MAIL_CATEGORIES)[number];
|
|
17
|
+
|
|
18
|
+
// Header values: a CR/LF would let an author inject extra mail headers.
|
|
19
|
+
const singleLine = (max: number) =>
|
|
20
|
+
z
|
|
21
|
+
.string()
|
|
22
|
+
.max(max)
|
|
23
|
+
.refine((value) => !/[\r\n]/.test(value), "must not contain line breaks");
|
|
24
|
+
|
|
25
|
+
const mailMetaSchema = z
|
|
26
|
+
.object({
|
|
27
|
+
subject: singleLine(200).min(1),
|
|
28
|
+
previewText: singleLine(200).optional(),
|
|
29
|
+
layoutSlug: templateSlugSchema.optional(),
|
|
30
|
+
category: z.enum(MAIL_CATEGORIES).optional(),
|
|
31
|
+
sender: z
|
|
32
|
+
.string()
|
|
33
|
+
.max(60)
|
|
34
|
+
.regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "sender must be a kebab-case key")
|
|
35
|
+
.optional(),
|
|
36
|
+
})
|
|
37
|
+
.strict();
|
|
38
|
+
|
|
39
|
+
const PDF_ORIENTATIONS = ["portrait", "landscape"] as const;
|
|
40
|
+
|
|
41
|
+
const marginMm = z.number().finite().min(0).max(100).optional();
|
|
42
|
+
|
|
43
|
+
// The file name ends up in a Content-Disposition header and on disk.
|
|
44
|
+
const FORBIDDEN_FILE_NAME = /[/\\";]|\.\./;
|
|
45
|
+
|
|
46
|
+
// Name of a PDF rendered into a mail; {{placeholders}} are filled at render time.
|
|
47
|
+
export const pdfAttachmentFileNamePatternSchema = singleLine(200)
|
|
48
|
+
.min(1)
|
|
49
|
+
.refine((value) => !FORBIDDEN_FILE_NAME.test(value), 'must not contain / \\ " ; or ..')
|
|
50
|
+
.refine((value) => value.toLowerCase().endsWith(".pdf"), "must end with .pdf");
|
|
51
|
+
|
|
52
|
+
const pdfMetaSchema = z
|
|
53
|
+
.object({
|
|
54
|
+
layoutSlug: templateSlugSchema.optional(),
|
|
55
|
+
pageFormat: z.enum(PDF_PAGE_FORMATS).optional(),
|
|
56
|
+
orientation: z.enum(PDF_ORIENTATIONS).optional(),
|
|
57
|
+
printBackground: z.boolean().optional(),
|
|
58
|
+
layoutHeaderFooter: z.boolean().optional(),
|
|
59
|
+
margins: z
|
|
60
|
+
.object({ top: marginMm, right: marginMm, bottom: marginMm, left: marginMm })
|
|
61
|
+
.strict()
|
|
62
|
+
.optional(),
|
|
63
|
+
fileName: singleLine(200)
|
|
64
|
+
.min(1)
|
|
65
|
+
.refine((value) => !FORBIDDEN_FILE_NAME.test(value), 'must not contain / \\ " ; or ..')
|
|
66
|
+
.optional(),
|
|
67
|
+
metadata: z
|
|
68
|
+
.object({ title: singleLine(200).optional(), author: singleLine(200).optional() })
|
|
69
|
+
.strict()
|
|
70
|
+
.optional(),
|
|
71
|
+
})
|
|
72
|
+
.strict();
|
|
73
|
+
|
|
74
|
+
const layoutMetaSchema = z.object({}).strict();
|
|
75
|
+
|
|
76
|
+
export type MailTemplateMeta = z.infer<typeof mailMetaSchema>;
|
|
77
|
+
export type PdfTemplateMeta = z.infer<typeof pdfMetaSchema>;
|
|
78
|
+
export type LayoutTemplateMeta = z.infer<typeof layoutMetaSchema>;
|
|
79
|
+
export type TemplateMeta = MailTemplateMeta | PdfTemplateMeta | LayoutTemplateMeta;
|
|
80
|
+
|
|
81
|
+
export function isMailTemplateMeta(meta: TemplateMeta | null): meta is MailTemplateMeta {
|
|
82
|
+
return meta !== null && "subject" in meta;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function readPdfTemplateMeta(meta: TemplateMeta | null): PdfTemplateMeta | null {
|
|
86
|
+
const parsed = pdfMetaSchema.safeParse(meta);
|
|
87
|
+
return parsed.success ? parsed.data : null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export type TemplateMetaIssue = { readonly path: string; readonly message: string };
|
|
91
|
+
|
|
92
|
+
export type TemplateMetaResult =
|
|
93
|
+
| { readonly ok: true; readonly meta: TemplateMeta | null }
|
|
94
|
+
| { readonly ok: false; readonly issues: readonly TemplateMetaIssue[] };
|
|
95
|
+
|
|
96
|
+
function schemaForKind(kind: TemplateKind) {
|
|
97
|
+
switch (kind) {
|
|
98
|
+
case "mail":
|
|
99
|
+
return mailMetaSchema;
|
|
100
|
+
case "document-pdf":
|
|
101
|
+
return pdfMetaSchema;
|
|
102
|
+
case "mail-layout":
|
|
103
|
+
case "pdf-layout":
|
|
104
|
+
return layoutMetaSchema;
|
|
105
|
+
default:
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Kinds without a meta schema must not carry meta at all; mail needs it,
|
|
111
|
+
// layouts and PDFs may omit it.
|
|
112
|
+
export function parseTemplateMeta(kind: TemplateKind, raw: unknown): TemplateMetaResult {
|
|
113
|
+
const schema = schemaForKind(kind);
|
|
114
|
+
if (schema === null) {
|
|
115
|
+
return raw === undefined || raw === null
|
|
116
|
+
? { ok: true, meta: null }
|
|
117
|
+
: { ok: false, issues: [{ path: "meta", message: `kind ${kind} does not take meta` }] };
|
|
118
|
+
}
|
|
119
|
+
if ((raw === undefined || raw === null) && kind !== "mail") return { ok: true, meta: null };
|
|
120
|
+
const parsed = schema.safeParse(raw ?? undefined);
|
|
121
|
+
if (parsed.success) return { ok: true, meta: parsed.data };
|
|
122
|
+
return {
|
|
123
|
+
ok: false,
|
|
124
|
+
issues: parsed.error.issues.map((issue) => ({
|
|
125
|
+
path: ["meta", ...issue.path.map(String)].join("."),
|
|
126
|
+
message: issue.message,
|
|
127
|
+
})),
|
|
128
|
+
};
|
|
129
|
+
}
|