@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,32 +1,38 @@
|
|
|
1
1
|
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import * as z from "zod";
|
|
3
3
|
import { linkTemplatePdfAttachment } from "../store-template-resource.js";
|
|
4
|
+
import { refuseTemplateEditById, templateEditAccess, } from "../template-edit-policy.js";
|
|
4
5
|
import { resourceKeySchema, resourceTargetTenantId } from "./resource-shared.js";
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
ctx,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
6
|
+
export function makeResourceLinkPdfWrite(policy) {
|
|
7
|
+
return defineWriteHandler({
|
|
8
|
+
name: "resource-link-pdf",
|
|
9
|
+
schema: z.object({
|
|
10
|
+
templateId: z.string().min(1),
|
|
11
|
+
key: resourceKeySchema,
|
|
12
|
+
/** Slug of a document-pdf template of the tenant or the system. */
|
|
13
|
+
templateSlug: z.string().min(1),
|
|
14
|
+
/** Name pattern of the attached PDF; may hold {{placeholders}}, must end with .pdf. */
|
|
15
|
+
fileName: z.string().min(1),
|
|
16
|
+
/** Cross-tenant target — SystemAdmin only. */
|
|
17
|
+
tenantIdOverride: z.string().min(1).optional(),
|
|
18
|
+
}),
|
|
19
|
+
access: templateEditAccess(policy.tenantEditable),
|
|
20
|
+
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. Outside the templates the app lists in tenantEditable only a SystemAdmin may use it.",
|
|
21
|
+
handler: async (event, ctx) => {
|
|
22
|
+
const { templateId, key, templateSlug, fileName } = event.payload;
|
|
23
|
+
const tenantId = resourceTargetTenantId(event.payload.tenantIdOverride, event.user.tenantId);
|
|
24
|
+
const refused = await refuseTemplateEditById(ctx.db, policy, event.user, tenantId, templateId);
|
|
25
|
+
if (refused !== null)
|
|
26
|
+
return refused;
|
|
27
|
+
return linkTemplatePdfAttachment({
|
|
28
|
+
ctx,
|
|
29
|
+
user: event.user,
|
|
30
|
+
tenantId,
|
|
31
|
+
templateId,
|
|
32
|
+
key,
|
|
33
|
+
templateSlug,
|
|
34
|
+
fileName,
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
|
-
|
|
2
|
+
import { type TemplateEditPolicy } from "../template-edit-policy.ts";
|
|
3
|
+
export declare function makeResourceRemoveWrite(policy: TemplateEditPolicy): import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"resource-remove", z.ZodObject<{
|
|
3
4
|
templateId: z.ZodString;
|
|
4
5
|
key: z.ZodString;
|
|
5
6
|
tenantIdOverride: z.ZodOptional<z.ZodString>;
|
|
@@ -5,40 +5,46 @@ import * as z from "zod";
|
|
|
5
5
|
import { requireForTenant } from "../../shared/index.js";
|
|
6
6
|
import { parseLinkedResourceEntries } from "../linked-resources.js";
|
|
7
7
|
import { templateResourcesTable } from "../table.js";
|
|
8
|
+
import { refuseTemplateEditById, templateEditAccess, } from "../template-edit-policy.js";
|
|
8
9
|
import { resourceKeySchema, resourceTargetTenantId } from "./resource-shared.js";
|
|
9
10
|
import { executor } from "./shared.js";
|
|
10
11
|
// The FileRef itself stays: copy-locale and tenant overrides can share ids and there is
|
|
11
12
|
// no reference count to tell when the last user is gone.
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
13
|
+
export function makeResourceRemoveWrite(policy) {
|
|
14
|
+
return defineWriteHandler({
|
|
15
|
+
name: "resource-remove",
|
|
16
|
+
schema: z.object({
|
|
17
|
+
templateId: z.string().min(1),
|
|
18
|
+
key: resourceKeySchema,
|
|
19
|
+
/** Cross-tenant target — SystemAdmin only. */
|
|
20
|
+
tenantIdOverride: z.string().min(1).optional(),
|
|
21
|
+
}),
|
|
22
|
+
access: templateEditAccess(policy.tenantEditable),
|
|
23
|
+
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. Outside the templates the app lists in tenantEditable only a SystemAdmin may use it.",
|
|
24
|
+
handler: async (event, ctx) => {
|
|
25
|
+
const { templateId, key } = event.payload;
|
|
26
|
+
const tenantId = resourceTargetTenantId(event.payload.tenantIdOverride, event.user.tenantId);
|
|
27
|
+
const refused = await refuseTemplateEditById(ctx.db, policy, event.user, tenantId, templateId);
|
|
28
|
+
if (refused !== null)
|
|
29
|
+
return refused;
|
|
30
|
+
const { db, streamTenantId } = requireForTenant(ctx, tenantId);
|
|
31
|
+
const row = await fetchOne(db, templateResourcesTable, {
|
|
32
|
+
tenantId,
|
|
33
|
+
id: templateId,
|
|
34
|
+
});
|
|
35
|
+
if (row === undefined)
|
|
36
|
+
return writeFailure(new NotFoundError("template-resource", templateId));
|
|
37
|
+
const { [key]: removed, ...remaining } = parseLinkedResourceEntries(row.linkedResources);
|
|
38
|
+
if (removed === undefined)
|
|
39
|
+
return { isSuccess: true, data: { key, removed: false } };
|
|
40
|
+
const updated = await executor.update({
|
|
41
|
+
id: row.id,
|
|
42
|
+
version: row.version,
|
|
43
|
+
changes: { linkedResources: JSON.stringify(remaining) },
|
|
44
|
+
}, event.user, db, { streamTenantId });
|
|
45
|
+
if (!updated.isSuccess)
|
|
46
|
+
return updated;
|
|
47
|
+
return { isSuccess: true, data: { key, removed: true } };
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
2
|
import { type PreparedTemplateFile, type StoredTemplateAttachment, type StoredTemplateResource } from "../store-template-resource.ts";
|
|
3
|
-
|
|
3
|
+
import { type TemplateEditPolicy } from "../template-edit-policy.ts";
|
|
4
|
+
export declare function makeResourceUploadWrite(policy: TemplateEditPolicy): import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"resource-upload", z.ZodObject<{
|
|
4
5
|
templateId: z.ZodOptional<z.ZodString>;
|
|
5
6
|
key: z.ZodOptional<z.ZodString>;
|
|
6
7
|
sourceFileRefId: z.ZodUUID;
|
|
@@ -5,6 +5,7 @@ import { FileRefTooLargeError, fileContextForTenant, readFileRefContent, } from
|
|
|
5
5
|
import { attachmentFileNameSchema } from "../../shared/index.js";
|
|
6
6
|
import { TemplateResolverErrors } from "../qualified-names.js";
|
|
7
7
|
import { prepareTemplateAttachmentBeforeTransaction, prepareTemplateResourceBeforeTransaction, readTemplateResourceMaxBytes, storePreparedTemplateAttachment, storePreparedTemplateResource, } from "../store-template-resource.js";
|
|
8
|
+
import { refuseTemplateEditById, templateEditAccess, } from "../template-edit-policy.js";
|
|
8
9
|
import { RESOURCE_PURPOSES, resourceKeySchema, resourceTargetTenantId } from "./resource-shared.js";
|
|
9
10
|
function attachmentTargetOf(input) {
|
|
10
11
|
const { templateId, key, fileName } = input;
|
|
@@ -16,113 +17,120 @@ function attachmentTargetOf(input) {
|
|
|
16
17
|
// Purpose "image": stores a re-encoded image as a FileRef and, with templateId + key, links it into
|
|
17
18
|
// the template; without templateId it only creates the FileRef (the branding logo upload path).
|
|
18
19
|
// Purpose "attachment": stores the file as uploaded and links it as a mail attachment.
|
|
19
|
-
export
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
const maxBytes = await readTemplateResourceMaxBytes(ctx.config, ctx.registry);
|
|
50
|
-
// The source is read from the session tenant only: with tenantIdOverride the upload
|
|
51
|
-
// sits in the admin's own tenant while the normalized file lands in the target tenant.
|
|
52
|
-
// Only the caller's own unclaimed upload qualifies, so a record-attached image of
|
|
53
|
-
// someone else cannot be copied into a template.
|
|
54
|
-
let source;
|
|
55
|
-
try {
|
|
56
|
-
source = await readFileRefContent({
|
|
57
|
-
db: ctx.db,
|
|
58
|
-
filesForTenant: (tid) => fileContextForTenant(ctx, ctx.user.tenantId, tid),
|
|
59
|
-
fileRefId: sourceFileRefId,
|
|
60
|
-
allowedTenantIds: [event.user.tenantId],
|
|
61
|
-
unclaimedUploadOf: event.user.id,
|
|
62
|
-
maxBytes,
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
catch (cause) {
|
|
66
|
-
if (cause instanceof FileRefTooLargeError) {
|
|
67
|
-
return writeFailure(new UnprocessableError(TemplateResolverErrors.resourceTooLarge, {
|
|
68
|
-
i18nKey: "templateResolver.errors.resource.tooLarge",
|
|
69
|
-
details: { maxBytes, size: cause.size },
|
|
20
|
+
export function makeResourceUploadWrite(policy) {
|
|
21
|
+
return defineWriteHandler({
|
|
22
|
+
name: "resource-upload",
|
|
23
|
+
schema: z
|
|
24
|
+
.object({
|
|
25
|
+
templateId: z.string().min(1).optional(),
|
|
26
|
+
key: resourceKeySchema.optional(),
|
|
27
|
+
/** An unclaimed upload of the caller (POST /api/files without entityType). */
|
|
28
|
+
sourceFileRefId: z.uuid(),
|
|
29
|
+
/** Cross-tenant target — SystemAdmin only. */
|
|
30
|
+
tenantIdOverride: z.string().min(1).optional(),
|
|
31
|
+
/** "attachment" links a PDF/PNG/JPEG as a file attached to every mail of the template. */
|
|
32
|
+
purpose: z.enum(RESOURCE_PURPOSES).default("image"),
|
|
33
|
+
/** Name the recipient sees; required for purpose "attachment". */
|
|
34
|
+
fileName: z.string().optional(),
|
|
35
|
+
})
|
|
36
|
+
.refine((p) => p.purpose === "attachment" || (p.templateId === undefined) === (p.key === undefined), {
|
|
37
|
+
message: "templateId and key must be given together",
|
|
38
|
+
path: ["key"],
|
|
39
|
+
}),
|
|
40
|
+
access: templateEditAccess(policy.tenantEditable),
|
|
41
|
+
description: "Turns a file uploaded via POST /api/files (multipart, no entityType) into a template resource. With purpose image (default) the upload is checked against the resource-max-bytes cap, re-encoded to a supported image format and stored as a new file; with templateId and key it is linked into that template under the key, without them only the file is created. With purpose attachment, templateId, key and fileName are required: a PDF, PNG or JPEG is stored unchanged and linked into a mail template as a file attached to every mail sent from it, at most 10 per template. A cap above the files route's maxUploadSize has no effect. Only a SystemAdmin and the roles of the templates the app lists in tenantEditable may use it; such a role may link only into those templates.",
|
|
42
|
+
beforeTransaction: async (event, ctx) => {
|
|
43
|
+
const { templateId, key, sourceFileRefId, purpose, fileName } = event.payload;
|
|
44
|
+
const attachmentTarget = purpose === "attachment" ? attachmentTargetOf({ templateId, key, fileName }) : null;
|
|
45
|
+
if (purpose === "attachment" && attachmentTarget === null) {
|
|
46
|
+
return writeFailure(new UnprocessableError(TemplateResolverErrors.attachmentInvalid, {
|
|
47
|
+
i18nKey: "templateResolver.errors.attachment.invalid",
|
|
48
|
+
details: { code: "target_required" },
|
|
70
49
|
}));
|
|
71
50
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
51
|
+
if (templateId !== undefined) {
|
|
52
|
+
const refused = await refuseTemplateEditById(ctx.db, policy, event.user, resourceTargetTenantId(event.payload.tenantIdOverride, event.user.tenantId), templateId);
|
|
53
|
+
if (refused !== null)
|
|
54
|
+
return refused;
|
|
55
|
+
}
|
|
56
|
+
const maxBytes = await readTemplateResourceMaxBytes(ctx.config, ctx.registry);
|
|
57
|
+
// The source is read from the session tenant only: with tenantIdOverride the upload
|
|
58
|
+
// sits in the admin's own tenant while the normalized file lands in the target tenant.
|
|
59
|
+
// Only the caller's own unclaimed upload qualifies, so a record-attached image of
|
|
60
|
+
// someone else cannot be copied into a template.
|
|
61
|
+
let source;
|
|
62
|
+
try {
|
|
63
|
+
source = await readFileRefContent({
|
|
64
|
+
db: ctx.db,
|
|
65
|
+
filesForTenant: (tid) => fileContextForTenant(ctx, ctx.user.tenantId, tid),
|
|
66
|
+
fileRefId: sourceFileRefId,
|
|
67
|
+
allowedTenantIds: [event.user.tenantId],
|
|
68
|
+
unclaimedUploadOf: event.user.id,
|
|
69
|
+
maxBytes,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch (cause) {
|
|
73
|
+
if (cause instanceof FileRefTooLargeError) {
|
|
74
|
+
return writeFailure(new UnprocessableError(TemplateResolverErrors.resourceTooLarge, {
|
|
75
|
+
i18nKey: "templateResolver.errors.resource.tooLarge",
|
|
76
|
+
details: { maxBytes, size: cause.size },
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
throw cause;
|
|
80
|
+
}
|
|
81
|
+
if (source === null)
|
|
82
|
+
return writeFailure(new NotFoundError("file-ref", sourceFileRefId));
|
|
83
|
+
// The source upload stays as an unclaimed file: bundled-features cannot reach the fileRef executor to delete it.
|
|
84
|
+
const tenantId = resourceTargetTenantId(event.payload.tenantIdOverride, event.user.tenantId);
|
|
85
|
+
if (attachmentTarget !== null) {
|
|
86
|
+
return prepareTemplateAttachmentBeforeTransaction({
|
|
87
|
+
ctx,
|
|
88
|
+
user: event.user,
|
|
89
|
+
tenantId,
|
|
90
|
+
bytes: source.content,
|
|
91
|
+
maxBytes,
|
|
92
|
+
...attachmentTarget,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return prepareTemplateResourceBeforeTransaction({
|
|
80
96
|
ctx,
|
|
81
97
|
user: event.user,
|
|
82
98
|
tenantId,
|
|
83
99
|
bytes: source.content,
|
|
84
100
|
maxBytes,
|
|
85
|
-
...
|
|
101
|
+
...(templateId !== undefined && { templateId }),
|
|
102
|
+
...(key !== undefined && { key }),
|
|
86
103
|
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}));
|
|
104
|
+
},
|
|
105
|
+
handler: async (event, ctx) => {
|
|
106
|
+
const { templateId, key } = event.payload;
|
|
107
|
+
const tenantId = resourceTargetTenantId(event.payload.tenantIdOverride, event.user.tenantId);
|
|
108
|
+
const { prepared } = event;
|
|
109
|
+
if (prepared.kind === "attachment") {
|
|
110
|
+
// The phase accepts attachments only with a complete target, so these are set.
|
|
111
|
+
if (templateId === undefined || key === undefined) {
|
|
112
|
+
return writeFailure(new UnprocessableError(TemplateResolverErrors.attachmentInvalid, {
|
|
113
|
+
i18nKey: "templateResolver.errors.attachment.invalid",
|
|
114
|
+
details: { code: "target_required" },
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
return storePreparedTemplateAttachment({
|
|
118
|
+
ctx,
|
|
119
|
+
user: event.user,
|
|
120
|
+
tenantId,
|
|
121
|
+
prepared,
|
|
122
|
+
templateId,
|
|
123
|
+
key,
|
|
124
|
+
});
|
|
109
125
|
}
|
|
110
|
-
return
|
|
126
|
+
return storePreparedTemplateResource({
|
|
111
127
|
ctx,
|
|
112
128
|
user: event.user,
|
|
113
129
|
tenantId,
|
|
114
130
|
prepared,
|
|
115
|
-
templateId,
|
|
116
|
-
key,
|
|
131
|
+
...(templateId !== undefined && { templateId }),
|
|
132
|
+
...(key !== undefined && { key }),
|
|
117
133
|
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
user: event.user,
|
|
122
|
-
tenantId,
|
|
123
|
-
prepared,
|
|
124
|
-
...(templateId !== undefined && { templateId }),
|
|
125
|
-
...(key !== undefined && { key }),
|
|
126
|
-
});
|
|
127
|
-
},
|
|
128
|
-
});
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { HandlerContext, Registry, SessionUser } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { type ChannelContext } from "../../delivery/index.ts";
|
|
3
|
+
export declare function emailChannelOf(registry: Registry): import("../../delivery/types.ts").DeliveryChannel | undefined;
|
|
4
|
+
export declare function testMailChannelContext(ctx: HandlerContext, user: SessionUser): ChannelContext;
|
|
5
|
+
export declare function resolveTestMailAddress(channel: NonNullable<ReturnType<typeof emailChannelOf>>, channelContext: ChannelContext, user: SessionUser): Promise<string | null>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { collectChannels, createEmailAttachmentReader, EMAIL_CHANNEL_NAME, } from "../../delivery/index.js";
|
|
2
|
+
import { fileContextForTenant } from "../../file-foundation/index.js";
|
|
3
|
+
export function emailChannelOf(registry) {
|
|
4
|
+
return collectChannels(registry).find((channel) => channel.name === EMAIL_CHANNEL_NAME);
|
|
5
|
+
}
|
|
6
|
+
export function testMailChannelContext(ctx, user) {
|
|
7
|
+
return {
|
|
8
|
+
db: ctx.db,
|
|
9
|
+
registry: ctx.registry,
|
|
10
|
+
sseBroker: undefined,
|
|
11
|
+
tenantId: user.tenantId,
|
|
12
|
+
secrets: ctx.secrets,
|
|
13
|
+
config: ctx.config,
|
|
14
|
+
readAttachment: createEmailAttachmentReader(ctx.db, ctx.registry, user.tenantId, ctx.config, (tenantId) => fileContextForTenant(ctx, ctx.user.tenantId, tenantId)),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export async function resolveTestMailAddress(channel, channelContext, user) {
|
|
18
|
+
return (await channel.resolve?.(user.id, channelContext)) ?? null;
|
|
19
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
export type SendTestStatus = {
|
|
3
|
+
readonly canSend: true;
|
|
4
|
+
readonly reason: null;
|
|
5
|
+
} | {
|
|
6
|
+
readonly canSend: false;
|
|
7
|
+
readonly reason: "no_channel" | "no_address";
|
|
8
|
+
};
|
|
9
|
+
export declare const sendTestStatusQuery: import("@cosmicdrift/kumiko-types/define-handler").QueryHandlerDefinition<"send-test-status", z.ZodObject<{}, z.core.$strip>, SendTestStatus, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { emailChannelOf, resolveTestMailAddress, testMailChannelContext, } from "./send-test-shared.js";
|
|
4
|
+
export const sendTestStatusQuery = defineQueryHandler({
|
|
5
|
+
name: "send-test-status",
|
|
6
|
+
schema: z.object({}),
|
|
7
|
+
access: { roles: ["TenantAdmin", "SystemAdmin"] },
|
|
8
|
+
description: "Tells whether send-test can reach the calling user: canSend is true when an email channel is mounted and resolves an address for the caller, otherwise reason is no_channel or no_address; the address itself is never returned.",
|
|
9
|
+
handler: async (query, ctx) => {
|
|
10
|
+
const channel = emailChannelOf(ctx.registry);
|
|
11
|
+
if (channel === undefined)
|
|
12
|
+
return { canSend: false, reason: "no_channel" };
|
|
13
|
+
const address = await resolveTestMailAddress(channel, testMailChannelContext(ctx, query.user), query.user);
|
|
14
|
+
return address === null
|
|
15
|
+
? { canSend: false, reason: "no_address" }
|
|
16
|
+
: { canSend: true, reason: null };
|
|
17
|
+
},
|
|
18
|
+
});
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
|
-
|
|
2
|
+
import { type TemplateEditPolicy } from "../template-edit-policy.ts";
|
|
3
|
+
export declare function makeSendTestWrite(policy: TemplateEditPolicy): import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"send-test", z.ZodObject<{
|
|
3
4
|
slug: z.ZodString;
|
|
4
5
|
locale: z.ZodString;
|
|
5
6
|
content: z.ZodOptional<z.ZodString>;
|
|
6
7
|
meta: z.ZodOptional<z.ZodUnknown>;
|
|
7
8
|
variableSchema: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
9
|
+
variables: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
8
10
|
includeAttachments: z.ZodDefault<z.ZodBoolean>;
|
|
9
11
|
prefixSubject: z.ZodDefault<z.ZodBoolean>;
|
|
10
12
|
}, z.core.$strip>, {
|