@cosmicdrift/kumiko-bundled-features 0.362.0 → 0.364.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/audit/handlers/list.query.js +9 -5
- package/dist/auth-email-password/bootstrap.d.ts +8 -5
- package/dist/auth-email-password/bootstrap.js +12 -4
- package/dist/auth-email-password/email-rate-limits.d.ts +4 -0
- package/dist/auth-email-password/email-rate-limits.js +13 -0
- 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/change-password.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/change-password.write.js +6 -2
- package/dist/auth-email-password/handlers/confirm-account-unlock.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/invite-accept-with-login.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/invite-accept.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/invite-create.write.d.ts +2 -2
- package/dist/auth-email-password/handlers/invite-create.write.js +4 -4
- package/dist/auth-email-password/handlers/invite-signup-complete.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/login.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/logout.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/logout.write.js +6 -2
- package/dist/auth-email-password/handlers/request-account-unlock.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/request-account-unlock.write.js +2 -4
- package/dist/auth-email-password/handlers/request-email-verification.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/request-email-verification.write.js +2 -4
- package/dist/auth-email-password/handlers/request-password-reset.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/request-password-reset.write.js +2 -4
- package/dist/auth-email-password/handlers/reset-password.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/signup-confirm.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/signup-confirm.write.js +3 -4
- package/dist/auth-email-password/handlers/signup-request.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/signup-request.write.js +2 -1
- package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/token-request-handler.d.ts +2 -3
- package/dist/auth-email-password/handlers/token-request-handler.js +2 -2
- package/dist/auth-email-password/handlers/verify-email.write.d.ts +1 -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-email-password/web/auth-gate.js +16 -4
- package/dist/auth-foundation/types.js +2 -8
- package/dist/auth-mfa/feature.js +4 -4
- package/dist/auth-mfa/handlers/disable.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-confirm-preauth.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-confirm.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-start-preauth.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-start.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/regenerate-recovery.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/verify.write.d.ts +1 -1
- 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 +74 -32
- package/dist/billing-foundation/consumer-protection/consent-gate.d.ts +3 -0
- package/dist/billing-foundation/consumer-protection/consent-gate.js +21 -2
- 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/register-termination.js +11 -1
- package/dist/billing-foundation/consumer-protection/termination-mail.d.ts +25 -13
- package/dist/billing-foundation/consumer-protection/termination-mail.js +11 -23
- 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-receipt-limit.d.ts +6 -0
- package/dist/billing-foundation/consumer-protection/termination-receipt-limit.js +34 -0
- package/dist/billing-foundation/consumer-protection/termination-record.d.ts +2 -0
- package/dist/billing-foundation/consumer-protection/termination-record.js +7 -1
- package/dist/billing-foundation/consumer-protection/termination-request.js +31 -21
- package/dist/billing-foundation/consumer-protection/termination-texts.d.ts +1 -1
- package/dist/billing-foundation/consumer-protection/termination-texts.js +6 -0
- package/dist/billing-foundation/consumer-protection/withdrawal-period.d.ts +10 -0
- package/dist/billing-foundation/consumer-protection/withdrawal-period.js +26 -0
- package/dist/billing-foundation/handlers/create-checkout-session.write.js +11 -5
- package/dist/billing-foundation/handlers/process-event.write.js +12 -8
- package/dist/billing-foundation/handlers/process-payment-event.write.js +1 -1
- package/dist/billing-foundation/i18n.js +3 -0
- package/dist/billing-foundation/subscription-tier-sync.d.ts +7 -1
- package/dist/billing-foundation/subscription-tier-sync.js +16 -9
- package/dist/billing-foundation/types.d.ts +7 -2
- package/dist/cap-counter/calendar-cap-usage-reservation.js +2 -0
- package/dist/cap-counter/enforce-cap.d.ts +18 -1
- package/dist/cap-counter/enforce-cap.js +49 -6
- package/dist/cap-counter/index.d.ts +1 -1
- package/dist/cap-counter/index.js +1 -1
- package/dist/cap-counter/with-cap-enforcement.d.ts +2 -0
- 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 +101 -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/channel-in-app/handlers/mark-all-read.write.d.ts +1 -1
- package/dist/channel-in-app/handlers/mark-read.write.d.ts +1 -1
- package/dist/compliance-profiles/handlers/set-profile.write.d.ts +1 -1
- package/dist/config/handlers/reset.write.d.ts +1 -1
- package/dist/config/handlers/set.write.d.ts +1 -1
- package/dist/config/handlers/set.write.js +3 -4
- package/dist/config/resolver.d.ts +1 -0
- package/dist/config/resolver.js +11 -14
- package/dist/config/write-helpers.d.ts +2 -0
- package/dist/config/write-helpers.js +19 -0
- package/dist/crypto-shredding/handlers/forget-subject.write.d.ts +1 -1
- package/dist/crypto-shredding/handlers/forget-subject.write.js +29 -19
- package/dist/custom-fields/handlers/update-tenant-field.write.js +0 -1
- 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 +41 -0
- package/dist/delivery/attachment-ref.js +78 -0
- package/dist/delivery/channel-context.d.ts +8 -4
- package/dist/delivery/channel-context.js +94 -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/handlers/resubscribe-address.write.d.ts +1 -1
- package/dist/delivery/handlers/resubscribe-user.write.d.ts +1 -1
- package/dist/delivery/handlers/set-preference.write.d.ts +1 -1
- package/dist/delivery/handlers/unsubscribe-address.write.d.ts +1 -1
- package/dist/delivery/handlers/unsubscribe-user.write.d.ts +1 -1
- package/dist/delivery/index.d.ts +5 -2
- package/dist/delivery/index.js +4 -1
- package/dist/delivery/jobs.js +66 -14
- package/dist/delivery/notify-factory.js +4 -1
- package/dist/delivery/testing.d.ts +4 -1
- package/dist/delivery/types.d.ts +26 -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/handlers/set.write.d.ts +1 -1
- package/dist/feature-toggles/toggle-sync-signal.js +4 -4
- package/dist/file-derivatives/handlers/public-variant.query.js +1 -1
- 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 +51 -0
- package/dist/files-tenant-data/hooks.js +4 -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/handlers/discard.write.d.ts +1 -1
- package/dist/form-draft/handlers/save.write.d.ts +1 -1
- 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-access-token.js +8 -3
- 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 +24 -13
- package/dist/inbound-provider-imap/imap-client.js +4 -3
- package/dist/jobs/db/queries/stale-run-sweep.d.ts +1 -1
- package/dist/jobs/db/queries/stale-run-sweep.js +12 -5
- package/dist/jobs/feature.js +5 -3
- package/dist/jobs/handlers/list.query.js +3 -1
- package/dist/jobs/handlers/retry.write.d.ts +1 -1
- package/dist/jobs/handlers/stale-run-sweep.job.d.ts +1 -1
- package/dist/jobs/handlers/stale-run-sweep.job.js +2 -2
- package/dist/jobs/handlers/trigger.write.d.ts +1 -1
- package/dist/jobs/job-run-logger.js +32 -10
- package/dist/jobs/tenant-job-run-table.js +3 -2
- package/dist/ledger/entity.js +3 -1
- 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/handlers/set.write.d.ts +1 -1
- 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/create.write.d.ts +1 -1
- package/dist/personal-access-tokens/handlers/list.query.js +2 -1
- package/dist/personal-access-tokens/handlers/revoke.write.d.ts +1 -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 +94 -32
- package/dist/renderer-pdf/feature.js +7 -1
- package/dist/renderer-pdf/render-table-pdf.d.ts +27 -33
- package/dist/renderer-pdf/render-table-pdf.js +95 -34
- package/dist/secrets/feature.js +4 -3
- package/dist/secrets/handlers/delete.write.d.ts +1 -1
- package/dist/secrets/handlers/set.write.d.ts +1 -1
- package/dist/secrets/secrets-context.js +15 -3
- 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/sessions/handlers/revoke-all-for-user.write.d.ts +1 -1
- package/dist/sessions/handlers/revoke-all-others.write.d.ts +1 -1
- package/dist/sessions/handlers/revoke.write.d.ts +1 -1
- package/dist/shared/attachment-file-name.d.ts +3 -0
- package/dist/shared/attachment-file-name.js +35 -0
- package/dist/shared/chunked-entity-migration.d.ts +1 -0
- package/dist/shared/chunked-entity-migration.js +1 -1
- 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 +7 -0
- package/dist/shared/mail-template-body.js +27 -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/subscription-stripe/plugin-methods.js +18 -8
- package/dist/subscription-stripe/verify-webhook.d.ts +1 -1
- 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 +42 -6
- package/dist/template-resolver/compile-document-body.d.ts +12 -0
- package/dist/template-resolver/compile-document-body.js +205 -0
- package/dist/template-resolver/constants.d.ts +13 -4
- package/dist/template-resolver/constants.js +32 -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 +316 -0
- package/dist/template-resolver/feature.d.ts +20 -0
- package/dist/template-resolver/feature.js +53 -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.d.ts +1 -1
- 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 +134 -0
- package/dist/template-resolver/handlers/diff-to-system.query.d.ts +22 -0
- package/dist/template-resolver/handlers/diff-to-system.query.js +30 -0
- package/dist/template-resolver/handlers/discard-draft.write.d.ts +13 -0
- package/dist/template-resolver/handlers/discard-draft.write.js +37 -0
- package/dist/template-resolver/handlers/export-bundle.query.d.ts +35 -0
- package/dist/template-resolver/handlers/export-bundle.query.js +118 -0
- package/dist/template-resolver/handlers/find-by-id.query.d.ts +2 -0
- package/dist/template-resolver/handlers/find-by-id.query.js +9 -6
- 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 +31 -0
- package/dist/template-resolver/handlers/layout-source.query.js +87 -0
- package/dist/template-resolver/handlers/list.query.d.ts +4 -1
- package/dist/template-resolver/handlers/list.query.js +9 -8
- package/dist/template-resolver/handlers/override-diff.d.ts +20 -0
- package/dist/template-resolver/handlers/override-diff.js +41 -0
- package/dist/template-resolver/handlers/overview.query.d.ts +65 -0
- package/dist/template-resolver/handlers/overview.query.js +205 -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 +13 -0
- package/dist/template-resolver/handlers/send-test.write.js +108 -0
- package/dist/template-resolver/handlers/set.write.d.ts +5 -2
- 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 +142 -0
- package/dist/template-resolver/handlers/template-write.d.ts +45 -0
- package/dist/template-resolver/handlers/template-write.js +141 -0
- package/dist/template-resolver/handlers/tenant-overrides.query.d.ts +49 -0
- package/dist/template-resolver/handlers/tenant-overrides.query.js +112 -0
- package/dist/template-resolver/handlers/toggle-status.write.d.ts +27 -2
- package/dist/template-resolver/handlers/toggle-status.write.js +47 -15
- 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 +11 -62
- package/dist/template-resolver/handlers/usage.query.d.ts +17 -0
- package/dist/template-resolver/handlers/usage.query.js +24 -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 +19 -1
- package/dist/template-resolver/index.js +17 -1
- package/dist/template-resolver/layout-appearance.d.ts +10 -0
- package/dist/template-resolver/layout-appearance.js +26 -0
- 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/pdf-constants.d.ts +7 -0
- package/dist/template-resolver/pdf-constants.js +8 -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 +34 -0
- package/dist/template-resolver/qualified-names.js +34 -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 +125 -18
- 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 +150 -8
- package/dist/template-resolver/table.js +20 -2
- 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-draft.d.ts +51 -0
- package/dist/template-resolver/template-draft.js +72 -0
- package/dist/template-resolver/template-meta.d.ts +85 -0
- package/dist/template-resolver/template-meta.js +145 -0
- package/dist/template-resolver/template-preview.d.ts +103 -0
- package/dist/template-resolver/template-preview.js +418 -0
- package/dist/template-resolver/template-resources.d.ts +19 -0
- package/dist/template-resolver/template-resources.js +73 -0
- package/dist/template-resolver/template-usage.d.ts +16 -0
- package/dist/template-resolver/template-usage.js +35 -0
- package/dist/template-resolver/template-validation.d.ts +22 -0
- package/dist/template-resolver/template-validation.js +173 -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 +12 -7
- package/dist/template-resolver/web/document-editor-extensions.d.ts +18 -0
- package/dist/template-resolver/web/document-editor-extensions.js +145 -0
- package/dist/template-resolver/web/document-editor.d.ts +4 -0
- package/dist/template-resolver/web/document-editor.js +17 -0
- package/dist/template-resolver/web/document-tiptap-editor.d.ts +3 -0
- package/dist/template-resolver/web/document-tiptap-editor.js +492 -0
- package/dist/template-resolver/web/document-toolbar-blocks.d.ts +41 -0
- package/dist/template-resolver/web/document-toolbar-blocks.js +19 -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 +91 -0
- package/dist/template-resolver/web/index.d.ts +5 -0
- package/dist/template-resolver/web/index.js +5 -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 +17 -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/add-member.write.d.ts +1 -1
- package/dist/tenant/handlers/cancel-invitation.write.d.ts +1 -1
- package/dist/tenant/handlers/create.write.d.ts +1 -1
- package/dist/tenant/handlers/remove-member.write.d.ts +1 -1
- package/dist/tenant/handlers/team-list.query.js +3 -2
- package/dist/tenant/handlers/toggle-enabled.write.d.ts +2 -2
- package/dist/tenant/handlers/update-member-roles.write.d.ts +1 -1
- package/dist/tenant/handlers/update.write.d.ts +1 -1
- package/dist/tenant/seeding.js +31 -6
- package/dist/tenant-handover/handlers/claim.write.d.ts +1 -1
- package/dist/tenant-lifecycle/handlers/cancel-destruction.write.d.ts +2 -2
- package/dist/tenant-lifecycle/handlers/cancel-destruction.write.js +2 -1
- package/dist/tenant-lifecycle/handlers/request-destruction.write.d.ts +1 -1
- package/dist/tier-engine/feature.js +9 -3
- package/dist/tier-engine/handlers/set-tenant-tier.write.d.ts +1 -1
- package/dist/user/db/queries/stream-tenant-backfill.js +4 -4
- package/dist/user/handlers/create.write.d.ts +1 -1
- package/dist/user/handlers/detail.query.js +2 -1
- package/dist/user/handlers/update.write.d.ts +1 -1
- package/dist/user/resolve-display-names.js +5 -9
- package/dist/user/seeding.js +20 -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 +2 -2
- package/dist/user-data-rights/handlers/cancel-deletion.write.js +2 -1
- package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/lift-restriction.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/request-deletion-by-email.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/request-deletion.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/request-deletion.write.js +5 -7
- package/dist/user-data-rights/handlers/request-export.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/restrict-account.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/run-forget-cleanup.write.d.ts +1 -1
- 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/handlers/change-email.write.d.ts +1 -1
- package/dist/user-profile/web/profile-screen.js +2 -1
- package/dist/waitlist/feature.js +10 -0
- package/dist/waitlist/handlers/invite.write.d.ts +1 -1
- package/dist/waitlist/handlers/reject.write.d.ts +1 -1
- package/dist/waitlist/handlers/submit.write.d.ts +1 -1
- 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/dist/waitlist/options.js +1 -1
- package/dist/workflow-runner/event-trigger.js +2 -2
- package/dist/workflow-runner/handlers/resume-run.write.js +7 -2
- package/package.json +37 -17
- package/src/admin-shell/changes.json +6 -0
- package/src/audit/changes.json +6 -0
- package/src/auth-email-password/changes.json +51 -0
- package/src/billing-foundation/changes.json +43 -0
- package/src/cap-counter/changes.json +6 -0
- package/src/channel-email/changes.json +19 -0
- package/src/config/changes.json +6 -0
- package/src/crypto-shredding/changes.json +6 -0
- package/src/custom-fields/changes.json +6 -0
- package/src/data-retention/changes.json +6 -0
- package/src/delivery/changes.json +66 -0
- package/src/file-foundation/changes.json +6 -0
- package/src/files-tenant-data/changes.json +6 -0
- package/src/inbound-mail-foundation/changes.json +6 -0
- package/src/jobs/changes.json +19 -0
- package/src/ledger/changes.json +7 -0
- package/src/managed-pages/changes.json +6 -0
- package/src/renderer-foundation/changes.json +39 -0
- package/src/renderer-pdf/changes.json +12 -0
- package/src/secrets/changes.json +6 -0
- package/src/shared/changes.json +6 -0
- package/src/subscription-stripe/changes.json +6 -0
- package/src/template-resolver/changes.json +186 -0
- package/src/tier-engine/changes.json +6 -0
- package/src/user/changes.json +6 -0
- package/src/user-data-rights/changes.json +13 -0
- package/src/user-profile/changes.json +1 -1
- package/src/waitlist/changes.json +7 -0
- package/src/workflow-runner/changes.json +6 -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,21 @@
|
|
|
1
|
+
function readPath(row, path) {
|
|
2
|
+
let current = row;
|
|
3
|
+
for (const segment of path.split(".")) {
|
|
4
|
+
if (typeof current !== "object" || current === null || !Object.hasOwn(current, segment)) {
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
current = Object.getOwnPropertyDescriptor(current, segment)?.value;
|
|
8
|
+
}
|
|
9
|
+
return current;
|
|
10
|
+
}
|
|
11
|
+
// Variables whose path is missing on the row are left out, so the template
|
|
12
|
+
// renders them empty instead of printing "undefined".
|
|
13
|
+
export function mapRecordToVariables(mapping, row) {
|
|
14
|
+
const variables = {};
|
|
15
|
+
for (const [variable, path] of Object.entries(mapping.map)) {
|
|
16
|
+
const value = readPath(row, path);
|
|
17
|
+
if (value !== undefined)
|
|
18
|
+
variables[variable] = value;
|
|
19
|
+
}
|
|
20
|
+
return variables;
|
|
21
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare const CONTENT_TYPE_BY_FORMAT: {
|
|
2
|
+
readonly png: "image/png";
|
|
3
|
+
readonly jpeg: "image/jpeg";
|
|
4
|
+
readonly gif: "image/gif";
|
|
5
|
+
readonly webp: "image/webp";
|
|
6
|
+
};
|
|
7
|
+
type ResourceImageFormat = keyof typeof CONTENT_TYPE_BY_FORMAT;
|
|
8
|
+
export type NormalizedResourceImage = {
|
|
9
|
+
readonly data: Uint8Array;
|
|
10
|
+
readonly contentType: (typeof CONTENT_TYPE_BY_FORMAT)[ResourceImageFormat];
|
|
11
|
+
readonly width: number;
|
|
12
|
+
readonly height: number;
|
|
13
|
+
};
|
|
14
|
+
export declare function normalizeResourceImage(data: Uint8Array): Promise<NormalizedResourceImage>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { assertNotSvg, SHARP_INPUT_OPTIONS } from "../shared/index.js";
|
|
2
|
+
const MAX_RESOURCE_WIDTH = 1200;
|
|
3
|
+
const CONTENT_TYPE_BY_FORMAT = {
|
|
4
|
+
png: "image/png",
|
|
5
|
+
jpeg: "image/jpeg",
|
|
6
|
+
gif: "image/gif",
|
|
7
|
+
webp: "image/webp",
|
|
8
|
+
};
|
|
9
|
+
function isResourceImageFormat(format) {
|
|
10
|
+
return format !== undefined && Object.hasOwn(CONTENT_TYPE_BY_FORMAT, format);
|
|
11
|
+
}
|
|
12
|
+
// Re-encodes an uploaded image: the stored content type comes from the decoded format, never
|
|
13
|
+
// from the client, and re-encoding drops metadata and anything appended to the file.
|
|
14
|
+
// sharp is imported lazily because every app loads template-resolver, few of them need an image decoder.
|
|
15
|
+
export async function normalizeResourceImage(data) {
|
|
16
|
+
const { default: sharp } = await import("sharp");
|
|
17
|
+
const metadata = await sharp(data, SHARP_INPUT_OPTIONS).metadata();
|
|
18
|
+
assertNotSvg(metadata.format);
|
|
19
|
+
if (!isResourceImageFormat(metadata.format)) {
|
|
20
|
+
throw new Error(`unsupported image format "${metadata.format ?? "unknown"}"`);
|
|
21
|
+
}
|
|
22
|
+
const format = metadata.format;
|
|
23
|
+
const pipeline = sharp(data, SHARP_INPUT_OPTIONS)
|
|
24
|
+
.rotate()
|
|
25
|
+
.resize({ width: MAX_RESOURCE_WIDTH, withoutEnlargement: true })
|
|
26
|
+
.toFormat(format);
|
|
27
|
+
const { data: output, info } = await pipeline.toBuffer({ resolveWithObject: true });
|
|
28
|
+
return {
|
|
29
|
+
data: new Uint8Array(output),
|
|
30
|
+
contentType: CONTENT_TYPE_BY_FORMAT[format],
|
|
31
|
+
width: info.width,
|
|
32
|
+
height: info.height,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
2
|
-
import { type SessionUser, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { type Registry, type SessionUser, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
3
|
import { type SeedIfExists } from "@cosmicdrift/kumiko-framework/seeding";
|
|
4
4
|
import { type ContentFormat, type UpsertKind } from "./constants.js";
|
|
5
|
+
import type { TemplateBundle } from "./template-bundle.js";
|
|
5
6
|
export type SeedSystemTemplateOptions = {
|
|
6
7
|
readonly slug: string;
|
|
7
8
|
readonly kind: UpsertKind;
|
|
@@ -9,7 +10,10 @@ export type SeedSystemTemplateOptions = {
|
|
|
9
10
|
readonly content: string;
|
|
10
11
|
readonly contentFormat: ContentFormat;
|
|
11
12
|
readonly variableSchema?: Record<string, unknown>;
|
|
13
|
+
/** Omitted: links on an existing row stay untouched, so a re-seed on boot keeps images a
|
|
14
|
+
* system admin uploaded through resource-upload. */
|
|
12
15
|
readonly linkedResources?: Record<string, string>;
|
|
16
|
+
readonly meta?: unknown;
|
|
13
17
|
readonly parentTemplateId?: string | null;
|
|
14
18
|
readonly by?: SessionUser;
|
|
15
19
|
readonly ifExists?: SeedIfExists;
|
|
@@ -17,6 +21,15 @@ export type SeedSystemTemplateOptions = {
|
|
|
17
21
|
export declare function seedSystemTemplate(db: DbConnection, opts: SeedSystemTemplateOptions): Promise<{
|
|
18
22
|
id: string;
|
|
19
23
|
}>;
|
|
24
|
+
export type SeedSystemTemplateBundleOptions = {
|
|
25
|
+
readonly by?: SessionUser;
|
|
26
|
+
readonly ifExists?: SeedIfExists;
|
|
27
|
+
};
|
|
28
|
+
export declare function seedSystemTemplateBundle(db: DbConnection, bundle: TemplateBundle, opts?: SeedSystemTemplateBundleOptions): Promise<{
|
|
29
|
+
ids: Record<string, string>;
|
|
30
|
+
}>;
|
|
31
|
+
export declare function hasNotificationTemplates(registry: Registry): boolean;
|
|
32
|
+
export declare function seedNotificationTemplates(db: DbConnection, registry: Registry): Promise<void>;
|
|
20
33
|
export type SeedTextBlockOptions = {
|
|
21
34
|
readonly tenantId: TenantId;
|
|
22
35
|
readonly slug: string;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
// Boot
|
|
2
|
-
// SYSTEM_TENANT_ID, scope=system, status=active)
|
|
3
|
-
//
|
|
1
|
+
// Boot/seed helper for app authors. Writes system templates (tenantId=
|
|
2
|
+
// SYSTEM_TENANT_ID, scope=system, status=active) idempotently — same projection
|
|
3
|
+
// path as the upsertSystem handler, without the access check.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// Use it from runProdApp seeds or TenantCreated hooks that install the welcome,
|
|
6
|
+
// incident or feature mail slugs, so the mail renderer can resolve them
|
|
7
|
+
// through the template-resolver.
|
|
8
8
|
//
|
|
9
|
-
// Default ifExists="skip". `createSystemUser(SYSTEM_TENANT_ID)
|
|
10
|
-
//
|
|
9
|
+
// Default ifExists="skip". The actor is `createSystemUser(SYSTEM_TENANT_ID)`,
|
|
10
|
+
// deliberately not TestUsers (prod seeds are not test utilities).
|
|
11
11
|
import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
12
12
|
import { createTenantDb, deleteMany, } from "@cosmicdrift/kumiko-framework/db";
|
|
13
13
|
import { createSystemUser, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
|
|
@@ -15,7 +15,12 @@ import { getStreamVersion, getUnscopedStreamMaxVersionForSeed, } from "@cosmicdr
|
|
|
15
15
|
import { runEventStoreSeed } from "@cosmicdrift/kumiko-framework/seeding";
|
|
16
16
|
import { TEXT_BLOCK_KIND } from "./constants.js";
|
|
17
17
|
import { executor } from "./handlers/shared.js";
|
|
18
|
+
import { evaluateTemplateWrite } from "./handlers/template-gates.js";
|
|
19
|
+
import { TEMPLATE_RESOLVER_FEATURE } from "./qualified-names.js";
|
|
18
20
|
import { templateResourcesTable } from "./table.js";
|
|
21
|
+
import { activeStampChanges, servedStampChanges } from "./template-draft.js";
|
|
22
|
+
import { templateSlugSchema } from "./template-meta.js";
|
|
23
|
+
import { variablesFromSchema } from "./template-validation.js";
|
|
19
24
|
/** Drop projection orphans (no events anywhere) so boot seeds can recreate. */
|
|
20
25
|
async function resolveExistingForEventStoreSeed(db, tdb, existingRow, tenantId) {
|
|
21
26
|
if (existingRow == null)
|
|
@@ -39,6 +44,23 @@ export async function seedSystemTemplate(db, opts) {
|
|
|
39
44
|
const tenantId = SYSTEM_TENANT_ID;
|
|
40
45
|
const by = opts.by ?? createSystemUser(tenantId);
|
|
41
46
|
const tdb = createTenantDb(db, tenantId, "system");
|
|
47
|
+
const label = `seedSystemTemplate ${opts.slug}/${opts.kind}/${opts.locale}`;
|
|
48
|
+
const slug = templateSlugSchema.safeParse(opts.slug);
|
|
49
|
+
if (!slug.success)
|
|
50
|
+
throw new Error(`${label} rejected: ${slug.error.issues[0]?.message}`);
|
|
51
|
+
const evaluated = evaluateTemplateWrite({
|
|
52
|
+
kind: opts.kind,
|
|
53
|
+
scope: "system",
|
|
54
|
+
contentFormat: opts.contentFormat,
|
|
55
|
+
content: opts.content,
|
|
56
|
+
rawMeta: opts.meta,
|
|
57
|
+
variables: variablesFromSchema(opts.variableSchema ?? {}),
|
|
58
|
+
resourceKeys: Object.keys(opts.linkedResources ?? {}),
|
|
59
|
+
});
|
|
60
|
+
const issues = evaluated.ok ? evaluated.publishIssues : evaluated.issues;
|
|
61
|
+
if (!evaluated.ok || issues.length > 0) {
|
|
62
|
+
throw new Error(`${label} rejected: ${issues.map((issue) => `${issue.code} (${issue.params?.["detail"] ?? issue.path})`).join("; ")}`);
|
|
63
|
+
}
|
|
42
64
|
const existingRow = await fetchOne(db, templateResourcesTable, {
|
|
43
65
|
tenantId,
|
|
44
66
|
slug: opts.slug,
|
|
@@ -47,24 +69,40 @@ export async function seedSystemTemplate(db, opts) {
|
|
|
47
69
|
});
|
|
48
70
|
const existing = await resolveExistingForEventStoreSeed(db, tdb, existingRow ? { id: existingRow.id, version: existingRow.version } : null, tenantId);
|
|
49
71
|
const variableSchema = JSON.stringify(opts.variableSchema ?? {});
|
|
50
|
-
const
|
|
72
|
+
const meta = evaluated.metaJson;
|
|
73
|
+
const linkedResources = opts.linkedResources === undefined ? undefined : JSON.stringify(opts.linkedResources);
|
|
51
74
|
const parentTemplateId = opts.parentTemplateId ?? null;
|
|
52
75
|
const rowFields = {
|
|
53
76
|
slug: opts.slug,
|
|
54
77
|
kind: opts.kind,
|
|
55
78
|
locale: opts.locale,
|
|
56
|
-
content:
|
|
79
|
+
content: evaluated.content,
|
|
57
80
|
contentFormat: opts.contentFormat,
|
|
58
81
|
variableSchema,
|
|
59
|
-
linkedResources,
|
|
82
|
+
...(linkedResources !== undefined && { linkedResources }),
|
|
83
|
+
meta,
|
|
60
84
|
title: null,
|
|
61
85
|
folder: null,
|
|
62
86
|
scope: "system",
|
|
63
87
|
parentTemplateId,
|
|
64
88
|
status: "active",
|
|
65
89
|
};
|
|
66
|
-
const applyUpdate = async (row) => {
|
|
67
|
-
|
|
90
|
+
const applyUpdate = async (row, current) => {
|
|
91
|
+
// Skip no-op updates so re-seeding on every pod boot does not bump
|
|
92
|
+
// version/modifiedAt or grow the event store.
|
|
93
|
+
if (current?.content === rowFields.content &&
|
|
94
|
+
current?.contentFormat === rowFields.contentFormat &&
|
|
95
|
+
current?.title === rowFields.title &&
|
|
96
|
+
current?.variableSchema === rowFields.variableSchema &&
|
|
97
|
+
current?.meta === rowFields.meta &&
|
|
98
|
+
(linkedResources === undefined || current?.linkedResources === linkedResources)) {
|
|
99
|
+
return { id: String(row.id) };
|
|
100
|
+
}
|
|
101
|
+
const result = await executor.update({
|
|
102
|
+
id: row.id,
|
|
103
|
+
version: row.version,
|
|
104
|
+
changes: { ...rowFields, ...activeStampChanges(row) },
|
|
105
|
+
}, by, tdb);
|
|
68
106
|
if (!result.isSuccess) {
|
|
69
107
|
throw new Error(`seedSystemTemplate update failed: ${JSON.stringify(result)}`);
|
|
70
108
|
}
|
|
@@ -74,7 +112,13 @@ export async function seedSystemTemplate(db, opts) {
|
|
|
74
112
|
existing,
|
|
75
113
|
ifExists: opts.ifExists,
|
|
76
114
|
create: async () => {
|
|
77
|
-
const result = await executor.create({
|
|
115
|
+
const result = await executor.create({
|
|
116
|
+
...rowFields,
|
|
117
|
+
...activeStampChanges(null),
|
|
118
|
+
draft: null,
|
|
119
|
+
linkedResources: linkedResources ?? "{}",
|
|
120
|
+
tenantId,
|
|
121
|
+
}, by, tdb);
|
|
78
122
|
if (!result.isSuccess) {
|
|
79
123
|
// Parallel boot pods can both delete the orphan and race on create —
|
|
80
124
|
// unique on (tenantId, slug, kind, locale) → resolve winner and update.
|
|
@@ -94,7 +138,7 @@ export async function seedSystemTemplate(db, opts) {
|
|
|
94
138
|
existing: resolved,
|
|
95
139
|
ifExists: opts.ifExists,
|
|
96
140
|
create: () => Promise.reject(new Error("seedSystemTemplate: unreachable create")),
|
|
97
|
-
update: applyUpdate,
|
|
141
|
+
update: (row) => applyUpdate(row, again),
|
|
98
142
|
});
|
|
99
143
|
}
|
|
100
144
|
throw new Error(`seedSystemTemplate create failed: ${JSON.stringify(result)}`);
|
|
@@ -105,9 +149,66 @@ export async function seedSystemTemplate(db, opts) {
|
|
|
105
149
|
}
|
|
106
150
|
return { id: String(data.id) };
|
|
107
151
|
},
|
|
108
|
-
update: applyUpdate,
|
|
152
|
+
update: (row) => applyUpdate(row, existingRow),
|
|
109
153
|
});
|
|
110
154
|
}
|
|
155
|
+
// Seeds one system row per bundle locale. Bundled resources need a file
|
|
156
|
+
// provider and the system tenant's file space, which a boot seed does not
|
|
157
|
+
// have, so those go through the import-bundle-system write instead.
|
|
158
|
+
export async function seedSystemTemplateBundle(db, bundle, opts = {}) {
|
|
159
|
+
if (Object.keys(bundle.resources).length > 0) {
|
|
160
|
+
throw new Error(`seedSystemTemplateBundle ${bundle.slug}/${bundle.kind}: bundles with resources cannot be seeded; use the import-bundle-system write`);
|
|
161
|
+
}
|
|
162
|
+
const ids = {};
|
|
163
|
+
for (const [locale, entry] of Object.entries(bundle.locales)) {
|
|
164
|
+
const seeded = await seedSystemTemplate(db, {
|
|
165
|
+
slug: bundle.slug,
|
|
166
|
+
kind: bundle.kind,
|
|
167
|
+
locale,
|
|
168
|
+
content: entry.body,
|
|
169
|
+
contentFormat: bundle.contentFormat,
|
|
170
|
+
variableSchema: bundle.variableSchema ?? {},
|
|
171
|
+
meta: entry.meta,
|
|
172
|
+
...(opts.by !== undefined ? { by: opts.by } : {}),
|
|
173
|
+
...(opts.ifExists !== undefined ? { ifExists: opts.ifExists } : {}),
|
|
174
|
+
});
|
|
175
|
+
ids[locale] = seeded.id;
|
|
176
|
+
}
|
|
177
|
+
return { ids };
|
|
178
|
+
}
|
|
179
|
+
export function hasNotificationTemplates(registry) {
|
|
180
|
+
return Array.from(registry.getAllNotifications().values()).some((notification) => notification.template !== undefined);
|
|
181
|
+
}
|
|
182
|
+
// Boot seed for notifications that ship their mail text with the code
|
|
183
|
+
// (`r.notification({ template, meta, variables })`). The code is authoritative,
|
|
184
|
+
// so existing system rows are updated; tenant overrides are untouched.
|
|
185
|
+
export async function seedNotificationTemplates(db, registry) {
|
|
186
|
+
const hasTemplates = hasNotificationTemplates(registry);
|
|
187
|
+
if (hasTemplates && registry.getFeature(TEMPLATE_RESOLVER_FEATURE) === undefined) {
|
|
188
|
+
throw new Error(`Notifications declare a mail template but the "${TEMPLATE_RESOLVER_FEATURE}" feature is not mounted`);
|
|
189
|
+
}
|
|
190
|
+
if (hasTemplates) {
|
|
191
|
+
// Dynamic: renderer-foundation imports this feature's barrel, a static import would cycle.
|
|
192
|
+
const { collectRendererPlugins } = await import("../renderer-foundation/index.js");
|
|
193
|
+
if (!collectRendererPlugins(registry).some((plugin) => plugin.kinds.includes("mail"))) {
|
|
194
|
+
throw new Error('Notifications declare a mail template but no renderer plugin for kind "mail" is registered (mount renderer-foundation and @cosmicdrift/kumiko-renderer-mail-html)');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
for (const [slug, notification] of registry.getAllNotifications()) {
|
|
198
|
+
for (const [locale, { body }] of Object.entries(notification.template ?? {})) {
|
|
199
|
+
await seedSystemTemplate(db, {
|
|
200
|
+
slug,
|
|
201
|
+
kind: "mail",
|
|
202
|
+
locale,
|
|
203
|
+
content: body,
|
|
204
|
+
contentFormat: "html",
|
|
205
|
+
meta: notification.meta?.[locale],
|
|
206
|
+
variableSchema: notification.variables,
|
|
207
|
+
ifExists: "update",
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
111
212
|
// Boot/test seed for kind=text-block. Same projection path as set.write,
|
|
112
213
|
// without the access check. Default ifExists="skip".
|
|
113
214
|
export async function seedTextBlock(db, opts) {
|
|
@@ -145,7 +246,13 @@ export async function seedTextBlock(db, opts) {
|
|
|
145
246
|
const result = await executor.update({
|
|
146
247
|
id: row.id,
|
|
147
248
|
version: row.version,
|
|
148
|
-
changes: {
|
|
249
|
+
changes: {
|
|
250
|
+
title: fields.title,
|
|
251
|
+
content: fields.content,
|
|
252
|
+
folder: fields.folder,
|
|
253
|
+
// A re-seed must not revive an archived block; only a served one gets a new served version.
|
|
254
|
+
...(current?.status === "active" && servedStampChanges(row)),
|
|
255
|
+
},
|
|
149
256
|
}, by, tdb);
|
|
150
257
|
if (!result.isSuccess) {
|
|
151
258
|
throw new Error(`seedTextBlock update failed: ${JSON.stringify(result)}`);
|
|
@@ -156,7 +263,7 @@ export async function seedTextBlock(db, opts) {
|
|
|
156
263
|
existing,
|
|
157
264
|
ifExists: opts.ifExists,
|
|
158
265
|
create: async () => {
|
|
159
|
-
const result = await executor.create({ ...fields, tenantId: opts.tenantId }, by, tdb);
|
|
266
|
+
const result = await executor.create({ ...fields, ...activeStampChanges(null), draft: null, tenantId: opts.tenantId }, by, tdb);
|
|
160
267
|
if (!result.isSuccess) {
|
|
161
268
|
if (result.error?.code === "unique_violation") {
|
|
162
269
|
const again = await fetchOne(db, templateResourcesTable, {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ConfigAccessor, HandlerContext, Registry, SessionUser, TenantId, WriteResult } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import type { FileAttachmentRef } from "../renderer-foundation/index.js";
|
|
3
|
+
import { type LinkedResourceEntry } from "./linked-resources.js";
|
|
4
|
+
export declare function readTemplateResourceMaxBytes(config: ConfigAccessor | undefined, registry: Registry): Promise<number>;
|
|
5
|
+
export type StoredTemplateResource = {
|
|
6
|
+
readonly fileRefId: string;
|
|
7
|
+
readonly key?: string;
|
|
8
|
+
readonly contentType: string;
|
|
9
|
+
readonly width: number;
|
|
10
|
+
readonly height: number;
|
|
11
|
+
readonly size: number;
|
|
12
|
+
};
|
|
13
|
+
export declare function storeTemplateResource(args: {
|
|
14
|
+
readonly ctx: HandlerContext;
|
|
15
|
+
readonly user: SessionUser;
|
|
16
|
+
readonly tenantId: TenantId;
|
|
17
|
+
readonly bytes: Uint8Array;
|
|
18
|
+
readonly maxBytes: number;
|
|
19
|
+
readonly templateId?: string;
|
|
20
|
+
readonly key?: string;
|
|
21
|
+
}): Promise<WriteResult<StoredTemplateResource>>;
|
|
22
|
+
export type StoredTemplateAttachment = {
|
|
23
|
+
readonly fileRefId: string;
|
|
24
|
+
readonly key: string;
|
|
25
|
+
readonly fileName: string;
|
|
26
|
+
readonly contentType: string;
|
|
27
|
+
readonly size: number;
|
|
28
|
+
};
|
|
29
|
+
export declare function attachmentNeedsMailTemplate(): WriteResult<never>;
|
|
30
|
+
export declare function attachmentLimitFailure(): WriteResult<never>;
|
|
31
|
+
export declare function otherAttachmentCount(linked: Readonly<Record<string, LinkedResourceEntry>>, key: string): number;
|
|
32
|
+
export declare function storeBundleAttachment(args: {
|
|
33
|
+
readonly ctx: HandlerContext;
|
|
34
|
+
readonly user: SessionUser;
|
|
35
|
+
readonly tenantId: TenantId;
|
|
36
|
+
readonly bytes: Uint8Array;
|
|
37
|
+
readonly maxBytes: number;
|
|
38
|
+
readonly key: string;
|
|
39
|
+
readonly fileName: string;
|
|
40
|
+
}): Promise<WriteResult<FileAttachmentRef>>;
|
|
41
|
+
export declare function storeTemplateAttachment(args: {
|
|
42
|
+
readonly ctx: HandlerContext;
|
|
43
|
+
readonly user: SessionUser;
|
|
44
|
+
readonly tenantId: TenantId;
|
|
45
|
+
readonly bytes: Uint8Array;
|
|
46
|
+
readonly maxBytes: number;
|
|
47
|
+
readonly templateId: string;
|
|
48
|
+
readonly key: string;
|
|
49
|
+
readonly fileName: string;
|
|
50
|
+
}): Promise<WriteResult<StoredTemplateAttachment>>;
|
|
51
|
+
export type LinkedTemplatePdfAttachment = {
|
|
52
|
+
readonly key: string;
|
|
53
|
+
readonly templateSlug: string;
|
|
54
|
+
readonly fileName: string;
|
|
55
|
+
};
|
|
56
|
+
export declare function linkTemplatePdfAttachment(args: {
|
|
57
|
+
readonly ctx: HandlerContext;
|
|
58
|
+
readonly user: SessionUser;
|
|
59
|
+
readonly tenantId: TenantId;
|
|
60
|
+
readonly templateId: string;
|
|
61
|
+
readonly key: string;
|
|
62
|
+
readonly templateSlug: string;
|
|
63
|
+
readonly fileName: string;
|
|
64
|
+
}): Promise<WriteResult<LinkedTemplatePdfAttachment>>;
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { fetchOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
|
+
import { NotFoundError, UnprocessableError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
|
|
3
|
+
import { createFileRefFromBytes } from "@cosmicdrift/kumiko-framework/files";
|
|
4
|
+
import { fileContextForTenant } from "../file-foundation/index.js";
|
|
5
|
+
import { requireForTenant } from "../shared/index.js";
|
|
6
|
+
import { SYSTEM_TENANT_ID } from "./constants.js";
|
|
7
|
+
import { DEFAULT_RESOURCE_MAX_BYTES, MAX_FILE_ATTACHMENTS_PER_TEMPLATE, RESOURCE_MAX_BYTES_CONFIG_KEY, } from "./handlers/resource-shared.js";
|
|
8
|
+
import { executor } from "./handlers/shared.js";
|
|
9
|
+
import { attachmentEntriesOf, parseLinkedResourceEntries, templatePdfAttachmentLinkSchema, } from "./linked-resources.js";
|
|
10
|
+
import { TemplateResolverErrors } from "./qualified-names.js";
|
|
11
|
+
import { normalizeResourceImage } from "./resource-image.js";
|
|
12
|
+
import { templateResourcesTable } from "./table.js";
|
|
13
|
+
const EXTENSION_BY_CONTENT_TYPE = {
|
|
14
|
+
"image/png": "png",
|
|
15
|
+
"image/jpeg": "jpg",
|
|
16
|
+
"image/gif": "gif",
|
|
17
|
+
"image/webp": "webp",
|
|
18
|
+
};
|
|
19
|
+
export async function readTemplateResourceMaxBytes(config, registry) {
|
|
20
|
+
if (config === undefined || registry.getConfigKey(RESOURCE_MAX_BYTES_CONFIG_KEY) === undefined) {
|
|
21
|
+
return DEFAULT_RESOURCE_MAX_BYTES;
|
|
22
|
+
}
|
|
23
|
+
const value = await config(RESOURCE_MAX_BYTES_CONFIG_KEY);
|
|
24
|
+
return typeof value === "number" ? value : DEFAULT_RESOURCE_MAX_BYTES;
|
|
25
|
+
}
|
|
26
|
+
// Normalizes the image, stores it as a file ref of the target tenant and, with templateId + key,
|
|
27
|
+
// links it into that tenant's template. The caller is responsible for authorization; the function trusts tenantId.
|
|
28
|
+
export async function storeTemplateResource(args) {
|
|
29
|
+
const { ctx, user, tenantId, bytes, maxBytes, templateId, key } = args;
|
|
30
|
+
if (bytes.length > maxBytes) {
|
|
31
|
+
return writeFailure(new UnprocessableError(TemplateResolverErrors.resourceTooLarge, {
|
|
32
|
+
i18nKey: "templateResolver.errors.resource.tooLarge",
|
|
33
|
+
details: { maxBytes, size: bytes.length },
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
let image;
|
|
37
|
+
try {
|
|
38
|
+
image = await normalizeResourceImage(Buffer.from(bytes));
|
|
39
|
+
}
|
|
40
|
+
catch (cause) {
|
|
41
|
+
return writeFailure(new UnprocessableError(TemplateResolverErrors.resourceUnsupportedImage, {
|
|
42
|
+
i18nKey: "templateResolver.errors.resource.unsupportedImage",
|
|
43
|
+
cause: cause instanceof Error ? cause : undefined,
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
const { db, streamTenantId } = requireForTenant(ctx, tenantId);
|
|
47
|
+
let row;
|
|
48
|
+
if (templateId !== undefined) {
|
|
49
|
+
row = await fetchOne(db, templateResourcesTable, {
|
|
50
|
+
tenantId,
|
|
51
|
+
id: templateId,
|
|
52
|
+
});
|
|
53
|
+
if (row === undefined)
|
|
54
|
+
return writeFailure(new NotFoundError("template-resource", templateId));
|
|
55
|
+
}
|
|
56
|
+
const created = await createFileRefFromBytes({
|
|
57
|
+
db,
|
|
58
|
+
files: fileContextForTenant(ctx, ctx.user.tenantId, tenantId),
|
|
59
|
+
registry: ctx.registry,
|
|
60
|
+
user: { ...user, tenantId },
|
|
61
|
+
}, {
|
|
62
|
+
// Neutral name: the source's file name may be personal data.
|
|
63
|
+
fileName: `${key ?? "resource"}.${EXTENSION_BY_CONTENT_TYPE[image.contentType] ?? "bin"}`,
|
|
64
|
+
mimeType: image.contentType,
|
|
65
|
+
data: image.data,
|
|
66
|
+
});
|
|
67
|
+
if (!created.isSuccess)
|
|
68
|
+
return created;
|
|
69
|
+
if (row !== undefined && key !== undefined) {
|
|
70
|
+
// The replaced FileRef stays: copy-locale and tenant overrides can share ids and
|
|
71
|
+
// there is no reference count to tell when the last user is gone.
|
|
72
|
+
const linkedResources = {
|
|
73
|
+
...parseLinkedResourceEntries(row.linkedResources),
|
|
74
|
+
[key]: created.data.id,
|
|
75
|
+
};
|
|
76
|
+
const updated = await executor.update({
|
|
77
|
+
id: row.id,
|
|
78
|
+
version: row.version,
|
|
79
|
+
changes: { linkedResources: JSON.stringify(linkedResources) },
|
|
80
|
+
}, user, db, { streamTenantId });
|
|
81
|
+
if (!updated.isSuccess)
|
|
82
|
+
return updated;
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
isSuccess: true,
|
|
86
|
+
data: {
|
|
87
|
+
fileRefId: created.data.id,
|
|
88
|
+
...(key !== undefined && { key }),
|
|
89
|
+
contentType: image.contentType,
|
|
90
|
+
width: image.width,
|
|
91
|
+
height: image.height,
|
|
92
|
+
size: image.data.length,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const PDF_MAGIC = [0x25, 0x50, 0x44, 0x46, 0x2d];
|
|
97
|
+
const PNG_MAGIC = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
|
|
98
|
+
const JPEG_MAGIC = [0xff, 0xd8, 0xff];
|
|
99
|
+
const ATTACHMENT_TYPES = [
|
|
100
|
+
{ contentType: "application/pdf", extension: "pdf", magic: PDF_MAGIC },
|
|
101
|
+
{ contentType: "image/png", extension: "png", magic: PNG_MAGIC },
|
|
102
|
+
{ contentType: "image/jpeg", extension: "jpg", magic: JPEG_MAGIC },
|
|
103
|
+
];
|
|
104
|
+
function detectAttachmentType(bytes) {
|
|
105
|
+
return ATTACHMENT_TYPES.find(({ magic }) => magic.every((byte, index) => bytes[index] === byte));
|
|
106
|
+
}
|
|
107
|
+
export function attachmentNeedsMailTemplate() {
|
|
108
|
+
return writeFailure(new UnprocessableError(TemplateResolverErrors.attachmentInvalid, {
|
|
109
|
+
i18nKey: "templateResolver.errors.attachment.invalid",
|
|
110
|
+
details: { code: "mail_only" },
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
export function attachmentLimitFailure() {
|
|
114
|
+
return writeFailure(new UnprocessableError(TemplateResolverErrors.attachmentLimit, {
|
|
115
|
+
i18nKey: "templateResolver.errors.attachment.limit",
|
|
116
|
+
i18nParams: { max: MAX_FILE_ATTACHMENTS_PER_TEMPLATE },
|
|
117
|
+
details: { max: MAX_FILE_ATTACHMENTS_PER_TEMPLATE },
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
// Files and rendered PDFs share one cap; the entry being replaced under `key` does not count.
|
|
121
|
+
export function otherAttachmentCount(linked, key) {
|
|
122
|
+
return attachmentEntriesOf(linked).filter(([linkedKey]) => linkedKey !== key).length;
|
|
123
|
+
}
|
|
124
|
+
function inspectAttachmentBytes(bytes, maxBytes) {
|
|
125
|
+
if (bytes.length > maxBytes) {
|
|
126
|
+
return {
|
|
127
|
+
failure: writeFailure(new UnprocessableError(TemplateResolverErrors.resourceTooLarge, {
|
|
128
|
+
details: { maxBytes, size: bytes.length },
|
|
129
|
+
})),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const type = detectAttachmentType(bytes);
|
|
133
|
+
if (type === undefined) {
|
|
134
|
+
return {
|
|
135
|
+
failure: writeFailure(new UnprocessableError(TemplateResolverErrors.attachmentUnsupportedType, {
|
|
136
|
+
i18nKey: "templateResolver.errors.attachment.unsupportedType",
|
|
137
|
+
})),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return { type };
|
|
141
|
+
}
|
|
142
|
+
// Stores the bytes unchanged as a file of the target tenant, without linking it anywhere.
|
|
143
|
+
async function createAttachmentFileRef(args) {
|
|
144
|
+
const { ctx, user, tenantId, bytes, key, type } = args;
|
|
145
|
+
const { db } = requireForTenant(ctx, tenantId);
|
|
146
|
+
const created = await createFileRefFromBytes({
|
|
147
|
+
db,
|
|
148
|
+
files: fileContextForTenant(ctx, ctx.user.tenantId, tenantId),
|
|
149
|
+
registry: ctx.registry,
|
|
150
|
+
user: { ...user, tenantId },
|
|
151
|
+
}, {
|
|
152
|
+
// Neutral stored name: the admin's file name may be personal data and lives in the link entry.
|
|
153
|
+
fileName: `${key}.${type.extension}`,
|
|
154
|
+
mimeType: type.contentType,
|
|
155
|
+
data: bytes,
|
|
156
|
+
});
|
|
157
|
+
return created.isSuccess ? { isSuccess: true, data: { id: created.data.id } } : created;
|
|
158
|
+
}
|
|
159
|
+
// Stores a PDF/PNG/JPEG for a bundle import: validated like an upload, linked later by the importer.
|
|
160
|
+
export async function storeBundleAttachment(args) {
|
|
161
|
+
const inspected = inspectAttachmentBytes(args.bytes, args.maxBytes);
|
|
162
|
+
if ("failure" in inspected)
|
|
163
|
+
return inspected.failure;
|
|
164
|
+
const created = await createAttachmentFileRef({ ...args, type: inspected.type });
|
|
165
|
+
if (!created.isSuccess)
|
|
166
|
+
return created;
|
|
167
|
+
return {
|
|
168
|
+
isSuccess: true,
|
|
169
|
+
data: { kind: "file", fileRefId: created.data.id, fileName: args.fileName },
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
// Stores a PDF/PNG/JPEG byte-for-byte (no re-encoding, the recipient gets what the admin uploaded)
|
|
173
|
+
// and links it under `key` in a mail template of the target tenant. The caller authorizes tenantId.
|
|
174
|
+
export async function storeTemplateAttachment(args) {
|
|
175
|
+
const { ctx, user, tenantId, bytes, maxBytes, templateId, key, fileName } = args;
|
|
176
|
+
const inspected = inspectAttachmentBytes(bytes, maxBytes);
|
|
177
|
+
if ("failure" in inspected)
|
|
178
|
+
return inspected.failure;
|
|
179
|
+
const { type } = inspected;
|
|
180
|
+
const { db, streamTenantId } = requireForTenant(ctx, tenantId);
|
|
181
|
+
const row = await fetchOne(db, templateResourcesTable, {
|
|
182
|
+
tenantId,
|
|
183
|
+
id: templateId,
|
|
184
|
+
});
|
|
185
|
+
if (row === undefined)
|
|
186
|
+
return writeFailure(new NotFoundError("template-resource", templateId));
|
|
187
|
+
if (row.kind !== "mail")
|
|
188
|
+
return attachmentNeedsMailTemplate();
|
|
189
|
+
const linked = parseLinkedResourceEntries(row.linkedResources);
|
|
190
|
+
if (otherAttachmentCount(linked, key) >= MAX_FILE_ATTACHMENTS_PER_TEMPLATE) {
|
|
191
|
+
return attachmentLimitFailure();
|
|
192
|
+
}
|
|
193
|
+
const created = await createAttachmentFileRef({ ctx, user, tenantId, bytes, key, type });
|
|
194
|
+
if (!created.isSuccess)
|
|
195
|
+
return created;
|
|
196
|
+
const entry = { kind: "file", fileRefId: created.data.id, fileName };
|
|
197
|
+
const updated = await executor.update({
|
|
198
|
+
id: row.id,
|
|
199
|
+
version: row.version,
|
|
200
|
+
changes: { linkedResources: JSON.stringify({ ...linked, [key]: entry }) },
|
|
201
|
+
}, user, db, { streamTenantId });
|
|
202
|
+
if (!updated.isSuccess)
|
|
203
|
+
return updated;
|
|
204
|
+
return {
|
|
205
|
+
isSuccess: true,
|
|
206
|
+
data: {
|
|
207
|
+
fileRefId: created.data.id,
|
|
208
|
+
key,
|
|
209
|
+
fileName,
|
|
210
|
+
contentType: type.contentType,
|
|
211
|
+
size: bytes.length,
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function pdfAttachmentInvalid(code) {
|
|
216
|
+
return writeFailure(new UnprocessableError(TemplateResolverErrors.attachmentInvalid, {
|
|
217
|
+
i18nKey: "templateResolver.errors.attachment.invalid",
|
|
218
|
+
details: { code },
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
// Links a PDF that is rendered from a document template at send time into a mail template of the
|
|
222
|
+
// target tenant. Nothing is stored besides the link entry. The caller authorizes tenantId.
|
|
223
|
+
export async function linkTemplatePdfAttachment(args) {
|
|
224
|
+
const { ctx, user, tenantId, templateId, key } = args;
|
|
225
|
+
const link = templatePdfAttachmentLinkSchema.safeParse({
|
|
226
|
+
kind: "rendered-pdf",
|
|
227
|
+
templateSlug: args.templateSlug,
|
|
228
|
+
fileName: args.fileName,
|
|
229
|
+
});
|
|
230
|
+
if (!link.success)
|
|
231
|
+
return pdfAttachmentInvalid("invalid_pdf_link");
|
|
232
|
+
const { db, streamTenantId } = requireForTenant(ctx, tenantId);
|
|
233
|
+
const row = await fetchOne(db, templateResourcesTable, {
|
|
234
|
+
tenantId,
|
|
235
|
+
id: templateId,
|
|
236
|
+
});
|
|
237
|
+
if (row === undefined)
|
|
238
|
+
return writeFailure(new NotFoundError("template-resource", templateId));
|
|
239
|
+
if (row.kind !== "mail")
|
|
240
|
+
return attachmentNeedsMailTemplate();
|
|
241
|
+
const linked = parseLinkedResourceEntries(row.linkedResources);
|
|
242
|
+
if (otherAttachmentCount(linked, key) >= MAX_FILE_ATTACHMENTS_PER_TEMPLATE) {
|
|
243
|
+
return attachmentLimitFailure();
|
|
244
|
+
}
|
|
245
|
+
const visibleTemplates = await selectMany(db, templateResourcesTable, { tenantId: [tenantId, SYSTEM_TENANT_ID], slug: link.data.templateSlug, kind: "document-pdf" }, { limit: 1 });
|
|
246
|
+
if (visibleTemplates.length === 0) {
|
|
247
|
+
return writeFailure(new UnprocessableError(TemplateResolverErrors.attachmentPdfTemplateMissing, {
|
|
248
|
+
i18nKey: "templateResolver.errors.attachment.pdfTemplateMissing",
|
|
249
|
+
i18nParams: { slug: link.data.templateSlug },
|
|
250
|
+
details: { templateSlug: link.data.templateSlug },
|
|
251
|
+
}));
|
|
252
|
+
}
|
|
253
|
+
const updated = await executor.update({
|
|
254
|
+
id: row.id,
|
|
255
|
+
version: row.version,
|
|
256
|
+
changes: { linkedResources: JSON.stringify({ ...linked, [key]: link.data }) },
|
|
257
|
+
}, user, db, { streamTenantId });
|
|
258
|
+
if (!updated.isSuccess)
|
|
259
|
+
return updated;
|
|
260
|
+
return {
|
|
261
|
+
isSuccess: true,
|
|
262
|
+
data: { key, templateSlug: link.data.templateSlug, fileName: link.data.fileName },
|
|
263
|
+
};
|
|
264
|
+
}
|