@cosmicdrift/kumiko-bundled-features 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/dist/admin-shell/feature.d.ts +3 -0
- package/dist/admin-shell/feature.js +6 -1
- package/dist/agent-tools/agent-manifest.js +2 -1
- package/dist/agent-tools/filter-ops.d.ts +1 -1
- package/dist/agent-tools/filter-ops.js +1 -3
- package/dist/agent-tools/tool-dispatch.js +2 -1
- package/dist/auth-email-password/email-templates.d.ts +4 -26
- package/dist/auth-email-password/email-templates.js +9 -101
- package/dist/auth-email-password/feature.js +24 -6
- package/dist/auth-email-password/handlers/invite-create.write.js +4 -4
- package/dist/auth-email-password/handlers/request-account-unlock.write.js +2 -4
- package/dist/auth-email-password/handlers/request-email-verification.write.js +2 -4
- package/dist/auth-email-password/handlers/request-password-reset.write.js +2 -4
- package/dist/auth-email-password/handlers/signup-confirm.write.js +3 -4
- package/dist/auth-email-password/handlers/token-request-handler.d.ts +1 -2
- package/dist/auth-email-password/handlers/token-request-handler.js +0 -1
- package/dist/auth-email-password/index.d.ts +2 -2
- package/dist/auth-email-password/index.js +1 -1
- package/dist/auth-email-password/magic-link-mail.d.ts +3 -2
- package/dist/auth-email-password/magic-link-mail.js +19 -14
- package/dist/auth-email-password/mail-notifications.d.ts +32 -0
- package/dist/auth-email-password/mail-notifications.js +116 -0
- package/dist/auth-email-password/signup-activation.d.ts +1 -1
- package/dist/auth-email-password/signup-activation.js +2 -3
- package/dist/auth-foundation/types.js +2 -8
- package/dist/auth-mfa/feature.js +4 -4
- package/dist/auth-mfa/hmac-token-codec.js +4 -4
- package/dist/auth-mfa/mfa-challenge-token.js +3 -3
- package/dist/auth-mfa/mfa-preauth-setup-token.js +3 -3
- package/dist/auth-mfa/mfa-setup-token.js +8 -8
- package/dist/billing-foundation/constants.d.ts +3 -3
- package/dist/billing-foundation/constants.js +5 -4
- package/dist/billing-foundation/consumer-protection/confirmation-mail.d.ts +32 -14
- package/dist/billing-foundation/consumer-protection/confirmation-mail.js +27 -32
- package/dist/billing-foundation/consumer-protection/issue-confirmation.js +3 -3
- package/dist/billing-foundation/consumer-protection/mail-notifications.d.ts +8 -0
- package/dist/billing-foundation/consumer-protection/mail-notifications.js +95 -0
- package/dist/billing-foundation/consumer-protection/register-consent.js +2 -0
- package/dist/billing-foundation/consumer-protection/termination-mail.d.ts +20 -10
- package/dist/billing-foundation/consumer-protection/termination-mail.js +10 -21
- package/dist/billing-foundation/consumer-protection/termination-notify.js +2 -2
- package/dist/billing-foundation/consumer-protection/termination-pages.js +1 -3
- package/dist/billing-foundation/consumer-protection/termination-request.js +2 -2
- package/dist/billing-foundation/handlers/process-event.write.js +2 -2
- package/dist/billing-foundation/handlers/process-payment-event.write.js +1 -1
- package/dist/billing-foundation/subscription-tier-sync.js +3 -4
- package/dist/cap-overview/handlers/tenant-caps-list.query.js +2 -1
- package/dist/cap-overview/web/cap-usage-cell.js +5 -4
- package/dist/channel-email/email-channel.d.ts +2 -2
- package/dist/channel-email/email-channel.js +89 -26
- package/dist/channel-email/feature.js +3 -3
- package/dist/channel-email/index.d.ts +1 -1
- package/dist/channel-email/index.js +1 -1
- package/dist/channel-email/pii-guard.js +10 -0
- package/dist/channel-email/smtp-transport.js +13 -0
- package/dist/channel-email/types.d.ts +8 -0
- package/dist/config/handlers/set.write.js +1 -2
- package/dist/custom-fields/lib/value-schema.js +10 -11
- package/dist/custom-fields/wire-user-data-rights.js +7 -6
- package/dist/data-retention/index.d.ts +1 -0
- package/dist/data-retention/index.js +1 -0
- package/dist/data-retention/run-retention-cleanup.js +6 -6
- package/dist/delivery/attachment-ref.d.ts +39 -0
- package/dist/delivery/attachment-ref.js +64 -0
- package/dist/delivery/channel-context.d.ts +8 -4
- package/dist/delivery/channel-context.js +76 -2
- package/dist/delivery/constants.d.ts +1 -0
- package/dist/delivery/constants.js +1 -0
- package/dist/delivery/delivery-service.d.ts +4 -1
- package/dist/delivery/delivery-service.js +38 -32
- package/dist/delivery/feature.js +1 -1
- package/dist/delivery/index.d.ts +5 -2
- package/dist/delivery/index.js +4 -1
- package/dist/delivery/jobs.js +25 -5
- package/dist/delivery/notify-factory.js +4 -1
- package/dist/delivery/testing.d.ts +4 -1
- package/dist/delivery/types.d.ts +20 -14
- package/dist/delivery/unsubscribe.js +4 -1
- package/dist/derivatives-sharp/render.js +1 -18
- package/dist/document-ingest-foundation/pages.js +4 -4
- package/dist/feature-toggles/toggle-sync-signal.js +4 -4
- package/dist/file-foundation/index.d.ts +1 -0
- package/dist/file-foundation/index.js +1 -0
- package/dist/file-foundation/read-file-ref-content.d.ts +27 -0
- package/dist/file-foundation/read-file-ref-content.js +49 -0
- package/dist/folders/web/folder-manager.js +3 -2
- package/dist/form-draft/db/queries/cleanup.js +4 -4
- package/dist/form-draft/db/queries/draft-count.js +2 -2
- package/dist/form-draft/db/queries/owned-file-refs.js +2 -2
- package/dist/form-draft/release-file-refs.js +3 -2
- package/dist/inbound-mail-foundation/handlers/connect-account.write.js +1 -1
- package/dist/inbound-mail-foundation/handlers/ingest-message.write.js +6 -6
- package/dist/inbound-mail-foundation/handlers/scope-visibility.d.ts +1 -1
- package/dist/inbound-mail-foundation/oauth-state.js +7 -7
- package/dist/inbound-mail-foundation/types.js +4 -4
- package/dist/inbound-mail-foundation/watch-supervisor.js +22 -13
- package/dist/inbound-provider-imap/imap-client.js +4 -3
- package/dist/ledger/recurring.js +1 -3
- package/dist/managed-pages/branding.d.ts +2 -0
- package/dist/managed-pages/branding.js +24 -2
- package/dist/managed-pages/feature.js +1 -0
- package/dist/managed-pages/i18n.js +1 -0
- package/dist/managed-pages/screens/branding-screen.js +8 -1
- package/dist/page-render/branding.d.ts +1 -0
- package/dist/page-render/branding.js +1 -0
- package/dist/page-render/markdown.js +1 -1
- package/dist/personal-access-tokens/feature.js +1 -1
- package/dist/personal-access-tokens/handlers/list.query.js +2 -1
- package/dist/personal-access-tokens/revoke-for-user.js +2 -1
- package/dist/renderer-foundation/api.d.ts +13 -13
- package/dist/renderer-foundation/api.js +1 -1
- package/dist/renderer-foundation/constants.js +2 -4
- package/dist/renderer-foundation/feature.js +7 -7
- package/dist/renderer-foundation/index.d.ts +2 -1
- package/dist/renderer-foundation/index.js +1 -0
- package/dist/renderer-foundation/types.d.ts +92 -32
- package/dist/renderer-pdf/feature.js +7 -1
- package/dist/sessions/db/queries/cleanup.js +2 -2
- package/dist/sessions/feature.js +1 -1
- package/dist/sessions/handlers/list.query.js +2 -1
- package/dist/shared/attachment-file-name.d.ts +3 -0
- package/dist/shared/attachment-file-name.js +35 -0
- package/dist/shared/index.d.ts +3 -0
- package/dist/shared/index.js +3 -0
- package/dist/shared/is-tenant-db.js +1 -1
- package/dist/shared/mail-template-body.d.ts +4 -0
- package/dist/shared/mail-template-body.js +17 -0
- package/dist/shared/sharp-input-limits.d.ts +6 -0
- package/dist/shared/sharp-input-limits.js +22 -0
- package/dist/shared/signup-handover.js +2 -2
- package/dist/tags/handlers/assign-tag.write.js +3 -1
- package/dist/tags/web/tag-chip.js +2 -0
- package/dist/template-resolver/api.d.ts +30 -2
- package/dist/template-resolver/api.js +40 -5
- package/dist/template-resolver/compile-document-body.d.ts +8 -0
- package/dist/template-resolver/compile-document-body.js +163 -0
- package/dist/template-resolver/constants.d.ts +13 -4
- package/dist/template-resolver/constants.js +30 -10
- package/dist/template-resolver/document-sanitizer.d.ts +32 -0
- package/dist/template-resolver/document-sanitizer.js +201 -0
- package/dist/template-resolver/document-schema.d.ts +21 -0
- package/dist/template-resolver/document-schema.js +296 -0
- package/dist/template-resolver/feature.d.ts +18 -0
- package/dist/template-resolver/feature.js +50 -6
- package/dist/template-resolver/handlers/bundle-import.d.ts +20 -0
- package/dist/template-resolver/handlers/bundle-import.js +147 -0
- package/dist/template-resolver/handlers/collection-set.write.js +25 -2
- package/dist/template-resolver/handlers/copy-locale.write.d.ts +19 -0
- package/dist/template-resolver/handlers/copy-locale.write.js +116 -0
- package/dist/template-resolver/handlers/diff-to-system.query.d.ts +22 -0
- package/dist/template-resolver/handlers/diff-to-system.query.js +64 -0
- package/dist/template-resolver/handlers/export-bundle.query.d.ts +35 -0
- package/dist/template-resolver/handlers/export-bundle.query.js +68 -0
- package/dist/template-resolver/handlers/find-by-id.query.d.ts +1 -0
- package/dist/template-resolver/handlers/find-by-id.query.js +3 -3
- package/dist/template-resolver/handlers/import-bundle-system.write.d.ts +39 -0
- package/dist/template-resolver/handlers/import-bundle-system.write.js +36 -0
- package/dist/template-resolver/handlers/import-bundle.write.d.ts +40 -0
- package/dist/template-resolver/handlers/import-bundle.write.js +19 -0
- package/dist/template-resolver/handlers/layout-source.query.d.ts +26 -0
- package/dist/template-resolver/handlers/layout-source.query.js +64 -0
- package/dist/template-resolver/handlers/list.query.d.ts +4 -1
- package/dist/template-resolver/handlers/list.query.js +5 -7
- package/dist/template-resolver/handlers/overview.query.d.ts +59 -0
- package/dist/template-resolver/handlers/overview.query.js +165 -0
- package/dist/template-resolver/handlers/preview.query.d.ts +12 -0
- package/dist/template-resolver/handlers/preview.query.js +24 -0
- package/dist/template-resolver/handlers/reset-to-system.write.d.ts +8 -0
- package/dist/template-resolver/handlers/reset-to-system.write.js +50 -0
- package/dist/template-resolver/handlers/resource-link-pdf.write.d.ts +8 -0
- package/dist/template-resolver/handlers/resource-link-pdf.write.js +32 -0
- package/dist/template-resolver/handlers/resource-remove.write.d.ts +9 -0
- package/dist/template-resolver/handlers/resource-remove.write.js +44 -0
- package/dist/template-resolver/handlers/resource-shared.d.ts +9 -0
- package/dist/template-resolver/handlers/resource-shared.js +14 -0
- package/dist/template-resolver/handlers/resource-upload.write.d.ts +13 -0
- package/dist/template-resolver/handlers/resource-upload.write.js +98 -0
- package/dist/template-resolver/handlers/send-test.write.d.ts +11 -0
- package/dist/template-resolver/handlers/send-test.write.js +81 -0
- package/dist/template-resolver/handlers/set.write.d.ts +4 -1
- package/dist/template-resolver/handlers/set.write.js +25 -3
- package/dist/template-resolver/handlers/shared.d.ts +9 -5
- package/dist/template-resolver/handlers/shared.js +6 -2
- package/dist/template-resolver/handlers/template-gates.d.ts +36 -0
- package/dist/template-resolver/handlers/template-gates.js +131 -0
- package/dist/template-resolver/handlers/template-write.d.ts +45 -0
- package/dist/template-resolver/handlers/template-write.js +109 -0
- package/dist/template-resolver/handlers/tenant-overrides.query.d.ts +17 -0
- package/dist/template-resolver/handlers/tenant-overrides.query.js +48 -0
- package/dist/template-resolver/handlers/toggle-status.write.d.ts +12 -0
- package/dist/template-resolver/handlers/toggle-status.write.js +27 -11
- package/dist/template-resolver/handlers/upsert-system.write.d.ts +5 -8
- package/dist/template-resolver/handlers/upsert-system.write.js +14 -59
- package/dist/template-resolver/handlers/upsert-tenant.write.d.ts +5 -8
- package/dist/template-resolver/handlers/upsert-tenant.write.js +10 -61
- package/dist/template-resolver/handlers/usage.query.d.ts +17 -0
- package/dist/template-resolver/handlers/usage.query.js +15 -0
- package/dist/template-resolver/handlers/validate.query.d.ts +74 -0
- package/dist/template-resolver/handlers/validate.query.js +80 -0
- package/dist/template-resolver/index.d.ts +18 -1
- package/dist/template-resolver/index.js +16 -1
- package/dist/template-resolver/layout-regions.d.ts +23 -0
- package/dist/template-resolver/layout-regions.js +125 -0
- package/dist/template-resolver/linked-resources.d.ts +17 -0
- package/dist/template-resolver/linked-resources.js +75 -0
- package/dist/template-resolver/preview-display-html.d.ts +7 -0
- package/dist/template-resolver/preview-display-html.js +27 -0
- package/dist/template-resolver/print-document.d.ts +1 -0
- package/dist/template-resolver/print-document.js +55 -0
- package/dist/template-resolver/qualified-names.d.ts +30 -0
- package/dist/template-resolver/qualified-names.js +30 -0
- package/dist/template-resolver/record-mapping.d.ts +2 -0
- package/dist/template-resolver/record-mapping.js +21 -0
- package/dist/template-resolver/resource-image.d.ts +15 -0
- package/dist/template-resolver/resource-image.js +34 -0
- package/dist/template-resolver/seeding.d.ts +14 -1
- package/dist/template-resolver/seeding.js +105 -15
- package/dist/template-resolver/store-template-resource.d.ts +64 -0
- package/dist/template-resolver/store-template-resource.js +264 -0
- package/dist/template-resolver/table.d.ts +47 -8
- package/dist/template-resolver/table.js +11 -0
- package/dist/template-resolver/template-bundle.d.ts +36 -0
- package/dist/template-resolver/template-bundle.js +60 -0
- package/dist/template-resolver/template-diff.d.ts +7 -0
- package/dist/template-resolver/template-diff.js +68 -0
- package/dist/template-resolver/template-meta.d.ts +60 -0
- package/dist/template-resolver/template-meta.js +101 -0
- package/dist/template-resolver/template-preview.d.ts +86 -0
- package/dist/template-resolver/template-preview.js +374 -0
- package/dist/template-resolver/template-resources.d.ts +14 -0
- package/dist/template-resolver/template-resources.js +42 -0
- package/dist/template-resolver/template-usage.d.ts +12 -0
- package/dist/template-resolver/template-usage.js +30 -0
- package/dist/template-resolver/template-validation.d.ts +22 -0
- package/dist/template-resolver/template-validation.js +172 -0
- package/dist/template-resolver/testing.js +5 -5
- package/dist/template-resolver/user-content-table.d.ts +14 -8
- package/dist/template-resolver/web/client-plugin.d.ts +1 -0
- package/dist/template-resolver/web/client-plugin.js +9 -6
- package/dist/template-resolver/web/document-editor-extensions.d.ts +15 -0
- package/dist/template-resolver/web/document-editor-extensions.js +107 -0
- package/dist/template-resolver/web/document-editor.d.ts +3 -0
- package/dist/template-resolver/web/document-editor.js +11 -0
- package/dist/template-resolver/web/document-tiptap-editor.d.ts +3 -0
- package/dist/template-resolver/web/document-tiptap-editor.js +376 -0
- package/dist/template-resolver/web/document-toolbar-blocks.d.ts +33 -0
- package/dist/template-resolver/web/document-toolbar-blocks.js +15 -0
- package/dist/template-resolver/web/editor-handle.d.ts +3 -0
- package/dist/template-resolver/web/editor-handle.js +24 -0
- package/dist/template-resolver/web/i18n.js +76 -0
- package/dist/template-resolver/web/index.d.ts +4 -0
- package/dist/template-resolver/web/index.js +4 -0
- package/dist/template-resolver/web/placeholder-line-editor-lazy.d.ts +4 -0
- package/dist/template-resolver/web/placeholder-line-editor-lazy.js +16 -0
- package/dist/template-resolver/web/placeholder-line-editor.d.ts +15 -0
- package/dist/template-resolver/web/placeholder-line-editor.js +94 -0
- package/dist/template-resolver/web/placeholder-text.d.ts +4 -0
- package/dist/template-resolver/web/placeholder-text.js +34 -0
- package/dist/template-resolver/web/use-stable-by-key.d.ts +1 -0
- package/dist/template-resolver/web/use-stable-by-key.js +13 -0
- package/dist/tenant/handlers/team-list.query.js +3 -2
- package/dist/tenant/seeding.js +31 -6
- package/dist/tenant-lifecycle/handlers/cancel-destruction.write.d.ts +1 -1
- package/dist/tenant-lifecycle/handlers/cancel-destruction.write.js +2 -1
- package/dist/user/db/queries/stream-tenant-backfill.js +4 -4
- package/dist/user/handlers/detail.query.js +2 -1
- package/dist/user-data-rights/db/queries/forget-cleanup.d.ts +2 -2
- package/dist/user-data-rights/email-templates.d.ts +1 -29
- package/dist/user-data-rights/email-templates.js +5 -106
- package/dist/user-data-rights/feature.js +53 -35
- package/dist/user-data-rights/handlers/cancel-deletion.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/cancel-deletion.write.js +2 -1
- package/dist/user-data-rights/handlers/request-deletion.write.js +5 -7
- package/dist/user-data-rights/lib/default-mailers.d.ts +15 -7
- package/dist/user-data-rights/lib/default-mailers.js +100 -48
- package/dist/user-data-rights/lib/update-user-lifecycle.js +1 -1
- package/dist/user-data-rights/mail-notifications.d.ts +13 -0
- package/dist/user-data-rights/mail-notifications.js +118 -0
- package/dist/user-data-rights/web/privacy-center-screen.js +2 -1
- package/dist/user-profile/web/profile-screen.js +2 -1
- package/dist/waitlist/feature.js +10 -0
- package/dist/waitlist/handlers/submit.write.js +3 -4
- package/dist/waitlist/mail.d.ts +19 -6
- package/dist/waitlist/mail.js +50 -14
- package/package.json +28 -17
- package/src/admin-shell/changes.json +6 -0
- package/src/auth-email-password/changes.json +14 -0
- package/src/billing-foundation/changes.json +7 -0
- package/src/channel-email/changes.json +19 -0
- package/src/data-retention/changes.json +6 -0
- package/src/delivery/changes.json +48 -0
- package/src/managed-pages/changes.json +6 -0
- package/src/renderer-foundation/changes.json +39 -0
- package/src/template-resolver/changes.json +118 -0
- package/src/user-data-rights/changes.json +7 -0
- package/src/waitlist/changes.json +7 -0
- package/dist/renderer-simple/feature.d.ts +0 -10
- package/dist/renderer-simple/feature.js +0 -42
- package/dist/renderer-simple/index.d.ts +0 -3
- package/dist/renderer-simple/index.js +0 -2
- package/dist/renderer-simple/mail-blocks.d.ts +0 -21
- package/dist/renderer-simple/mail-blocks.js +0 -107
- package/dist/renderer-simple/resolve-variables.d.ts +0 -7
- package/dist/renderer-simple/resolve-variables.js +0 -47
- package/dist/renderer-simple/simple-renderer.d.ts +0 -24
- package/dist/renderer-simple/simple-renderer.js +0 -200
- package/src/renderer-simple/changes.json +0 -27
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { defineWriteHandler, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { ValidationError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
3
|
+
import * as z from "zod";
|
|
4
|
+
import { templateBundleSchema } from "../template-bundle.js";
|
|
5
|
+
import { importTemplateBundle } from "./bundle-import.js";
|
|
6
|
+
export const importBundleSystemWrite = defineWriteHandler({
|
|
7
|
+
name: "import-bundle-system",
|
|
8
|
+
schema: z.object({ bundle: templateBundleSchema }),
|
|
9
|
+
access: { roles: ["SystemAdmin"] },
|
|
10
|
+
// A system template change reaches every tenant without an override.
|
|
11
|
+
agent: { risk: "high" },
|
|
12
|
+
description: "Imports a template bundle as system-default templates, active straight away and overwriting existing system rows; SystemAdmin only. Bundles with resource files need a caller in the system tenant, because the files are stored in the caller's file space.",
|
|
13
|
+
handler: (event, ctx) => {
|
|
14
|
+
const { bundle } = event.payload;
|
|
15
|
+
if (Object.keys(bundle.resources).length > 0 && event.user.tenantId !== SYSTEM_TENANT_ID) {
|
|
16
|
+
return Promise.resolve(writeFailure(new ValidationError({
|
|
17
|
+
fields: [
|
|
18
|
+
{
|
|
19
|
+
path: "bundle.resources",
|
|
20
|
+
code: "system_resources_need_system_tenant",
|
|
21
|
+
i18nKey: "templateResolver.errors.template.systemResourcesNeedSystemTenant",
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
})));
|
|
25
|
+
}
|
|
26
|
+
return importTemplateBundle({
|
|
27
|
+
ctx,
|
|
28
|
+
user: event.user,
|
|
29
|
+
tenantId: SYSTEM_TENANT_ID,
|
|
30
|
+
scope: "system",
|
|
31
|
+
status: "active",
|
|
32
|
+
bundle,
|
|
33
|
+
overwrite: true,
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
export declare const importBundleWrite: import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"import-bundle", z.ZodObject<{
|
|
3
|
+
bundle: z.ZodObject<{
|
|
4
|
+
slug: z.ZodString;
|
|
5
|
+
kind: z.ZodEnum<{
|
|
6
|
+
"ai-prompt": "ai-prompt";
|
|
7
|
+
"document-pdf": "document-pdf";
|
|
8
|
+
"image-snapshot": "image-snapshot";
|
|
9
|
+
mail: "mail";
|
|
10
|
+
"mail-layout": "mail-layout";
|
|
11
|
+
"pdf-layout": "pdf-layout";
|
|
12
|
+
}>;
|
|
13
|
+
contentFormat: z.ZodEnum<{
|
|
14
|
+
html: "html";
|
|
15
|
+
markdown: "markdown";
|
|
16
|
+
mjml: "mjml";
|
|
17
|
+
plain: "plain";
|
|
18
|
+
}>;
|
|
19
|
+
variableSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
20
|
+
locales: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
21
|
+
body: z.ZodString;
|
|
22
|
+
meta: z.ZodOptional<z.ZodUnknown>;
|
|
23
|
+
}, z.core.$strict>>;
|
|
24
|
+
resources: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
25
|
+
fileName: z.ZodString;
|
|
26
|
+
contentType: z.ZodString;
|
|
27
|
+
base64: z.ZodString;
|
|
28
|
+
purpose: z.ZodOptional<z.ZodEnum<{
|
|
29
|
+
attachment: "attachment";
|
|
30
|
+
}>>;
|
|
31
|
+
}, z.core.$strict>>>;
|
|
32
|
+
pdfAttachments: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
33
|
+
templateSlug: z.ZodString;
|
|
34
|
+
fileName: z.ZodString;
|
|
35
|
+
}, z.core.$strict>>>;
|
|
36
|
+
}, z.core.$strict>;
|
|
37
|
+
overwrite: z.ZodDefault<z.ZodBoolean>;
|
|
38
|
+
}, z.core.$strip>, {
|
|
39
|
+
ids: Record<string, string>;
|
|
40
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { templateBundleSchema } from "../template-bundle.js";
|
|
4
|
+
import { importTemplateBundle } from "./bundle-import.js";
|
|
5
|
+
export const importBundleWrite = defineWriteHandler({
|
|
6
|
+
name: "import-bundle",
|
|
7
|
+
schema: z.object({ bundle: templateBundleSchema, overwrite: z.boolean().default(false) }),
|
|
8
|
+
access: { roles: ["TenantAdmin", "SystemAdmin"] },
|
|
9
|
+
description: "Imports a template bundle (all locales plus resource files, as produced by export-bundle) into the caller's tenant as drafts; it refuses a locale that already has an override unless overwrite is set, and publish makes the drafts live.",
|
|
10
|
+
handler: (event, ctx) => importTemplateBundle({
|
|
11
|
+
ctx,
|
|
12
|
+
user: event.user,
|
|
13
|
+
tenantId: event.user.tenantId,
|
|
14
|
+
scope: "tenant",
|
|
15
|
+
status: "draft",
|
|
16
|
+
bundle: event.payload.bundle,
|
|
17
|
+
overwrite: event.payload.overwrite,
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
import { type LayoutRegions } from "../layout-regions.js";
|
|
3
|
+
export type LayoutSourceRow = {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly version: number;
|
|
6
|
+
readonly status: string;
|
|
7
|
+
readonly locale: string;
|
|
8
|
+
};
|
|
9
|
+
export type LayoutSourceResponse = {
|
|
10
|
+
readonly tenant: LayoutSourceRow | null;
|
|
11
|
+
readonly system: (LayoutSourceRow & {
|
|
12
|
+
readonly contentFormat: string;
|
|
13
|
+
}) | null;
|
|
14
|
+
readonly regions: LayoutRegions;
|
|
15
|
+
readonly effectiveFormat: "html" | "mjml" | null;
|
|
16
|
+
readonly mjmlSource: string | null;
|
|
17
|
+
readonly locale: string;
|
|
18
|
+
};
|
|
19
|
+
export declare const layoutSourceQuery: import("@cosmicdrift/kumiko-types/define-handler").QueryHandlerDefinition<"layout-source", z.ZodObject<{
|
|
20
|
+
kind: z.ZodEnum<{
|
|
21
|
+
"mail-layout": "mail-layout";
|
|
22
|
+
"pdf-layout": "pdf-layout";
|
|
23
|
+
}>;
|
|
24
|
+
slug: z.ZodDefault<z.ZodString>;
|
|
25
|
+
locale: z.ZodOptional<z.ZodString>;
|
|
26
|
+
}, z.core.$strip>, LayoutSourceResponse, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
|
+
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import * as z from "zod";
|
|
4
|
+
import { FALLBACK_LOCALE } from "../constants.js";
|
|
5
|
+
import { readLayoutRegions } from "../layout-regions.js";
|
|
6
|
+
import { templateResourcesTable } from "../table.js";
|
|
7
|
+
import { localeSchema, templateSlugSchema } from "./shared.js";
|
|
8
|
+
const EMPTY_REGIONS = { header: "", signature: "", footer: "" };
|
|
9
|
+
function pickByLocale(rows, requested) {
|
|
10
|
+
const live = rows.filter((row) => row.status !== "archived");
|
|
11
|
+
const candidates = live.length > 0 ? live : rows;
|
|
12
|
+
return (candidates.find((row) => row.locale === requested) ??
|
|
13
|
+
candidates.find((row) => row.locale === FALLBACK_LOCALE) ??
|
|
14
|
+
candidates[0] ??
|
|
15
|
+
null);
|
|
16
|
+
}
|
|
17
|
+
function toSourceRow(row) {
|
|
18
|
+
return { id: String(row.id), version: row.version, status: row.status, locale: row.locale };
|
|
19
|
+
}
|
|
20
|
+
function layoutFormat(row) {
|
|
21
|
+
return row.contentFormat === "html" || row.contentFormat === "mjml" ? row.contentFormat : null;
|
|
22
|
+
}
|
|
23
|
+
function pickEditedRows(rows, isSystemAdmin, locale) {
|
|
24
|
+
const tenantRow = isSystemAdmin
|
|
25
|
+
? null
|
|
26
|
+
: pickByLocale(rows.filter((row) => row.scope === "tenant"), locale);
|
|
27
|
+
const systemRow = pickByLocale(rows.filter((row) => row.scope === "system"), locale);
|
|
28
|
+
return { tenantRow, systemRow };
|
|
29
|
+
}
|
|
30
|
+
export const layoutSourceQuery = defineQueryHandler({
|
|
31
|
+
name: "layout-source",
|
|
32
|
+
schema: z.object({
|
|
33
|
+
kind: z.enum(["mail-layout", "pdf-layout"]),
|
|
34
|
+
slug: templateSlugSchema.default("default"),
|
|
35
|
+
locale: localeSchema.optional(),
|
|
36
|
+
}),
|
|
37
|
+
access: { roles: ["TenantAdmin", "SystemAdmin"] },
|
|
38
|
+
description: "Reads the layout the caller edits: the tenant's own version and the system version of one layout slug, the header, signature and footer regions of the effective html version, and the locale a save has to use; a SystemAdmin edits the system version and also receives its mjml source. Use it to load the layout editor.",
|
|
39
|
+
handler: async (query, ctx) => {
|
|
40
|
+
const { kind, slug, locale } = query.payload;
|
|
41
|
+
const isSystemAdmin = query.user.roles.includes("SystemAdmin");
|
|
42
|
+
const rows = await selectMany(ctx.db, templateResourcesTable, {
|
|
43
|
+
kind,
|
|
44
|
+
slug,
|
|
45
|
+
});
|
|
46
|
+
const { tenantRow, systemRow } = pickEditedRows(rows, isSystemAdmin, locale);
|
|
47
|
+
const effective = tenantRow ?? systemRow;
|
|
48
|
+
const effectiveFormat = effective === null ? null : layoutFormat(effective);
|
|
49
|
+
return {
|
|
50
|
+
tenant: tenantRow === null ? null : toSourceRow(tenantRow),
|
|
51
|
+
system: systemRow === null
|
|
52
|
+
? null
|
|
53
|
+
: { ...toSourceRow(systemRow), contentFormat: systemRow.contentFormat },
|
|
54
|
+
regions: effective !== null && effectiveFormat === "html"
|
|
55
|
+
? readLayoutRegions(effective.content ?? "")
|
|
56
|
+
: EMPTY_REGIONS,
|
|
57
|
+
effectiveFormat,
|
|
58
|
+
mjmlSource: isSystemAdmin && systemRow !== null && layoutFormat(systemRow) === "mjml"
|
|
59
|
+
? (systemRow.content ?? "")
|
|
60
|
+
: null,
|
|
61
|
+
locale: effective?.locale ?? locale ?? FALLBACK_LOCALE,
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
});
|
|
@@ -4,8 +4,11 @@ export declare const listQuery: import("@cosmicdrift/kumiko-types/define-handler
|
|
|
4
4
|
"ai-prompt": "ai-prompt";
|
|
5
5
|
"document-pdf": "document-pdf";
|
|
6
6
|
"image-snapshot": "image-snapshot";
|
|
7
|
-
|
|
7
|
+
mail: "mail";
|
|
8
|
+
"mail-layout": "mail-layout";
|
|
8
9
|
notification: "notification";
|
|
10
|
+
"pdf-layout": "pdf-layout";
|
|
11
|
+
snippet: "snippet";
|
|
9
12
|
"text-block": "text-block";
|
|
10
13
|
}>>;
|
|
11
14
|
locale: z.ZodOptional<z.ZodString>;
|
|
@@ -3,9 +3,8 @@ import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
|
3
3
|
import * as z from "zod";
|
|
4
4
|
import { TEMPLATE_KINDS, TEMPLATE_STATUSES } from "../constants.js";
|
|
5
5
|
import { templateResourcesTable } from "../table.js";
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// Spalten mit scope-marker. SystemAdmin sieht alle (auch andere Tenants).
|
|
6
|
+
// ctx.db is tenant-scoped even for SystemAdmin: every caller sees its own
|
|
7
|
+
// tenant's rows plus the system defaults, never another tenant's rows.
|
|
9
8
|
export const listQuery = defineQueryHandler({
|
|
10
9
|
name: "list",
|
|
11
10
|
schema: z.object({
|
|
@@ -18,10 +17,9 @@ export const listQuery = defineQueryHandler({
|
|
|
18
17
|
description: "Lists template resources of the caller's tenant alongside the system defaults as summaries without content, filterable by kind, locale and status; use it to browse the catalogue, then find-by-id to read one row.",
|
|
19
18
|
handler: async (query, ctx) => {
|
|
20
19
|
const where = {};
|
|
21
|
-
// includeSystem=false narrows to own tenant AT THE DB —
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// the DB keeps the limit meaningful (no post-filter starvation).
|
|
20
|
+
// includeSystem=false narrows to own tenant AT THE DB — TenantDb permits
|
|
21
|
+
// narrowing within its enforced [own, SYSTEM] scope. Filtering at the DB
|
|
22
|
+
// keeps the limit meaningful (no post-filter starvation).
|
|
25
23
|
if (!query.payload.includeSystem) {
|
|
26
24
|
where["tenantId"] = query.user.tenantId;
|
|
27
25
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
import { type TemplateStatus } from "../constants.js";
|
|
3
|
+
declare const OVERVIEW_KINDS: readonly ["mail", "document-pdf", "mail-layout", "pdf-layout"];
|
|
4
|
+
type OverviewKind = (typeof OVERVIEW_KINDS)[number];
|
|
5
|
+
export type OverviewOrigin = "tenant" | "system";
|
|
6
|
+
export type OverviewKindGroup = "mail" | "pdf" | "layout";
|
|
7
|
+
export type OverviewUsage = {
|
|
8
|
+
readonly qualifiedName: string;
|
|
9
|
+
readonly feature: string;
|
|
10
|
+
readonly notification: string;
|
|
11
|
+
readonly trigger: string | null;
|
|
12
|
+
};
|
|
13
|
+
export type OverviewRow = {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly slug: string;
|
|
16
|
+
readonly kind: OverviewKind;
|
|
17
|
+
readonly kindGroup: OverviewKindGroup;
|
|
18
|
+
readonly title: string;
|
|
19
|
+
readonly origin: OverviewOrigin;
|
|
20
|
+
readonly locales: readonly string[];
|
|
21
|
+
readonly status: TemplateStatus;
|
|
22
|
+
readonly version: number;
|
|
23
|
+
readonly systemVersion: number | null;
|
|
24
|
+
readonly layoutSlug: string | null;
|
|
25
|
+
readonly modifiedAt: string;
|
|
26
|
+
readonly usedBy: readonly OverviewUsage[];
|
|
27
|
+
readonly usedByCount: number;
|
|
28
|
+
};
|
|
29
|
+
export type OverviewResponse = {
|
|
30
|
+
readonly rows: readonly OverviewRow[];
|
|
31
|
+
readonly nextCursor: null;
|
|
32
|
+
readonly total: number;
|
|
33
|
+
};
|
|
34
|
+
export declare const overviewQuery: import("@cosmicdrift/kumiko-types/define-handler").QueryHandlerDefinition<"overview", z.ZodObject<{
|
|
35
|
+
search: z.ZodOptional<z.ZodString>;
|
|
36
|
+
filters: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
37
|
+
field: z.ZodEnum<{
|
|
38
|
+
kind: "kind";
|
|
39
|
+
kindGroup: "kindGroup";
|
|
40
|
+
origin: "origin";
|
|
41
|
+
status: "status";
|
|
42
|
+
}>;
|
|
43
|
+
op: z.ZodLiteral<"in">;
|
|
44
|
+
value: z.ZodArray<z.ZodString>;
|
|
45
|
+
}, z.core.$strip>>>;
|
|
46
|
+
sort: z.ZodOptional<z.ZodString>;
|
|
47
|
+
sortDirection: z.ZodOptional<z.ZodEnum<{
|
|
48
|
+
asc: "asc";
|
|
49
|
+
desc: "desc";
|
|
50
|
+
}>>;
|
|
51
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
52
|
+
offset: z.ZodOptional<z.ZodNumber>;
|
|
53
|
+
totalCount: z.ZodOptional<z.ZodBoolean>;
|
|
54
|
+
}, z.core.$strip>, {
|
|
55
|
+
rows: OverviewRow[];
|
|
56
|
+
nextCursor: null;
|
|
57
|
+
total: number;
|
|
58
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
59
|
+
export {};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
|
+
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import * as z from "zod";
|
|
4
|
+
import { DEFAULT_MAIL_LAYOUT_SLUG, FALLBACK_LOCALE } from "../constants.js";
|
|
5
|
+
import { templateResourcesTable } from "../table.js";
|
|
6
|
+
import { collectTemplateUsage } from "../template-usage.js";
|
|
7
|
+
import { storedRecord } from "./template-gates.js";
|
|
8
|
+
const OVERVIEW_KINDS = ["mail", "document-pdf", "mail-layout", "pdf-layout"];
|
|
9
|
+
const OVERVIEW_ROW_LIMIT = 5000;
|
|
10
|
+
const SORT_FIELDS = ["title", "slug", "kind", "origin", "status", "modifiedAt"];
|
|
11
|
+
const KIND_GROUP = {
|
|
12
|
+
mail: "mail",
|
|
13
|
+
"document-pdf": "pdf",
|
|
14
|
+
"mail-layout": "layout",
|
|
15
|
+
"pdf-layout": "layout",
|
|
16
|
+
};
|
|
17
|
+
function isOverviewKind(kind) {
|
|
18
|
+
return OVERVIEW_KINDS.some((candidate) => candidate === kind);
|
|
19
|
+
}
|
|
20
|
+
function isTemplateStatus(status) {
|
|
21
|
+
return status === "draft" || status === "active" || status === "archived";
|
|
22
|
+
}
|
|
23
|
+
function layoutSlugOf(row) {
|
|
24
|
+
const layoutSlug = storedRecord(row.meta)["layoutSlug"];
|
|
25
|
+
return typeof layoutSlug === "string" ? layoutSlug : DEFAULT_MAIL_LAYOUT_SLUG;
|
|
26
|
+
}
|
|
27
|
+
function preferredRow(rows) {
|
|
28
|
+
return (rows.find((row) => row.locale === FALLBACK_LOCALE) ??
|
|
29
|
+
[...rows].sort((a, b) => a.locale.localeCompare(b.locale))[0]);
|
|
30
|
+
}
|
|
31
|
+
function effectiveRowsOf(rows) {
|
|
32
|
+
const tenantRows = rows.filter((row) => row.scope === "tenant");
|
|
33
|
+
const systemRows = rows.filter((row) => row.scope === "system");
|
|
34
|
+
const live = (list) => list.filter((row) => row.status !== "archived");
|
|
35
|
+
const liveTenant = live(tenantRows);
|
|
36
|
+
if (liveTenant.length > 0)
|
|
37
|
+
return { origin: "tenant", rows: liveTenant };
|
|
38
|
+
if (systemRows.length > 0) {
|
|
39
|
+
const liveSystem = live(systemRows);
|
|
40
|
+
return { origin: "system", rows: liveSystem.length > 0 ? liveSystem : systemRows };
|
|
41
|
+
}
|
|
42
|
+
return tenantRows.length > 0 ? { origin: "tenant", rows: tenantRows } : null;
|
|
43
|
+
}
|
|
44
|
+
function modifiedAtOf(row) {
|
|
45
|
+
return (row.modifiedAt ?? row.insertedAt).toString();
|
|
46
|
+
}
|
|
47
|
+
function splitDocumentSlug(slug) {
|
|
48
|
+
const separator = slug.indexOf(":");
|
|
49
|
+
return separator === -1
|
|
50
|
+
? { feature: "", notification: slug }
|
|
51
|
+
: { feature: slug.slice(0, separator), notification: slug.slice(separator + 1) };
|
|
52
|
+
}
|
|
53
|
+
function compareBy(field, a, b) {
|
|
54
|
+
return String(a[field]).localeCompare(String(b[field]));
|
|
55
|
+
}
|
|
56
|
+
function isSortField(value) {
|
|
57
|
+
return SORT_FIELDS.some((candidate) => candidate === value);
|
|
58
|
+
}
|
|
59
|
+
function groupBySlugAndKind(rows) {
|
|
60
|
+
const groups = new Map();
|
|
61
|
+
for (const row of rows) {
|
|
62
|
+
if (!isOverviewKind(row.kind))
|
|
63
|
+
continue;
|
|
64
|
+
const key = `${row.kind}|${row.slug}`;
|
|
65
|
+
groups.set(key, [...(groups.get(key) ?? []), row]);
|
|
66
|
+
}
|
|
67
|
+
return groups;
|
|
68
|
+
}
|
|
69
|
+
function buildDrafts(groups) {
|
|
70
|
+
const layoutSlugByDocument = new Map();
|
|
71
|
+
const drafts = [];
|
|
72
|
+
for (const [id, groupRows] of groups) {
|
|
73
|
+
const effective = effectiveRowsOf(groupRows);
|
|
74
|
+
const preferred = effective === null ? undefined : preferredRow(effective.rows);
|
|
75
|
+
if (effective === null || preferred === undefined)
|
|
76
|
+
continue;
|
|
77
|
+
if (!isOverviewKind(preferred.kind) || !isTemplateStatus(preferred.status))
|
|
78
|
+
continue;
|
|
79
|
+
const systemSameLocale = groupRows.find((row) => row.scope === "system" && row.locale === preferred.locale);
|
|
80
|
+
const isDocument = preferred.kind === "mail" || preferred.kind === "document-pdf";
|
|
81
|
+
const layoutSlug = isDocument ? layoutSlugOf(preferred) : null;
|
|
82
|
+
if (layoutSlug !== null && preferred.kind === "document-pdf") {
|
|
83
|
+
layoutSlugByDocument.set(preferred.slug, layoutSlug);
|
|
84
|
+
}
|
|
85
|
+
drafts.push({
|
|
86
|
+
id,
|
|
87
|
+
slug: preferred.slug,
|
|
88
|
+
kind: preferred.kind,
|
|
89
|
+
kindGroup: KIND_GROUP[preferred.kind],
|
|
90
|
+
title: preferred.title ?? preferred.slug,
|
|
91
|
+
origin: effective.origin,
|
|
92
|
+
locales: [...new Set(effective.rows.map((row) => row.locale))].sort(),
|
|
93
|
+
status: preferred.status,
|
|
94
|
+
version: preferred.version,
|
|
95
|
+
systemVersion: systemSameLocale?.version ?? null,
|
|
96
|
+
layoutSlug,
|
|
97
|
+
modifiedAt: modifiedAtOf(preferred),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return { drafts, layoutSlugByDocument };
|
|
101
|
+
}
|
|
102
|
+
function withUsage(draft, layoutSlugByDocument, registry) {
|
|
103
|
+
let usedBy = [];
|
|
104
|
+
if (draft.kind === "mail" || draft.kind === "mail-layout") {
|
|
105
|
+
usedBy = collectTemplateUsage(registry, draft.slug, draft.kind).map((entry) => ({
|
|
106
|
+
qualifiedName: entry.qualifiedName,
|
|
107
|
+
feature: entry.feature,
|
|
108
|
+
notification: entry.notification,
|
|
109
|
+
trigger: entry.trigger,
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
else if (draft.kind === "pdf-layout") {
|
|
113
|
+
usedBy = [...layoutSlugByDocument]
|
|
114
|
+
.filter(([, layoutSlug]) => layoutSlug === draft.slug)
|
|
115
|
+
.map(([documentSlug]) => ({
|
|
116
|
+
qualifiedName: documentSlug,
|
|
117
|
+
...splitDocumentSlug(documentSlug),
|
|
118
|
+
trigger: null,
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
return { ...draft, usedBy, usedByCount: usedBy.length };
|
|
122
|
+
}
|
|
123
|
+
function filterRows(rows, search, filters) {
|
|
124
|
+
const needle = search?.trim().toLowerCase() ?? "";
|
|
125
|
+
return rows.filter((row) => {
|
|
126
|
+
if (needle !== "" && !`${row.title} ${row.slug}`.toLowerCase().includes(needle))
|
|
127
|
+
return false;
|
|
128
|
+
return filters.every((filter) => filter.value.includes(String(row[filter.field])));
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const filterSchema = z.object({
|
|
132
|
+
field: z.enum(["kindGroup", "kind", "origin", "status"]),
|
|
133
|
+
op: z.literal("in"),
|
|
134
|
+
value: z.array(z.string()),
|
|
135
|
+
});
|
|
136
|
+
export const overviewQuery = defineQueryHandler({
|
|
137
|
+
name: "overview",
|
|
138
|
+
schema: z.object({
|
|
139
|
+
search: z.string().optional(),
|
|
140
|
+
filters: z.array(filterSchema).optional(),
|
|
141
|
+
sort: z.string().optional(),
|
|
142
|
+
sortDirection: z.enum(["asc", "desc"]).optional(),
|
|
143
|
+
limit: z.number().int().min(1).max(200).default(50),
|
|
144
|
+
offset: z.number().int().min(0).optional(),
|
|
145
|
+
totalCount: z.boolean().optional(),
|
|
146
|
+
}),
|
|
147
|
+
access: { roles: ["TenantAdmin", "SystemAdmin"] },
|
|
148
|
+
description: "Lists mail, PDF and layout templates as one row per slug and kind for the caller's tenant: where the effective version comes from (own override or system), its locales, status and version, the system version it overrides, the layout it uses and which notifications or templates use it; use it to browse and filter all templates at a glance.",
|
|
149
|
+
handler: async (query, ctx) => {
|
|
150
|
+
const payload = query.payload;
|
|
151
|
+
const rows = await selectMany(ctx.db, templateResourcesTable, { kind: { in: [...OVERVIEW_KINDS] } }, { limit: OVERVIEW_ROW_LIMIT });
|
|
152
|
+
const { drafts, layoutSlugByDocument } = buildDrafts(groupBySlugAndKind(rows));
|
|
153
|
+
const overviewRows = drafts.map((draft) => withUsage(draft, layoutSlugByDocument, ctx.registry));
|
|
154
|
+
const filtered = filterRows(overviewRows, payload.search, payload.filters ?? []);
|
|
155
|
+
const sortField = payload.sort !== undefined && isSortField(payload.sort) ? payload.sort : "modifiedAt";
|
|
156
|
+
const direction = (payload.sortDirection ?? "desc") === "desc" ? -1 : 1;
|
|
157
|
+
filtered.sort((a, b) => direction * compareBy(sortField, a, b) || a.id.localeCompare(b.id));
|
|
158
|
+
const offset = payload.offset ?? 0;
|
|
159
|
+
return {
|
|
160
|
+
rows: filtered.slice(offset, offset + payload.limit),
|
|
161
|
+
nextCursor: null,
|
|
162
|
+
total: filtered.length,
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
export declare const previewQuery: import("@cosmicdrift/kumiko-types/define-handler").QueryHandlerDefinition<"preview", z.ZodObject<{
|
|
3
|
+
slug: z.ZodString;
|
|
4
|
+
kind: z.ZodEnum<{
|
|
5
|
+
"document-pdf": "document-pdf";
|
|
6
|
+
mail: "mail";
|
|
7
|
+
}>;
|
|
8
|
+
locale: z.ZodString;
|
|
9
|
+
content: z.ZodOptional<z.ZodString>;
|
|
10
|
+
meta: z.ZodOptional<z.ZodUnknown>;
|
|
11
|
+
variableSchema: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
12
|
+
}, z.core.$strip>, import("../template-preview.js").TemplatePreview, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { renderTemplatePreview } from "../template-preview.js";
|
|
4
|
+
import { localeSchema, templateSlugSchema } from "./shared.js";
|
|
5
|
+
export const previewQuery = defineQueryHandler({
|
|
6
|
+
name: "preview",
|
|
7
|
+
schema: z.object({
|
|
8
|
+
slug: templateSlugSchema,
|
|
9
|
+
kind: z.enum(["mail", "document-pdf"]),
|
|
10
|
+
locale: localeSchema,
|
|
11
|
+
/** Without content the stored template is previewed. */
|
|
12
|
+
content: z.string().max(200_000).optional(),
|
|
13
|
+
meta: z.unknown().optional(),
|
|
14
|
+
variableSchema: z.record(z.string(), z.unknown()).default({}),
|
|
15
|
+
}),
|
|
16
|
+
access: { roles: ["TenantAdmin", "SystemAdmin"] },
|
|
17
|
+
description: "Renders a template draft, or the stored template when no content is given, with sample values taken from the declared variable examples, without saving or sending anything: returns the mail subject, html and plain text with its inline images (a PDF template returns its body and layout regions instead), the sample values used, and checks for unresolved images, missing plain text, mail size and unused required placeholders; a draft that fails the write rules is rejected with the same problems as a save.",
|
|
18
|
+
handler: async (query, ctx) => {
|
|
19
|
+
const outcome = await renderTemplatePreview(ctx, query.user, query.payload);
|
|
20
|
+
if (!outcome.ok)
|
|
21
|
+
throw outcome.error;
|
|
22
|
+
return outcome.preview;
|
|
23
|
+
},
|
|
24
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
export declare const resetToSystemWrite: import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"reset-to-system", z.ZodObject<{
|
|
3
|
+
id: z.ZodString;
|
|
4
|
+
}, z.core.$strip>, {
|
|
5
|
+
id: string;
|
|
6
|
+
status: "archived";
|
|
7
|
+
systemTemplateId: string;
|
|
8
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
|
+
import { defineWriteHandler, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { NotFoundError, ValidationError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
4
|
+
import * as z from "zod";
|
|
5
|
+
import { templateResourcesTable } from "../table.js";
|
|
6
|
+
import { executor } from "./shared.js";
|
|
7
|
+
export const resetToSystemWrite = defineWriteHandler({
|
|
8
|
+
name: "reset-to-system",
|
|
9
|
+
schema: z.object({ id: z.string().min(1) }),
|
|
10
|
+
access: { roles: ["TenantAdmin", "SystemAdmin"] },
|
|
11
|
+
description: "Retires a tenant override by archiving it so the resolver serves the active system template again; the override and its content survive and can be published again. Refused when there is no active system template to fall back to.",
|
|
12
|
+
handler: async (event, ctx) => {
|
|
13
|
+
const existing = await fetchOne(ctx.db, templateResourcesTable, {
|
|
14
|
+
id: event.payload.id,
|
|
15
|
+
});
|
|
16
|
+
if (!existing || existing.tenantId !== event.user.tenantId || existing.scope !== "tenant") {
|
|
17
|
+
return writeFailure(new NotFoundError("template-resource", event.payload.id));
|
|
18
|
+
}
|
|
19
|
+
const systemRow = await fetchOne(ctx.db, templateResourcesTable, {
|
|
20
|
+
tenantId: SYSTEM_TENANT_ID,
|
|
21
|
+
slug: existing.slug,
|
|
22
|
+
kind: existing.kind,
|
|
23
|
+
locale: existing.locale,
|
|
24
|
+
status: "active",
|
|
25
|
+
});
|
|
26
|
+
if (!systemRow) {
|
|
27
|
+
return writeFailure(new ValidationError({
|
|
28
|
+
fields: [
|
|
29
|
+
{
|
|
30
|
+
path: "id",
|
|
31
|
+
code: "no_system_template",
|
|
32
|
+
i18nKey: "templateResolver.errors.noSystemTemplate",
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
const response = {
|
|
38
|
+
isSuccess: true,
|
|
39
|
+
data: {
|
|
40
|
+
id: String(existing.id),
|
|
41
|
+
status: "archived",
|
|
42
|
+
systemTemplateId: String(systemRow.id),
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
if (existing.status === "archived")
|
|
46
|
+
return response;
|
|
47
|
+
const result = await executor.update({ id: existing.id, version: existing.version, changes: { status: "archived" } }, event.user, ctx.db);
|
|
48
|
+
return result.isSuccess ? response : result;
|
|
49
|
+
},
|
|
50
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
export declare const resourceLinkPdfWrite: import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"resource-link-pdf", z.ZodObject<{
|
|
3
|
+
templateId: z.ZodString;
|
|
4
|
+
key: z.ZodString;
|
|
5
|
+
templateSlug: z.ZodString;
|
|
6
|
+
fileName: z.ZodString;
|
|
7
|
+
tenantIdOverride: z.ZodOptional<z.ZodString>;
|
|
8
|
+
}, z.core.$strip>, import("../store-template-resource.js").LinkedTemplatePdfAttachment, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { linkTemplatePdfAttachment } from "../store-template-resource.js";
|
|
4
|
+
import { resourceKeySchema, resourceTargetTenantId } from "./resource-shared.js";
|
|
5
|
+
export const resourceLinkPdfWrite = defineWriteHandler({
|
|
6
|
+
name: "resource-link-pdf",
|
|
7
|
+
schema: z.object({
|
|
8
|
+
templateId: z.string().min(1),
|
|
9
|
+
key: resourceKeySchema,
|
|
10
|
+
/** Slug of a document-pdf template of the tenant or the system. */
|
|
11
|
+
templateSlug: z.string().min(1),
|
|
12
|
+
/** Name pattern of the attached PDF; may hold {{placeholders}}, must end with .pdf. */
|
|
13
|
+
fileName: z.string().min(1),
|
|
14
|
+
/** Cross-tenant target — SystemAdmin only. */
|
|
15
|
+
tenantIdOverride: z.string().min(1).optional(),
|
|
16
|
+
}),
|
|
17
|
+
access: { roles: ["TenantAdmin", "SystemAdmin"] },
|
|
18
|
+
description: "Links a PDF rendered from a document-pdf template into a mail template under the key. Every mail sent from the template then carries that PDF, rendered at send time with the mail's own variables and locale; nothing is uploaded or stored. fileName is a pattern that may use the mail's {{placeholders}} and must end with .pdf. The document template must exist for the tenant or the system. Counts toward the 10 attachments per mail template together with uploaded files.",
|
|
19
|
+
handler: async (event, ctx) => {
|
|
20
|
+
const { templateId, key, templateSlug, fileName } = event.payload;
|
|
21
|
+
const tenantId = resourceTargetTenantId(event.payload.tenantIdOverride, event.user.tenantId);
|
|
22
|
+
return linkTemplatePdfAttachment({
|
|
23
|
+
ctx,
|
|
24
|
+
user: event.user,
|
|
25
|
+
tenantId,
|
|
26
|
+
templateId,
|
|
27
|
+
key,
|
|
28
|
+
templateSlug,
|
|
29
|
+
fileName,
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
export declare const resourceRemoveWrite: import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"resource-remove", z.ZodObject<{
|
|
3
|
+
templateId: z.ZodString;
|
|
4
|
+
key: z.ZodString;
|
|
5
|
+
tenantIdOverride: z.ZodOptional<z.ZodString>;
|
|
6
|
+
}, z.core.$strip>, {
|
|
7
|
+
key: string;
|
|
8
|
+
removed: boolean;
|
|
9
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
|
+
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { NotFoundError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
4
|
+
import * as z from "zod";
|
|
5
|
+
import { requireForTenant } from "../../shared/index.js";
|
|
6
|
+
import { parseLinkedResourceEntries } from "../linked-resources.js";
|
|
7
|
+
import { templateResourcesTable } from "../table.js";
|
|
8
|
+
import { resourceKeySchema, resourceTargetTenantId } from "./resource-shared.js";
|
|
9
|
+
import { executor } from "./shared.js";
|
|
10
|
+
// The FileRef itself stays: copy-locale and tenant overrides can share ids and there is
|
|
11
|
+
// no reference count to tell when the last user is gone.
|
|
12
|
+
export const resourceRemoveWrite = defineWriteHandler({
|
|
13
|
+
name: "resource-remove",
|
|
14
|
+
schema: z.object({
|
|
15
|
+
templateId: z.string().min(1),
|
|
16
|
+
key: resourceKeySchema,
|
|
17
|
+
/** Cross-tenant target — SystemAdmin only. */
|
|
18
|
+
tenantIdOverride: z.string().min(1).optional(),
|
|
19
|
+
}),
|
|
20
|
+
access: { roles: ["TenantAdmin", "SystemAdmin"] },
|
|
21
|
+
description: "Unlinks a resource key from a template, whether it holds an image, an attached file or a linked PDF template; removing a key the template does not have succeeds without a change.",
|
|
22
|
+
handler: async (event, ctx) => {
|
|
23
|
+
const { templateId, key } = event.payload;
|
|
24
|
+
const tenantId = resourceTargetTenantId(event.payload.tenantIdOverride, event.user.tenantId);
|
|
25
|
+
const { db, streamTenantId } = requireForTenant(ctx, tenantId);
|
|
26
|
+
const row = await fetchOne(db, templateResourcesTable, {
|
|
27
|
+
tenantId,
|
|
28
|
+
id: templateId,
|
|
29
|
+
});
|
|
30
|
+
if (row === undefined)
|
|
31
|
+
return writeFailure(new NotFoundError("template-resource", templateId));
|
|
32
|
+
const { [key]: removed, ...remaining } = parseLinkedResourceEntries(row.linkedResources);
|
|
33
|
+
if (removed === undefined)
|
|
34
|
+
return { isSuccess: true, data: { key, removed: false } };
|
|
35
|
+
const updated = await executor.update({
|
|
36
|
+
id: row.id,
|
|
37
|
+
version: row.version,
|
|
38
|
+
changes: { linkedResources: JSON.stringify(remaining) },
|
|
39
|
+
}, event.user, db, { streamTenantId });
|
|
40
|
+
if (!updated.isSuccess)
|
|
41
|
+
return updated;
|
|
42
|
+
return { isSuccess: true, data: { key, removed: true } };
|
|
43
|
+
},
|
|
44
|
+
});
|