@cosmicdrift/kumiko-bundled-features 0.374.0 → 0.378.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/account-security/constants.d.ts +1 -0
- package/dist/account-security/constants.js +1 -0
- package/dist/account-security/index.d.ts +1 -1
- package/dist/account-security/index.js +1 -1
- package/dist/admin-shell/web/client-plugin.d.ts +1 -1
- package/dist/audit/web/client-plugin.d.ts +1 -1
- package/dist/auth-email-password/mail-notifications.js +1 -1
- package/dist/auth-email-password/web/auth-client.js +1 -1
- package/dist/auth-email-password/web/auth-form-primitives.js +1 -1
- package/dist/auth-email-password/web/default-topbar-actions.js +1 -1
- package/dist/auth-email-password/web/index.js +1 -1
- package/dist/auth-email-password/web/session.js +1 -1
- package/dist/auth-email-password/web/tenant-menu-items.js +1 -1
- package/dist/auth-email-password/web/tenant-switcher.js +1 -1
- package/dist/auth-email-password/web/user-menu.js +1 -1
- package/dist/auth-foundation/env.d.ts +9 -0
- package/dist/auth-foundation/env.js +21 -0
- package/dist/auth-foundation/index.d.ts +1 -0
- package/dist/auth-foundation/index.js +1 -0
- package/dist/auth-mfa/schema/user-mfa.d.ts +6 -6
- package/dist/billing-foundation/consumer-protection/confirmation-mail.js +1 -1
- package/dist/billing-foundation/consumer-protection/termination-pages.js +1 -1
- package/dist/billing-foundation/contact-card.d.ts +2 -0
- package/dist/billing-foundation/contact-card.js +16 -0
- package/dist/billing-foundation/entities.d.ts +8 -8
- package/dist/billing-foundation/handlers/billing-plans.query.js +1 -1
- package/dist/billing-foundation/index.d.ts +1 -1
- package/dist/billing-foundation/plan-catalog.js +1 -0
- package/dist/billing-foundation/projection.d.ts +8 -8
- package/dist/billing-foundation/types.d.ts +11 -0
- package/dist/billing-foundation/validate-options.js +13 -0
- package/dist/billing-foundation/web/billing-contact-plan.d.ts +5 -0
- package/dist/billing-foundation/web/billing-contact-plan.js +17 -0
- package/dist/billing-foundation/web/billing-plans-panel.js +5 -13
- package/dist/billing-foundation/web/cancel-contract-dialog.js +2 -2
- package/dist/billing-foundation/web/checkout-consent-dialog.d.ts +1 -1
- package/dist/billing-foundation/web/checkout-consent-dialog.js +1 -0
- package/dist/billing-foundation/web/client-plugin.d.ts +1 -1
- package/dist/billing-foundation/web/localized-params.d.ts +1 -0
- package/dist/billing-foundation/web/localized-params.js +11 -0
- package/dist/cap-counter/entity.d.ts +1 -1
- package/dist/cap-counter/stock-cap-guard.js +8 -0
- package/dist/cap-overview/web/cap-cards-panel.js +3 -3
- package/dist/cap-overview/web/client-plugin.d.ts +1 -1
- package/dist/channel-email/email-channel.d.ts +2 -1
- package/dist/compliance-profiles/web/client-plugin.d.ts +1 -1
- package/dist/config/feature.js +2 -2
- package/dist/config/table.d.ts +6 -6
- package/dist/config/web/i18n.js +1 -0
- package/dist/custom-fields/entity.d.ts +4 -4
- package/dist/custom-fields/web/client-plugin.d.ts +1 -1
- package/dist/data-retention/schema/tenant-retention-override.d.ts +4 -4
- package/dist/delivery/tables.d.ts +6 -6
- package/dist/delivery/unsubscribe-page.js +1 -1
- package/dist/delivery/web/client-plugin.d.ts +1 -1
- package/dist/delivery/web/delivery-status-cell.js +1 -1
- package/dist/document-ingest-foundation/entity.d.ts +4 -4
- package/dist/feature-toggles/web/client-plugin.d.ts +1 -1
- package/dist/file-provider-s3/env-named-feature.d.ts +17 -0
- package/dist/file-provider-s3/env-named-feature.js +23 -0
- package/dist/file-provider-s3/env-schema.d.ts +7 -0
- package/dist/file-provider-s3/env-schema.js +15 -10
- package/dist/file-provider-s3/index.d.ts +1 -0
- package/dist/file-provider-s3/index.js +1 -0
- package/dist/folders/entity.d.ts +8 -8
- package/dist/folders/web/client-plugin.d.ts +1 -1
- package/dist/form-draft/entity.d.ts +2 -2
- package/dist/host-routing/constants.d.ts +2 -0
- package/dist/host-routing/constants.js +3 -0
- package/dist/host-routing/dispatch.d.ts +1 -1
- package/dist/host-routing/dispatch.js +35 -25
- package/dist/host-routing/feature.js +100 -27
- package/dist/host-routing/host.d.ts +9 -0
- package/dist/host-routing/host.js +24 -2
- package/dist/host-routing/index.d.ts +2 -1
- package/dist/host-routing/index.js +1 -0
- package/dist/host-routing/options.d.ts +14 -0
- package/dist/host-routing/redirects.d.ts +6 -0
- package/dist/host-routing/redirects.js +27 -0
- package/dist/host-routing/tenant-lookup.d.ts +7 -0
- package/dist/host-routing/tenant-lookup.js +39 -0
- package/dist/host-routing/validate-options.js +34 -1
- package/dist/inbound-mail-foundation/entities.d.ts +25 -25
- package/dist/inbound-mail-foundation/feature.js +7 -0
- package/dist/inbound-mail-foundation/handlers/list-accounts.query.d.ts +1 -1
- package/dist/inbound-mail-foundation/handlers/list-accounts.query.js +11 -3
- package/dist/inbound-mail-foundation/i18n.d.ts +3 -0
- package/dist/inbound-mail-foundation/i18n.js +35 -0
- package/dist/inbound-mail-foundation/index.d.ts +1 -0
- package/dist/inbound-mail-foundation/index.js +1 -0
- package/dist/inbound-mail-foundation/projection.d.ts +20 -20
- package/dist/inbound-mail-foundation/screens.d.ts +7 -0
- package/dist/inbound-mail-foundation/screens.js +86 -0
- package/dist/inbound-mail-foundation/types.d.ts +2 -2
- package/dist/jobs/job-run-table.d.ts +6 -6
- package/dist/jobs/web/client-plugin.d.ts +1 -1
- package/dist/ledger/entity.d.ts +13 -13
- package/dist/legal-pages/feature.js +11 -1
- package/dist/legal-pages/layout.d.ts +2 -2
- package/dist/legal-pages/layout.js +4 -4
- package/dist/legal-pages/web/client-plugin.d.ts +1 -1
- package/dist/mail-foundation/feature.d.ts +20 -21
- package/dist/mail-foundation/feature.js +1 -1
- package/dist/mail-foundation/i18n.d.ts +5 -2
- package/dist/mail-foundation/i18n.js +3 -2
- package/dist/mail-transport-smtp/i18n.d.ts +1 -1
- package/dist/mail-transport-smtp/i18n.js +1 -1
- package/dist/managed-pages/table.d.ts +12 -12
- package/dist/managed-pages/web/client-plugin.d.ts +1 -1
- package/dist/notes-history/entity.d.ts +12 -12
- package/dist/notes-history/web/client-plugin.d.ts +1 -1
- package/dist/page-render/branding.js +1 -1
- package/dist/page-render/layout.d.ts +1 -1
- package/dist/page-render/layout.js +2 -2
- package/dist/page-render/markdown.js +1 -1
- package/dist/personal-access-tokens/i18n.js +1 -1
- package/dist/personal-access-tokens/schema/api-token.d.ts +6 -6
- package/dist/personal-access-tokens/screens.js +1 -1
- package/dist/secrets/feature.d.ts +1 -1
- package/dist/secrets/feature.js +3 -3
- package/dist/secrets/table.d.ts +1 -1
- package/dist/seo/sitemap.js +1 -1
- package/dist/sessions/schema/user-session.d.ts +4 -4
- package/dist/shared/mail-template-body.js +1 -1
- package/dist/shared/purge-files-reaching-subject.js +98 -3
- package/dist/tags/entity.d.ts +9 -9
- package/dist/tags/web/client-plugin.d.ts +1 -1
- package/dist/template-resolver/compile-document-body.d.ts +3 -0
- package/dist/template-resolver/compile-document-body.js +50 -3
- package/dist/template-resolver/constants.d.ts +3 -0
- package/dist/template-resolver/constants.js +2 -0
- package/dist/template-resolver/document-blocks.d.ts +5 -0
- package/dist/template-resolver/document-blocks.js +22 -0
- package/dist/template-resolver/document-images.d.ts +2 -0
- package/dist/template-resolver/document-images.js +28 -0
- package/dist/template-resolver/document-ops.d.ts +167 -0
- package/dist/template-resolver/document-ops.js +390 -0
- package/dist/template-resolver/document-sanitizer.d.ts +4 -1
- package/dist/template-resolver/document-sanitizer.js +26 -0
- package/dist/template-resolver/document-schema.d.ts +2 -1
- package/dist/template-resolver/document-schema.js +4 -0
- package/dist/template-resolver/feature.d.ts +11 -0
- package/dist/template-resolver/feature.js +60 -31
- package/dist/template-resolver/handlers/bundle-import.js +1 -0
- package/dist/template-resolver/handlers/collection-item.query.js +2 -1
- package/dist/template-resolver/handlers/collection-list.query.js +2 -1
- package/dist/template-resolver/handlers/collection-set.write.js +2 -2
- package/dist/template-resolver/handlers/collection-shared.d.ts +1 -2
- package/dist/template-resolver/handlers/collection-shared.js +0 -4
- package/dist/template-resolver/handlers/copy-locale.write.d.ts +2 -1
- package/dist/template-resolver/handlers/copy-locale.write.js +67 -63
- package/dist/template-resolver/handlers/discard-draft.write.d.ts +3 -2
- package/dist/template-resolver/handlers/discard-draft.write.js +18 -6
- package/dist/template-resolver/handlers/document-base.d.ts +23 -0
- package/dist/template-resolver/handlers/document-base.js +55 -0
- package/dist/template-resolver/handlers/document-blocks.query.d.ts +22 -0
- package/dist/template-resolver/handlers/document-blocks.query.js +46 -0
- package/dist/template-resolver/handlers/export-bundle.query.d.ts +1 -0
- package/dist/template-resolver/handlers/export-bundle.query.js +9 -2
- package/dist/template-resolver/handlers/import-bundle-system.write.d.ts +1 -0
- package/dist/template-resolver/handlers/import-bundle.write.d.ts +3 -1
- package/dist/template-resolver/handlers/import-bundle.write.js +30 -25
- package/dist/template-resolver/handlers/overview.query.d.ts +2 -1
- package/dist/template-resolver/handlers/overview.query.js +46 -42
- package/dist/template-resolver/handlers/patch-draft.write.d.ts +67 -0
- package/dist/template-resolver/handlers/patch-draft.write.js +111 -0
- package/dist/template-resolver/handlers/preview.query.d.ts +1 -0
- package/dist/template-resolver/handlers/preview.query.js +2 -1
- package/dist/template-resolver/handlers/reset-to-system.write.d.ts +2 -1
- package/dist/template-resolver/handlers/reset-to-system.write.js +51 -44
- package/dist/template-resolver/handlers/resource-link-pdf.write.d.ts +2 -1
- package/dist/template-resolver/handlers/resource-link-pdf.write.js +34 -28
- package/dist/template-resolver/handlers/resource-remove.write.d.ts +2 -1
- package/dist/template-resolver/handlers/resource-remove.write.js +39 -33
- package/dist/template-resolver/handlers/resource-upload.write.d.ts +2 -1
- package/dist/template-resolver/handlers/resource-upload.write.js +104 -96
- package/dist/template-resolver/handlers/send-test-shared.d.ts +5 -0
- package/dist/template-resolver/handlers/send-test-shared.js +19 -0
- package/dist/template-resolver/handlers/send-test-status.query.d.ts +9 -0
- package/dist/template-resolver/handlers/send-test-status.query.js +18 -0
- package/dist/template-resolver/handlers/send-test.write.d.ts +3 -1
- package/dist/template-resolver/handlers/send-test.write.js +87 -88
- package/dist/template-resolver/handlers/set.write.d.ts +2 -1
- package/dist/template-resolver/handlers/set.write.js +94 -90
- package/dist/template-resolver/handlers/template-write.d.ts +2 -0
- package/dist/template-resolver/handlers/template-write.js +14 -2
- package/dist/template-resolver/handlers/toggle-status.write.d.ts +6 -7
- package/dist/template-resolver/handlers/toggle-status.write.js +28 -10
- package/dist/template-resolver/handlers/upsert-tenant.write.d.ts +2 -1
- package/dist/template-resolver/handlers/upsert-tenant.write.js +30 -24
- package/dist/template-resolver/handlers/validate.query.d.ts +10 -4
- package/dist/template-resolver/handlers/validate.query.js +12 -7
- package/dist/template-resolver/index.d.ts +4 -2
- package/dist/template-resolver/index.js +3 -1
- package/dist/template-resolver/{web/placeholder-text.js → placeholder-text.js} +1 -1
- package/dist/template-resolver/qualified-names.d.ts +4 -0
- package/dist/template-resolver/qualified-names.js +4 -0
- package/dist/template-resolver/seeding.d.ts +1 -0
- package/dist/template-resolver/seeding.js +2 -1
- package/dist/template-resolver/table.d.ts +10 -10
- package/dist/template-resolver/template-bundle.d.ts +1 -0
- package/dist/template-resolver/template-bundle.js +6 -1
- package/dist/template-resolver/template-edit-policy.d.ts +22 -0
- package/dist/template-resolver/template-edit-policy.js +45 -0
- package/dist/template-resolver/template-preview.d.ts +4 -1
- package/dist/template-resolver/template-preview.js +23 -5
- package/dist/template-resolver/template-validation.d.ts +2 -1
- package/dist/template-resolver/template-validation.js +34 -14
- package/dist/template-resolver/user-content-table.d.ts +10 -10
- package/dist/template-resolver/web/client-plugin.d.ts +1 -1
- package/dist/template-resolver/web/client-plugin.js +3 -3
- package/dist/template-resolver/web/document-editor-extensions.d.ts +1 -0
- package/dist/template-resolver/web/document-editor-extensions.js +21 -1
- package/dist/template-resolver/web/document-tiptap-editor.d.ts +1 -1
- package/dist/template-resolver/web/document-tiptap-editor.js +23 -7
- package/dist/template-resolver/web/document-toolbar-blocks.d.ts +4 -5
- package/dist/template-resolver/web/document-toolbar-blocks.js +1 -21
- package/dist/template-resolver/web/i18n.js +18 -0
- package/dist/template-resolver/web/placeholder-line-editor.js +2 -2
- package/dist/tenant/invitation-table.d.ts +6 -6
- package/dist/tenant/membership-table.d.ts +4 -4
- package/dist/tenant/schema/tenant.d.ts +6 -6
- package/dist/tenant/web/client-plugin.d.ts +1 -1
- package/dist/tenant/web/member-status-cell.js +1 -1
- package/dist/tier-engine/entity.d.ts +1 -1
- package/dist/tier-engine/tier-resolver.d.ts +1 -1
- package/dist/tier-engine/web/client-plugin.d.ts +1 -1
- package/dist/user/schema/user.d.ts +12 -12
- package/dist/user-data-rights/env.d.ts +5 -0
- package/dist/user-data-rights/env.js +11 -0
- package/dist/user-data-rights/feature.js +4 -2
- package/dist/user-data-rights/index.d.ts +1 -0
- package/dist/user-data-rights/index.js +1 -0
- package/dist/user-data-rights/lib/mail-transport-resolver.js +1 -1
- package/dist/user-data-rights/lib/storage-provider-resolver.js +1 -1
- package/dist/user-data-rights/mail-notifications.js +1 -1
- package/dist/user-data-rights/schema/download-attempt.d.ts +14 -14
- package/dist/user-data-rights/schema/download-token.d.ts +8 -8
- package/dist/user-data-rights/schema/export-job.d.ts +6 -6
- package/dist/user-data-rights/web/client-plugin.d.ts +1 -1
- package/dist/user-data-rights/web/privacy-center-screen.js +2 -2
- package/dist/waitlist/entity.d.ts +14 -14
- package/dist/waitlist/legacy-migrate.d.ts +2 -0
- package/dist/waitlist/legacy-migrate.js +38 -3
- package/package.json +16 -11
- package/src/account-security/changes.json +6 -0
- package/src/auth-foundation/changes.json +6 -0
- package/src/billing-foundation/changes.json +6 -0
- package/src/cap-counter/changes.json +12 -0
- package/src/channel-email/changes.json +6 -0
- package/src/crypto-shredding/changes.json +6 -0
- package/src/file-provider-s3/changes.json +6 -0
- package/src/host-routing/changes.json +6 -0
- package/src/inbound-mail-foundation/changes.json +13 -0
- package/src/legal-pages/changes.json +6 -0
- package/src/mail-foundation/changes.json +6 -0
- package/src/template-resolver/changes.json +38 -0
- package/src/user-data-rights/changes.json +6 -0
- package/src/waitlist/changes.json +6 -0
- /package/dist/template-resolver/{web/placeholder-text.d.ts → placeholder-text.d.ts} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import * as z from "zod";
|
|
3
|
+
import { refuseTemplateEdit, templateEditAccess, } from "../template-edit-policy.js";
|
|
3
4
|
import { resolveTemplateVariables } from "../template-validation.js";
|
|
4
5
|
import { upsertPayloadSchema } from "./shared.js";
|
|
5
6
|
import { writeTemplateRow } from "./template-write.js";
|
|
@@ -8,27 +9,32 @@ import { writeTemplateRow } from "./template-write.js";
|
|
|
8
9
|
// via publish-Handler. SystemAdmin kann via tenantIdOverride für
|
|
9
10
|
// einen anderen Tenant schreiben (typisch: Plattform-Admin-UI das
|
|
10
11
|
// Tenant-Templates kuratiert).
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
tenantId
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
12
|
+
export function makeUpsertTenantWrite(policy) {
|
|
13
|
+
return defineWriteHandler({
|
|
14
|
+
name: "upsert-tenant",
|
|
15
|
+
schema: upsertPayloadSchema.extend({
|
|
16
|
+
tenantIdOverride: z.string().min(1).optional(),
|
|
17
|
+
status: z.enum(["draft", "active"]).default("draft"),
|
|
18
|
+
}),
|
|
19
|
+
access: templateEditAccess(policy.tenantEditable),
|
|
20
|
+
description: "Creates or overwrites a tenant-scoped template override with its variable schema, landing as a draft that only publish makes live (over an active override the draft is stored beside it, the active version keeps being served until publish); it refuses a system-tenant target, which is what upsert-system is for; a TenantAdmin may write it only for templates the app lists in tenantEditable, otherwise SystemAdmin only.",
|
|
21
|
+
handler: async (event, ctx) => {
|
|
22
|
+
const refused = refuseTemplateEdit(policy, event.user, event.payload);
|
|
23
|
+
if (refused !== null)
|
|
24
|
+
return refused;
|
|
25
|
+
// @cast-boundary engine-payload — override is a zod-validated string and
|
|
26
|
+
// event.user.tenantId is already TenantId-branded; forTenant rejects the
|
|
27
|
+
// override for non-SystemAdmins.
|
|
28
|
+
const tenantId = (event.payload.tenantIdOverride ?? event.user.tenantId);
|
|
29
|
+
return writeTemplateRow({
|
|
30
|
+
ctx,
|
|
31
|
+
user: event.user,
|
|
32
|
+
tenantId,
|
|
33
|
+
scope: "tenant",
|
|
34
|
+
status: event.payload.status,
|
|
35
|
+
variables: resolveTemplateVariables(ctx.registry, event.payload.slug, event.payload.variableSchema),
|
|
36
|
+
payload: event.payload,
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -12,12 +12,18 @@ type TemplateDraftInput = {
|
|
|
12
12
|
readonly locale?: string | undefined;
|
|
13
13
|
readonly scope: TemplateWriteScope;
|
|
14
14
|
};
|
|
15
|
+
export declare function knownResourceKeysFor(ctx: HandlerContext, user: SessionUser, where: {
|
|
16
|
+
readonly slug: string;
|
|
17
|
+
readonly kind: string;
|
|
18
|
+
readonly scope: TemplateWriteScope;
|
|
19
|
+
readonly locale: string | undefined;
|
|
20
|
+
}): Promise<string[]>;
|
|
15
21
|
export declare function validateTemplateDraft(ctx: HandlerContext, user: SessionUser, payload: TemplateDraftInput): Promise<{
|
|
16
22
|
response: {
|
|
17
23
|
canSave: boolean;
|
|
18
24
|
canPublish: boolean;
|
|
19
|
-
issues: import("@cosmicdrift/kumiko-
|
|
20
|
-
publishIssues: import("@cosmicdrift/kumiko-
|
|
25
|
+
issues: import("@cosmicdrift/kumiko-framework/errors").FieldIssue[];
|
|
26
|
+
publishIssues: import("@cosmicdrift/kumiko-framework/errors").FieldIssue[];
|
|
21
27
|
used: string[];
|
|
22
28
|
unknown: string[];
|
|
23
29
|
missingRequired: string[];
|
|
@@ -58,8 +64,8 @@ export declare const validateQuery: import("@cosmicdrift/kumiko-types/define-han
|
|
|
58
64
|
}, z.core.$strip>, {
|
|
59
65
|
canSave: boolean;
|
|
60
66
|
canPublish: boolean;
|
|
61
|
-
issues: import("@cosmicdrift/kumiko-
|
|
62
|
-
publishIssues: import("@cosmicdrift/kumiko-
|
|
67
|
+
issues: import("@cosmicdrift/kumiko-framework/errors").FieldIssue[];
|
|
68
|
+
publishIssues: import("@cosmicdrift/kumiko-framework/errors").FieldIssue[];
|
|
63
69
|
used: string[];
|
|
64
70
|
unknown: string[];
|
|
65
71
|
missingRequired: string[];
|
|
@@ -18,13 +18,10 @@ async function knownResourceKeys(db, where) {
|
|
|
18
18
|
});
|
|
19
19
|
return rows.flatMap((row) => imageResourceKeysOf(row.linkedResources));
|
|
20
20
|
}
|
|
21
|
-
//
|
|
22
|
-
export async function
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
// The dry run must not promise what the caller's write handlers would refuse.
|
|
26
|
-
const scope = isSystemAdmin(user) ? payload.scope : "tenant";
|
|
27
|
-
const known = [
|
|
21
|
+
// Image keys the editor may reference: the system rows plus, for a tenant write, the caller's own.
|
|
22
|
+
export async function knownResourceKeysFor(ctx, user, where) {
|
|
23
|
+
const { slug, kind, scope, locale } = where;
|
|
24
|
+
return [
|
|
28
25
|
...(await knownResourceKeys(ctx.db, {
|
|
29
26
|
tenantId: SYSTEM_TENANT_ID,
|
|
30
27
|
slug,
|
|
@@ -36,6 +33,14 @@ export async function validateTemplateDraft(ctx, user, payload) {
|
|
|
36
33
|
? await knownResourceKeys(ctx.db, { tenantId: user.tenantId, slug, kind, scope, locale })
|
|
37
34
|
: []),
|
|
38
35
|
];
|
|
36
|
+
}
|
|
37
|
+
// The dry run of the write pipeline; preview feeds on the evaluation, validate returns the response.
|
|
38
|
+
export async function validateTemplateDraft(ctx, user, payload) {
|
|
39
|
+
const variables = resolveTemplateVariables(ctx.registry, payload.slug, payload.variableSchema);
|
|
40
|
+
const { slug, kind, locale } = payload;
|
|
41
|
+
// The dry run must not promise what the caller's write handlers would refuse.
|
|
42
|
+
const scope = isSystemAdmin(user) ? payload.scope : "tenant";
|
|
43
|
+
const known = await knownResourceKeysFor(ctx, user, { slug, kind, scope, locale });
|
|
39
44
|
const evaluation = evaluateTemplateWrite({
|
|
40
45
|
kind,
|
|
41
46
|
scope,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
export { isResourceImageContentType } from "../shared/index.ts";
|
|
1
2
|
export { createTemplateResolverApi, type ResolveRequest, requireTemplateResolver, TemplateNotFoundError, type TemplateResolverApi, type TemplateResource, } from "./api.ts";
|
|
2
|
-
export { type CompileDocumentBodyOptions, collectDocumentResourceKeys, compileDocumentBodyToHandlebars, toResourceDataUri, } from "./compile-document-body.ts";
|
|
3
|
+
export { type CompileDocumentBodyOptions, collectDocumentImageVariables, collectDocumentResourceKeys, compileDocumentBodyToHandlebars, toResourceDataUri, } from "./compile-document-body.ts";
|
|
3
4
|
export { CONTENT_FORMATS, type ContentFormat, DEFAULT_MAIL_LAYOUT_SLUG, DEFAULT_PDF_LAYOUT_SLUG, DOCUMENT_HTML_KINDS, type DocumentHtmlKind, FALLBACK_LOCALE, NON_RENDER_KINDS, type NonRenderKind, PDF_DEFAULT_MARGIN_MM, PDF_FOOTER_MARGIN_MM, PDF_HEADER_MARGIN_MM, PDF_ORIENTATIONS, PDF_PAGE_FORMATS, type PdfOrientation, type PdfPageFormat, RENDER_KINDS, type RenderKind, SYSTEM_TENANT_ID, TEMPLATE_KINDS, TEMPLATE_SCOPES, TEMPLATE_STATUSES, TEXT_BLOCK_KIND, type TemplateKind, type TemplateScope, type TemplateStatus, UPSERT_KINDS, type UpsertKind, } from "./constants.ts";
|
|
4
5
|
export { type DocumentViolation, type DocumentViolationCode, documentViolationsToFieldIssues, flattenParagraphsToLineBreaks, type SanitizeBodyResult, type SanitizeHtmlResult, sanitizeDocumentHtml, sanitizeTemplateBody, } from "./document-sanitizer.ts";
|
|
5
6
|
export { DOCUMENT_CONDITION_ATTRIBUTE, DOCUMENT_NODE_NAMES, DOCUMENT_TEXT_ALIGNMENTS, DocumentButton, DocumentCondition, DocumentDivider, DocumentLink, DocumentPageBreak, DocumentResource, DocumentTable, DocumentTableCell, DocumentTableHeader, DocumentTableRow, DocumentTextAlign, type DocumentTextAlignment, DocumentVariable, documentExtensions, isAllowedDocumentLinkHref, isDocumentPlaceholderHref, isDocumentReferenceName, isDocumentResourceKey, } from "./document-schema.ts";
|
|
6
|
-
export { createTemplateResolverFeature } from "./feature.ts";
|
|
7
|
+
export { createTemplateResolverFeature, type TemplateResolverOptions } from "./feature.ts";
|
|
7
8
|
export type { LayoutSourceResponse, LayoutSourceRow } from "./handlers/layout-source.query.ts";
|
|
8
9
|
export type { OverviewKindGroup, OverviewOrigin, OverviewResponse, OverviewRow, OverviewUsage, } from "./handlers/overview.query.ts";
|
|
9
10
|
export { localeSchema } from "./handlers/shared.ts";
|
|
@@ -16,6 +17,7 @@ export { mapRecordToVariables } from "./record-mapping.ts";
|
|
|
16
17
|
export { type PreparedTemplateImage, prepareTemplateResourceBeforeTransaction, readTemplateResourceMaxBytes, type StoredTemplateResource, storePreparedTemplateResource, } from "./store-template-resource.ts";
|
|
17
18
|
export { type TemplateResourceRow, templateResourceEntity, templateResourcesTable, } from "./table.ts";
|
|
18
19
|
export { type TemplateBundle, templateBundleSchema } from "./template-bundle.ts";
|
|
20
|
+
export { type TemplateEditPolicy, type TenantEditableTemplate, templateEditAccess, } from "./template-edit-policy.ts";
|
|
19
21
|
export { isMailTemplateMeta, type LayoutTemplateMeta, MAIL_CATEGORIES, MAIL_TONES, type MailCategory, type MailLayoutMeta, type MailTemplateMeta, type MailTone, type PdfTemplateMeta, parseTemplateMeta, pdfAttachmentFileNamePatternSchema, readMailLayoutMeta, readPdfTemplateMeta, type TemplateMeta, type TemplateMetaIssue, type TemplateMetaResult, templateSlugSchema, } from "./template-meta.ts";
|
|
20
22
|
export { type DocumentDraftOutcome, evaluateDocumentDraft } from "./template-preview.ts";
|
|
21
23
|
export { readTemplateResource, type TemplateResourceFile } from "./template-resources.ts";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
export { isResourceImageContentType } from "../shared/index.js";
|
|
1
2
|
export { createTemplateResolverApi, requireTemplateResolver, TemplateNotFoundError, } from "./api.js";
|
|
2
|
-
export { collectDocumentResourceKeys, compileDocumentBodyToHandlebars, toResourceDataUri, } from "./compile-document-body.js";
|
|
3
|
+
export { collectDocumentImageVariables, collectDocumentResourceKeys, compileDocumentBodyToHandlebars, toResourceDataUri, } from "./compile-document-body.js";
|
|
3
4
|
export { CONTENT_FORMATS, DEFAULT_MAIL_LAYOUT_SLUG, DEFAULT_PDF_LAYOUT_SLUG, DOCUMENT_HTML_KINDS, FALLBACK_LOCALE, NON_RENDER_KINDS, PDF_DEFAULT_MARGIN_MM, PDF_FOOTER_MARGIN_MM, PDF_HEADER_MARGIN_MM, PDF_ORIENTATIONS, PDF_PAGE_FORMATS, RENDER_KINDS, SYSTEM_TENANT_ID, TEMPLATE_KINDS, TEMPLATE_SCOPES, TEMPLATE_STATUSES, TEXT_BLOCK_KIND, UPSERT_KINDS, } from "./constants.js";
|
|
4
5
|
export { documentViolationsToFieldIssues, flattenParagraphsToLineBreaks, sanitizeDocumentHtml, sanitizeTemplateBody, } from "./document-sanitizer.js";
|
|
5
6
|
export { DOCUMENT_CONDITION_ATTRIBUTE, DOCUMENT_NODE_NAMES, DOCUMENT_TEXT_ALIGNMENTS, DocumentButton, DocumentCondition, DocumentDivider, DocumentLink, DocumentPageBreak, DocumentResource, DocumentTable, DocumentTableCell, DocumentTableHeader, DocumentTableRow, DocumentTextAlign, DocumentVariable, documentExtensions, isAllowedDocumentLinkHref, isDocumentPlaceholderHref, isDocumentReferenceName, isDocumentResourceKey, } from "./document-schema.js";
|
|
@@ -14,6 +15,7 @@ export { mapRecordToVariables } from "./record-mapping.js";
|
|
|
14
15
|
export { prepareTemplateResourceBeforeTransaction, readTemplateResourceMaxBytes, storePreparedTemplateResource, } from "./store-template-resource.js";
|
|
15
16
|
export { templateResourceEntity, templateResourcesTable, } from "./table.js";
|
|
16
17
|
export { templateBundleSchema } from "./template-bundle.js";
|
|
18
|
+
export { templateEditAccess, } from "./template-edit-policy.js";
|
|
17
19
|
export { isMailTemplateMeta, MAIL_CATEGORIES, MAIL_TONES, parseTemplateMeta, pdfAttachmentFileNamePatternSchema, readMailLayoutMeta, readPdfTemplateMeta, templateSlugSchema, } from "./template-meta.js";
|
|
18
20
|
export { evaluateDocumentDraft } from "./template-preview.js";
|
|
19
21
|
export { readTemplateResource } from "./template-resources.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isDocumentReferenceName } from "
|
|
1
|
+
import { isDocumentReferenceName } from "./document-schema.js";
|
|
2
2
|
const PLACEHOLDER_TOKEN = /\{\{([^{}]*)\}\}/g;
|
|
3
3
|
// Names the schema refuses stay plain text, so a typo never becomes a chip
|
|
4
4
|
// that the server would reject.
|
|
@@ -17,6 +17,7 @@ export declare const TemplateResolverHandlers: {
|
|
|
17
17
|
readonly resourceRemove: "template-resolver:write:resource-remove";
|
|
18
18
|
readonly resourceLinkPdf: "template-resolver:write:resource-link-pdf";
|
|
19
19
|
readonly sendTest: "template-resolver:write:send-test";
|
|
20
|
+
readonly patchDraft: "template-resolver:write:patch-draft";
|
|
20
21
|
};
|
|
21
22
|
export declare const TemplateResolverQueries: {
|
|
22
23
|
readonly findById: "template-resolver:query:find-by-id";
|
|
@@ -28,7 +29,9 @@ export declare const TemplateResolverQueries: {
|
|
|
28
29
|
readonly diffToSystem: "template-resolver:query:diff-to-system";
|
|
29
30
|
readonly validate: "template-resolver:query:validate";
|
|
30
31
|
readonly preview: "template-resolver:query:preview";
|
|
32
|
+
readonly documentBlocks: "template-resolver:query:document-blocks";
|
|
31
33
|
readonly exportBundle: "template-resolver:query:export-bundle";
|
|
34
|
+
readonly sendTestStatus: "template-resolver:query:send-test-status";
|
|
32
35
|
readonly bySlug: "template-resolver:query:by-slug";
|
|
33
36
|
readonly byTenant: "template-resolver:query:by-tenant";
|
|
34
37
|
};
|
|
@@ -45,6 +48,7 @@ export declare const TemplateResolverErrors: {
|
|
|
45
48
|
readonly targetExists: "target_exists";
|
|
46
49
|
readonly systemResourcesNeedSystemTenant: "system_resources_need_system_tenant";
|
|
47
50
|
readonly systemOnlyTemplate: "system_only_template";
|
|
51
|
+
readonly templateNotTenantEditable: "template_not_tenant_editable";
|
|
48
52
|
readonly bundleLocalesDiffer: "bundle_locales_differ";
|
|
49
53
|
readonly resourceTooLarge: "template_resource_too_large";
|
|
50
54
|
readonly resourceUnsupportedImage: "template_resource_unsupported_image";
|
|
@@ -19,6 +19,7 @@ export const TemplateResolverHandlers = {
|
|
|
19
19
|
resourceRemove: "template-resolver:write:resource-remove",
|
|
20
20
|
resourceLinkPdf: "template-resolver:write:resource-link-pdf",
|
|
21
21
|
sendTest: "template-resolver:write:send-test",
|
|
22
|
+
patchDraft: "template-resolver:write:patch-draft",
|
|
22
23
|
};
|
|
23
24
|
export const TemplateResolverQueries = {
|
|
24
25
|
findById: "template-resolver:query:find-by-id",
|
|
@@ -30,7 +31,9 @@ export const TemplateResolverQueries = {
|
|
|
30
31
|
diffToSystem: "template-resolver:query:diff-to-system",
|
|
31
32
|
validate: "template-resolver:query:validate",
|
|
32
33
|
preview: "template-resolver:query:preview",
|
|
34
|
+
documentBlocks: "template-resolver:query:document-blocks",
|
|
33
35
|
exportBundle: "template-resolver:query:export-bundle",
|
|
36
|
+
sendTestStatus: "template-resolver:query:send-test-status",
|
|
34
37
|
// Public pair — anonymous-reachable, kind pinned to text-block.
|
|
35
38
|
bySlug: "template-resolver:query:by-slug",
|
|
36
39
|
byTenant: "template-resolver:query:by-tenant",
|
|
@@ -56,6 +59,7 @@ export const TemplateResolverErrors = {
|
|
|
56
59
|
targetExists: "target_exists",
|
|
57
60
|
systemResourcesNeedSystemTenant: "system_resources_need_system_tenant",
|
|
58
61
|
systemOnlyTemplate: "system_only_template",
|
|
62
|
+
templateNotTenantEditable: "template_not_tenant_editable",
|
|
59
63
|
bundleLocalesDiffer: "bundle_locales_differ",
|
|
60
64
|
resourceTooLarge: "template_resource_too_large",
|
|
61
65
|
resourceUnsupportedImage: "template_resource_unsupported_image",
|
|
@@ -15,6 +15,7 @@ export type SeedSystemTemplateOptions = {
|
|
|
15
15
|
* system admin uploaded through resource-upload. */
|
|
16
16
|
readonly linkedResources?: Record<string, string>;
|
|
17
17
|
readonly meta?: unknown;
|
|
18
|
+
readonly title?: string;
|
|
18
19
|
readonly parentTemplateId?: string | null;
|
|
19
20
|
readonly by?: SessionUser;
|
|
20
21
|
readonly ifExists?: SeedIfExists;
|
|
@@ -81,7 +81,7 @@ export async function seedSystemTemplate(db, opts) {
|
|
|
81
81
|
variableSchema,
|
|
82
82
|
...(linkedResources !== undefined && { linkedResources }),
|
|
83
83
|
meta,
|
|
84
|
-
title: null,
|
|
84
|
+
title: opts.title ?? null,
|
|
85
85
|
folder: null,
|
|
86
86
|
scope: "system",
|
|
87
87
|
parentTemplateId,
|
|
@@ -171,6 +171,7 @@ export async function seedSystemTemplateBundle(db, bundle, opts = {}) {
|
|
|
171
171
|
contentFormat: bundle.contentFormat,
|
|
172
172
|
variableSchema: bundle.variableSchema ?? {},
|
|
173
173
|
meta: entry.meta,
|
|
174
|
+
...(entry.title !== undefined ? { title: entry.title } : {}),
|
|
174
175
|
...(opts.by !== undefined ? { by: opts.by } : {}),
|
|
175
176
|
...(opts.ifExists !== undefined ? { ifExists: opts.ifExists } : {}),
|
|
176
177
|
});
|
|
@@ -14,7 +14,7 @@ export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
14
14
|
readonly sensitive?: boolean;
|
|
15
15
|
readonly writeOnly?: boolean;
|
|
16
16
|
readonly readAsInstruction?: boolean;
|
|
17
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
17
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
18
18
|
readonly default?: string;
|
|
19
19
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
20
20
|
readonly multiline?: boolean | {
|
|
@@ -66,7 +66,7 @@ export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
66
66
|
readonly sensitive?: boolean;
|
|
67
67
|
readonly writeOnly?: boolean;
|
|
68
68
|
readonly readAsInstruction?: boolean;
|
|
69
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
69
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
70
70
|
readonly default?: string;
|
|
71
71
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
72
72
|
readonly multiline?: boolean | {
|
|
@@ -89,7 +89,7 @@ export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
89
89
|
readonly sensitive?: boolean;
|
|
90
90
|
readonly writeOnly?: boolean;
|
|
91
91
|
readonly readAsInstruction?: boolean;
|
|
92
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
92
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
93
93
|
readonly default?: string;
|
|
94
94
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
95
95
|
readonly multiline?: boolean | {
|
|
@@ -112,7 +112,7 @@ export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
112
112
|
readonly sensitive?: boolean;
|
|
113
113
|
readonly writeOnly?: boolean;
|
|
114
114
|
readonly readAsInstruction?: boolean;
|
|
115
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
115
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
116
116
|
readonly default?: string;
|
|
117
117
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
118
118
|
readonly multiline?: boolean | {
|
|
@@ -245,7 +245,7 @@ export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/f
|
|
|
245
245
|
readonly sensitive?: boolean;
|
|
246
246
|
readonly writeOnly?: boolean;
|
|
247
247
|
readonly readAsInstruction?: boolean;
|
|
248
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
248
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
249
249
|
readonly default?: string;
|
|
250
250
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
251
251
|
readonly multiline?: boolean | {
|
|
@@ -343,7 +343,7 @@ export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framewo
|
|
|
343
343
|
readonly sensitive?: boolean;
|
|
344
344
|
readonly writeOnly?: boolean;
|
|
345
345
|
readonly readAsInstruction?: boolean;
|
|
346
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
346
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
347
347
|
readonly default?: string;
|
|
348
348
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
349
349
|
readonly multiline?: boolean | {
|
|
@@ -395,7 +395,7 @@ export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framewo
|
|
|
395
395
|
readonly sensitive?: boolean;
|
|
396
396
|
readonly writeOnly?: boolean;
|
|
397
397
|
readonly readAsInstruction?: boolean;
|
|
398
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
398
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
399
399
|
readonly default?: string;
|
|
400
400
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
401
401
|
readonly multiline?: boolean | {
|
|
@@ -418,7 +418,7 @@ export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framewo
|
|
|
418
418
|
readonly sensitive?: boolean;
|
|
419
419
|
readonly writeOnly?: boolean;
|
|
420
420
|
readonly readAsInstruction?: boolean;
|
|
421
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
421
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
422
422
|
readonly default?: string;
|
|
423
423
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
424
424
|
readonly multiline?: boolean | {
|
|
@@ -441,7 +441,7 @@ export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framewo
|
|
|
441
441
|
readonly sensitive?: boolean;
|
|
442
442
|
readonly writeOnly?: boolean;
|
|
443
443
|
readonly readAsInstruction?: boolean;
|
|
444
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
444
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
445
445
|
readonly default?: string;
|
|
446
446
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
447
447
|
readonly multiline?: boolean | {
|
|
@@ -574,7 +574,7 @@ export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framewo
|
|
|
574
574
|
readonly sensitive?: boolean;
|
|
575
575
|
readonly writeOnly?: boolean;
|
|
576
576
|
readonly readAsInstruction?: boolean;
|
|
577
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
577
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
578
578
|
readonly default?: string;
|
|
579
579
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
580
580
|
readonly multiline?: boolean | {
|
|
@@ -19,6 +19,7 @@ export declare const templateBundleSchema: z.ZodObject<{
|
|
|
19
19
|
locales: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
20
20
|
body: z.ZodString;
|
|
21
21
|
meta: z.ZodOptional<z.ZodUnknown>;
|
|
22
|
+
title: z.ZodOptional<z.ZodString>;
|
|
22
23
|
}, z.core.$strict>>;
|
|
23
24
|
resources: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
24
25
|
fileName: z.ZodString;
|
|
@@ -33,7 +33,12 @@ const bundleResourceSchema = z
|
|
|
33
33
|
.strict();
|
|
34
34
|
const pdfAttachmentLinkBodySchema = templatePdfAttachmentLinkSchema.omit({ kind: true });
|
|
35
35
|
const bundleLocaleEntrySchema = z
|
|
36
|
-
.object({
|
|
36
|
+
.object({
|
|
37
|
+
body: z.string().max(200_000),
|
|
38
|
+
meta: z.unknown().optional(),
|
|
39
|
+
// Shown in the template overview instead of the slug; one per locale, so it is translated.
|
|
40
|
+
title: z.string().min(1).max(200).optional(),
|
|
41
|
+
})
|
|
37
42
|
.strict();
|
|
38
43
|
export const templateBundleSchema = z
|
|
39
44
|
.object({
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type HandlerContext, type RoleAccessRule, type SessionUser } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { type WriteFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
3
|
+
import type { TemplateKind } from "./constants.ts";
|
|
4
|
+
export type TenantEditableTemplate = {
|
|
5
|
+
readonly kind: TemplateKind;
|
|
6
|
+
readonly slug: string;
|
|
7
|
+
readonly access: RoleAccessRule;
|
|
8
|
+
};
|
|
9
|
+
export type TemplateEditPolicy = {
|
|
10
|
+
readonly tenantEditable: readonly TenantEditableTemplate[];
|
|
11
|
+
};
|
|
12
|
+
type EditTarget = {
|
|
13
|
+
readonly slug: string;
|
|
14
|
+
readonly kind: string;
|
|
15
|
+
};
|
|
16
|
+
type EditUser = Pick<SessionUser, "roles">;
|
|
17
|
+
export declare function templateEditAccess(entries: readonly TenantEditableTemplate[]): RoleAccessRule;
|
|
18
|
+
export declare function mayEditTemplate(policy: TemplateEditPolicy, user: EditUser, target: EditTarget): boolean;
|
|
19
|
+
export declare function refuseTemplateEdit(policy: TemplateEditPolicy, user: EditUser, target: EditTarget): WriteFailure | null;
|
|
20
|
+
export declare function refuseMissingTemplate(policy: TemplateEditPolicy, user: EditUser): WriteFailure | null;
|
|
21
|
+
export declare function refuseTemplateEditById(db: HandlerContext["db"], policy: TemplateEditPolicy, user: EditUser, tenantId: string, templateId: string): Promise<WriteFailure | null>;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { fetchOne } from "@cosmicdrift/kumiko-framework/db";
|
|
2
|
+
import { hasAccess, } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { AccessDeniedError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
|
|
4
|
+
import { TemplateResolverErrors } from "./qualified-names.js";
|
|
5
|
+
import { templateResourcesTable } from "./table.js";
|
|
6
|
+
import { isSystemAdmin } from "./template-draft.js";
|
|
7
|
+
const SYSTEM_ADMIN_ROLE = "SystemAdmin";
|
|
8
|
+
export function templateEditAccess(entries) {
|
|
9
|
+
const roles = new Set([SYSTEM_ADMIN_ROLE]);
|
|
10
|
+
for (const entry of entries) {
|
|
11
|
+
for (const role of entry.access.roles)
|
|
12
|
+
roles.add(role);
|
|
13
|
+
}
|
|
14
|
+
return { roles: [...roles] };
|
|
15
|
+
}
|
|
16
|
+
export function mayEditTemplate(policy, user, target) {
|
|
17
|
+
if (isSystemAdmin(user))
|
|
18
|
+
return true;
|
|
19
|
+
const entry = policy.tenantEditable.find((candidate) => candidate.slug === target.slug && candidate.kind === target.kind);
|
|
20
|
+
return entry !== undefined && hasAccess(user, entry.access);
|
|
21
|
+
}
|
|
22
|
+
export function refuseTemplateEdit(policy, user, target) {
|
|
23
|
+
if (mayEditTemplate(policy, user, target))
|
|
24
|
+
return null;
|
|
25
|
+
return writeFailure(new AccessDeniedError({
|
|
26
|
+
i18nKey: "templateResolver.errors.templateNotTenantEditable",
|
|
27
|
+
details: { reason: TemplateResolverErrors.templateNotTenantEditable },
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
// Fail closed: a missing row gives a non-SystemAdmin the same refusal as a locked one,
|
|
31
|
+
// so the response never reveals whether an id exists. SystemAdmin keeps the 404.
|
|
32
|
+
export function refuseMissingTemplate(policy, user) {
|
|
33
|
+
return isSystemAdmin(user) ? null : refuseTemplateEdit(policy, user, { slug: "", kind: "" });
|
|
34
|
+
}
|
|
35
|
+
export async function refuseTemplateEditById(db, policy, user, tenantId, templateId) {
|
|
36
|
+
if (isSystemAdmin(user))
|
|
37
|
+
return null;
|
|
38
|
+
const row = await fetchOne(db, templateResourcesTable, {
|
|
39
|
+
tenantId,
|
|
40
|
+
id: templateId,
|
|
41
|
+
});
|
|
42
|
+
return row === undefined
|
|
43
|
+
? refuseMissingTemplate(policy, user)
|
|
44
|
+
: refuseTemplateEdit(policy, user, row);
|
|
45
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type HandlerContext, type SessionUser, type TemplateVariables } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import { type KumikoError } from "@cosmicdrift/kumiko-framework/errors";
|
|
3
3
|
import { type MailAttachmentRef } from "../renderer-foundation/index.ts";
|
|
4
4
|
import { type TemplateScope } from "./constants.ts";
|
|
@@ -41,6 +41,8 @@ export type TemplatePreviewInput = {
|
|
|
41
41
|
readonly content?: string | undefined;
|
|
42
42
|
readonly meta?: unknown;
|
|
43
43
|
readonly variableSchema: Readonly<Record<string, unknown>>;
|
|
44
|
+
/** Replaces the declared example values for the named variables. */
|
|
45
|
+
readonly variables?: Readonly<Record<string, unknown>> | undefined;
|
|
44
46
|
};
|
|
45
47
|
type ValidationResponse = Awaited<ReturnType<typeof validateTemplateDraft>>["response"];
|
|
46
48
|
export type TemplatePreview = {
|
|
@@ -59,6 +61,7 @@ export type TemplatePreview = {
|
|
|
59
61
|
readonly scope: TemplateScope;
|
|
60
62
|
readonly version: number;
|
|
61
63
|
} | null;
|
|
64
|
+
/** The values the preview was actually rendered with: examples plus any overrides. */
|
|
62
65
|
readonly sampleValues: Readonly<Record<string, unknown>>;
|
|
63
66
|
readonly checks: readonly PreviewCheck[];
|
|
64
67
|
readonly validation: ValidationResponse;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { selectMany } from "@cosmicdrift/kumiko-framework/db";
|
|
2
|
-
import {
|
|
2
|
+
import { templateVariablesToZod, } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { NotFoundError, UnprocessableError, ValidationError, validationErrorFromZod, } from "@cosmicdrift/kumiko-framework/errors";
|
|
3
4
|
import { fileRefsTable } from "@cosmicdrift/kumiko-framework/files";
|
|
4
5
|
import { createI18n } from "@cosmicdrift/kumiko-framework/i18n";
|
|
5
|
-
import { escapeHtml } from "@cosmicdrift/kumiko-headless";
|
|
6
|
+
import { escapeHtml } from "@cosmicdrift/kumiko-renderer/headless";
|
|
7
|
+
import * as z from "zod";
|
|
6
8
|
import { createEmailAttachmentReader, createTemplateMailRenderer } from "../delivery/index.js";
|
|
7
9
|
import { fileContextForTenant } from "../file-foundation/index.js";
|
|
8
10
|
import { collectRendererPlugins, createRendererFoundationApi, RendererError, } from "../renderer-foundation/index.js";
|
|
@@ -62,12 +64,17 @@ function sampleList(def, labelText) {
|
|
|
62
64
|
rows.push(sampleRow(def.item, labelText));
|
|
63
65
|
return rows;
|
|
64
66
|
}
|
|
67
|
+
function sampleValueFor(name, def, labelText) {
|
|
68
|
+
// An images variable has no sample: file ids are record data, never preview content.
|
|
69
|
+
if (def.type === "images")
|
|
70
|
+
return [];
|
|
71
|
+
return def.type === "list" ? sampleList(def, labelText) : sampleScalar(name, def, labelText);
|
|
72
|
+
}
|
|
65
73
|
// Declared examples win; the fallbacks are deterministic so a preview never differs between calls.
|
|
66
74
|
export function sampleValuesFor(variables, labelText = (label) => label) {
|
|
67
75
|
const values = {};
|
|
68
76
|
for (const [name, def] of Object.entries(variables)) {
|
|
69
|
-
values[name] =
|
|
70
|
-
def.type === "list" ? sampleList(def, labelText) : sampleScalar(name, def, labelText);
|
|
77
|
+
values[name] = sampleValueFor(name, def, labelText);
|
|
71
78
|
}
|
|
72
79
|
return values;
|
|
73
80
|
}
|
|
@@ -413,7 +420,18 @@ export async function renderTemplatePreview(ctx, user, input) {
|
|
|
413
420
|
return failed(new ValidationError({ fields: [...evaluation.issues] }));
|
|
414
421
|
const content = evaluation.content ?? "";
|
|
415
422
|
const meta = parseTemplateMeta(input.kind, parseStoredJson(evaluation.metaJson));
|
|
416
|
-
const
|
|
423
|
+
const exampleValues = sampleValuesFor(validation.variables, labelTextFor(ctx, input.locale));
|
|
424
|
+
let sampleValues = exampleValues;
|
|
425
|
+
if (input.variables !== undefined) {
|
|
426
|
+
const overrides = z.object({
|
|
427
|
+
variables: templateVariablesToZod(validation.variables).partial().strict(),
|
|
428
|
+
});
|
|
429
|
+
const checked = overrides.safeParse({ variables: input.variables });
|
|
430
|
+
if (!checked.success)
|
|
431
|
+
return failed(validationErrorFromZod(checked.error));
|
|
432
|
+
// Original values, not the parse result: parsing would strip keys inside list rows.
|
|
433
|
+
sampleValues = { ...exampleValues, ...input.variables };
|
|
434
|
+
}
|
|
417
435
|
if (input.kind === "document-pdf") {
|
|
418
436
|
return renderDocumentPreview(ctx, user, {
|
|
419
437
|
input,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Registry, TemplateVariables } from "@cosmicdrift/kumiko-framework/engine";
|
|
1
|
+
import type { Registry, TemplateVariableDef, TemplateVariables } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import type { TemplateMeta } from "./template-meta.ts";
|
|
3
3
|
export declare const SYSTEM_PLACEHOLDERS: ReadonlySet<string>;
|
|
4
4
|
export declare function systemPlaceholdersFor(kind: string): ReadonlySet<string>;
|
|
@@ -9,6 +9,7 @@ export type TemplateValidation = {
|
|
|
9
9
|
/** data-resource keys with no uploaded file; a warning, never a blocker. */
|
|
10
10
|
readonly unknownResources: readonly string[];
|
|
11
11
|
};
|
|
12
|
+
export declare function toVariableDef(raw: unknown): TemplateVariableDef | null;
|
|
12
13
|
export declare function resolveTemplateVariables(registry: Registry, slug: string, variableSchema: Readonly<Record<string, unknown>>): TemplateVariables;
|
|
13
14
|
export declare function variablesFromSchema(variableSchema: Readonly<Record<string, unknown>>): TemplateVariables;
|
|
14
15
|
export declare function validateTemplatePlaceholders(input: {
|
|
@@ -38,6 +38,7 @@ const scalarVariableSchema = z.discriminatedUnion("type", [
|
|
|
38
38
|
]);
|
|
39
39
|
const variableDefSchema = z.union([
|
|
40
40
|
scalarVariableSchema,
|
|
41
|
+
z.object({ ...variableCommon, type: z.literal("images") }),
|
|
41
42
|
z.object({
|
|
42
43
|
...variableCommon,
|
|
43
44
|
type: z.literal("list"),
|
|
@@ -48,7 +49,7 @@ const variableDefSchema = z.union([
|
|
|
48
49
|
// A stored variableSchema value is either a bare string (legacy "this is a text
|
|
49
50
|
// variable") or a TemplateVariableDef object; anything else is not declared and
|
|
50
51
|
// therefore shows up as an unknown placeholder instead of being guessed at.
|
|
51
|
-
function toVariableDef(raw) {
|
|
52
|
+
export function toVariableDef(raw) {
|
|
52
53
|
if (typeof raw === "string")
|
|
53
54
|
return { type: "text" };
|
|
54
55
|
const parsed = variableDefSchema.safeParse(raw);
|
|
@@ -74,7 +75,9 @@ function listItemFields(variables, listName) {
|
|
|
74
75
|
const def = listName !== null && Object.hasOwn(variables, listName) ? variables[listName] : undefined;
|
|
75
76
|
return def?.type === "list" ? def.item : {};
|
|
76
77
|
}
|
|
77
|
-
|
|
78
|
+
// An images variable holds file ids: as text it would leak them, so only an images block
|
|
79
|
+
// (and a condition on its presence) may name it.
|
|
80
|
+
function isKnownName(name, scope, itemFields, allowImages = false) {
|
|
78
81
|
const { variables } = scope;
|
|
79
82
|
const root = rootName(name);
|
|
80
83
|
// Declared variables and item fields are scalars: a dotted path under one never renders a value.
|
|
@@ -82,12 +85,15 @@ function isKnownName(name, scope, itemFields) {
|
|
|
82
85
|
return { known: true, used: "" };
|
|
83
86
|
if (scope.systemPlaceholders.has(root))
|
|
84
87
|
return { known: true, used: "" };
|
|
85
|
-
if (Object.hasOwn(variables, name))
|
|
86
|
-
return
|
|
88
|
+
if (Object.hasOwn(variables, name)) {
|
|
89
|
+
return variables[name]?.type === "images" && !allowImages
|
|
90
|
+
? { known: false, used: "" }
|
|
91
|
+
: { known: true, used: name };
|
|
92
|
+
}
|
|
87
93
|
return { known: false, used: "" };
|
|
88
94
|
}
|
|
89
|
-
function recordName(name, itemFields, scope) {
|
|
90
|
-
const resolution = isKnownName(name, scope, itemFields);
|
|
95
|
+
function recordName(name, itemFields, scope, allowImages = false) {
|
|
96
|
+
const resolution = isKnownName(name, scope, itemFields, allowImages);
|
|
91
97
|
if (!resolution.known)
|
|
92
98
|
scope.unknown.add(name);
|
|
93
99
|
else if (resolution.used !== "")
|
|
@@ -96,10 +102,21 @@ function recordName(name, itemFields, scope) {
|
|
|
96
102
|
function enclosingEachName(element) {
|
|
97
103
|
return element.closest("tr[data-each]")?.getAttribute("data-each") ?? null;
|
|
98
104
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
function nameAttributeOf(element) {
|
|
106
|
+
const tag = element.tagName.toLowerCase();
|
|
107
|
+
if (tag === "span")
|
|
108
|
+
return "data-variable";
|
|
109
|
+
if (tag !== "div")
|
|
110
|
+
return undefined;
|
|
111
|
+
return element.hasAttribute("data-images") ? "data-images" : "data-steps";
|
|
112
|
+
}
|
|
113
|
+
function recordImagesName(name, scope) {
|
|
114
|
+
const def = Object.hasOwn(scope.variables, name) ? scope.variables[name] : undefined;
|
|
115
|
+
if (def?.type === "images")
|
|
116
|
+
scope.used.add(name);
|
|
117
|
+
else
|
|
118
|
+
scope.unknown.add(name);
|
|
119
|
+
}
|
|
103
120
|
function scanRepeatRows(root, scope) {
|
|
104
121
|
for (const element of Array.from(root.querySelectorAll("tr[data-each]"))) {
|
|
105
122
|
const listName = element.getAttribute("data-each") ?? "";
|
|
@@ -113,15 +130,18 @@ function scanRepeatRows(root, scope) {
|
|
|
113
130
|
function scanConditions(root, scope) {
|
|
114
131
|
for (const element of Array.from(root.querySelectorAll(`[${DOCUMENT_CONDITION_ATTRIBUTE}]`))) {
|
|
115
132
|
const itemFields = listItemFields(scope.variables, enclosingEachName(conditionScopeElement(element)));
|
|
116
|
-
recordName(element.getAttribute(DOCUMENT_CONDITION_ATTRIBUTE) ?? "", itemFields, scope);
|
|
133
|
+
recordName(element.getAttribute(DOCUMENT_CONDITION_ATTRIBUTE) ?? "", itemFields, scope, true);
|
|
117
134
|
}
|
|
118
135
|
}
|
|
119
136
|
function scanReferences(root, scope) {
|
|
120
|
-
for (const element of Array.from(root.querySelectorAll("span[data-variable], div[data-steps], a[href], img[data-resource]"))) {
|
|
137
|
+
for (const element of Array.from(root.querySelectorAll("span[data-variable], div[data-steps], div[data-images], a[href], img[data-resource]"))) {
|
|
121
138
|
const itemFields = listItemFields(scope.variables, enclosingEachName(element));
|
|
122
139
|
const tag = element.tagName.toLowerCase();
|
|
123
|
-
const nameAttribute =
|
|
124
|
-
if (nameAttribute
|
|
140
|
+
const nameAttribute = nameAttributeOf(element);
|
|
141
|
+
if (nameAttribute === "data-images") {
|
|
142
|
+
recordImagesName(element.getAttribute(nameAttribute) ?? "", scope);
|
|
143
|
+
}
|
|
144
|
+
else if (nameAttribute !== undefined) {
|
|
125
145
|
recordName(element.getAttribute(nameAttribute) ?? "", itemFields, scope);
|
|
126
146
|
}
|
|
127
147
|
else if (tag === "a") {
|