@cosmicdrift/kumiko-samples 0.393.0 → 0.395.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/packages/bundled-features/package.json +15 -5
- package/packages/bundled-features/src/account-security/README.md +1 -1
- package/packages/bundled-features/src/account-security/changes.json +6 -0
- package/packages/bundled-features/src/account-security/feature.ts +9 -1
- package/packages/bundled-features/src/audit/event-classification.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/changes.json +6 -0
- package/packages/bundled-features/src/auth-email-password/web/auth-client.ts +10 -52
- package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +4 -4
- package/packages/bundled-features/src/auth-email-password/web/auth-redirect.ts +3 -12
- package/packages/bundled-features/src/auth-email-password/web/index.ts +2 -0
- package/packages/bundled-features/src/auth-email-password/web/login-failure.ts +54 -0
- package/packages/bundled-features/src/auth-email-password/web/login-url.ts +19 -0
- package/packages/bundled-features/src/auth-email-password/web/static-login.ts +183 -0
- package/packages/bundled-features/src/auth-mfa/web/mfa-client.ts +7 -63
- package/packages/bundled-features/src/billing-foundation/changes.json +30 -0
- package/packages/bundled-features/src/billing-foundation/checkout-core.ts +8 -16
- package/packages/bundled-features/src/billing-foundation/events.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/feature.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/handlers/create-checkout-session.write.ts +9 -2
- package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/index.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/one-off-checkout-gate.ts +44 -0
- package/packages/bundled-features/src/billing-foundation/one-off-customer.ts +17 -0
- package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +1 -0
- package/packages/bundled-features/src/billing-foundation/types.ts +26 -0
- package/packages/bundled-features/src/billing-foundation/web/billing-plans-panel.tsx +1 -0
- package/packages/bundled-features/src/billing-foundation/web/plan-switch-confirm-dialog.tsx +8 -2
- package/packages/bundled-features/src/billing-foundation/web/plan-switch-recurring.ts +23 -0
- package/packages/bundled-features/src/billing-foundation/webhook-handler.ts +1 -0
- package/packages/bundled-features/src/cap-overview/changes.json +6 -0
- package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +3 -3
- package/packages/bundled-features/src/cap-overview/types.ts +2 -0
- package/packages/bundled-features/src/compliance-profiles/web/compliance-profile-catalog.tsx +10 -1
- package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +12 -3
- package/packages/bundled-features/src/files/changes.json +18 -0
- package/packages/bundled-features/src/inbound-mail-foundation/account-health.ts +146 -0
- package/packages/bundled-features/src/inbound-mail-foundation/account-poll-schedule.ts +74 -0
- package/packages/bundled-features/src/inbound-mail-foundation/account-poll.ts +238 -0
- package/packages/bundled-features/src/inbound-mail-foundation/account-record.ts +2 -0
- package/packages/bundled-features/src/inbound-mail-foundation/account-wake.ts +21 -0
- package/packages/bundled-features/src/inbound-mail-foundation/changes.json +143 -0
- package/packages/bundled-features/src/inbound-mail-foundation/connect-callback-verify.ts +65 -0
- package/packages/bundled-features/src/inbound-mail-foundation/connect-route-responses.ts +26 -0
- package/packages/bundled-features/src/inbound-mail-foundation/connect-routes.ts +93 -62
- package/packages/bundled-features/src/inbound-mail-foundation/constants.ts +66 -3
- package/packages/bundled-features/src/inbound-mail-foundation/content-refetch.ts +137 -0
- package/packages/bundled-features/src/inbound-mail-foundation/describe-error.ts +5 -0
- package/packages/bundled-features/src/inbound-mail-foundation/entities.ts +45 -2
- package/packages/bundled-features/src/inbound-mail-foundation/events.ts +69 -2
- package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +133 -118
- package/packages/bundled-features/src/inbound-mail-foundation/file-storage-failure.ts +23 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/account-address.ts +79 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/acknowledge-quarantine.write.ts +55 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/attach-message-content.write.ts +184 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +137 -84
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +65 -45
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message-prepare.ts +177 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message-schema.ts +42 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +79 -160
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +93 -30
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +46 -30
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-quarantine.query.ts +106 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/mark-credential-stored.write.ts +47 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/message-content.query.ts +99 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/message-content.ts +229 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/message-detail.query.ts +34 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/reactivate-account.write.ts +129 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/retry-quarantined-message.write.ts +49 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +48 -9
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/set-trusted-authserv-id.write.ts +66 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/thread-rollup.ts +136 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/trusted-authserv-id.ts +4 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +82 -60
- package/packages/bundled-features/src/inbound-mail-foundation/i18n.ts +80 -2
- package/packages/bundled-features/src/inbound-mail-foundation/inbound-oauth-flow.ts +48 -0
- package/packages/bundled-features/src/inbound-mail-foundation/index.ts +93 -15
- package/packages/bundled-features/src/inbound-mail-foundation/ingest-failures.ts +137 -0
- package/packages/bundled-features/src/inbound-mail-foundation/ingest-payload.ts +46 -0
- package/packages/bundled-features/src/inbound-mail-foundation/mail-roles.ts +132 -0
- package/packages/bundled-features/src/inbound-mail-foundation/normalize-inbound-message.ts +119 -0
- package/packages/bundled-features/src/inbound-mail-foundation/oauth-access-token.ts +26 -8
- package/packages/bundled-features/src/inbound-mail-foundation/outbound-attachment-access.ts +28 -0
- package/packages/bundled-features/src/inbound-mail-foundation/outbound-attachments.ts +111 -0
- package/packages/bundled-features/src/inbound-mail-foundation/outbound-mail.ts +19 -1
- package/packages/bundled-features/src/inbound-mail-foundation/probe.ts +2 -1
- package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +33 -2
- package/packages/bundled-features/src/inbound-mail-foundation/provider-errors.ts +74 -0
- package/packages/bundled-features/src/inbound-mail-foundation/quarantine-entities.ts +56 -0
- package/packages/bundled-features/src/inbound-mail-foundation/quarantine-header-import.ts +63 -0
- package/packages/bundled-features/src/inbound-mail-foundation/quarantine-retry.ts +137 -0
- package/packages/bundled-features/src/inbound-mail-foundation/quarantine-screens.ts +66 -0
- package/packages/bundled-features/src/inbound-mail-foundation/quarantine.ts +72 -0
- package/packages/bundled-features/src/inbound-mail-foundation/retention-job.ts +78 -0
- package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +43 -8
- package/packages/bundled-features/src/inbound-mail-foundation/screens.ts +238 -89
- package/packages/bundled-features/src/inbound-mail-foundation/send-from-account.ts +24 -5
- package/packages/bundled-features/src/inbound-mail-foundation/sender-auth.ts +215 -0
- package/packages/bundled-features/src/inbound-mail-foundation/supervisor-ingest.ts +311 -0
- package/packages/bundled-features/src/inbound-mail-foundation/sync-account-now.ts +61 -0
- package/packages/bundled-features/src/inbound-mail-foundation/sync-cursor-store.ts +79 -0
- package/packages/bundled-features/src/inbound-mail-foundation/syncable-accounts.ts +123 -0
- package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +5 -1
- package/packages/bundled-features/src/inbound-mail-foundation/types.ts +76 -105
- package/packages/bundled-features/src/inbound-mail-foundation/wake-consumer.ts +33 -0
- package/packages/bundled-features/src/inbound-mail-foundation/watch-catch-up.ts +68 -0
- package/packages/bundled-features/src/inbound-mail-foundation/watch-lease.ts +124 -0
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor-types.ts +87 -0
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +143 -433
- package/packages/bundled-features/src/inbound-mail-foundation/web/client-plugin.ts +73 -0
- package/packages/bundled-features/src/inbound-mail-foundation/web/constants.ts +7 -0
- package/packages/bundled-features/src/inbound-mail-foundation/web/index.ts +5 -0
- package/packages/bundled-features/src/inbound-provider-imap/changes.json +36 -0
- package/packages/bundled-features/src/inbound-provider-imap/connect-mailbox.ts +124 -41
- package/packages/bundled-features/src/inbound-provider-imap/connect-my-mailbox.ts +45 -0
- package/packages/bundled-features/src/inbound-provider-imap/constants.ts +2 -1
- package/packages/bundled-features/src/inbound-provider-imap/credential-document.ts +2 -1
- package/packages/bundled-features/src/inbound-provider-imap/download-batch.ts +38 -0
- package/packages/bundled-features/src/inbound-provider-imap/feature.ts +61 -136
- package/packages/bundled-features/src/inbound-provider-imap/fetch-content.ts +84 -0
- package/packages/bundled-features/src/inbound-provider-imap/i18n.ts +9 -0
- package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +35 -84
- package/packages/bundled-features/src/inbound-provider-imap/index.ts +1 -2
- package/packages/bundled-features/src/inbound-provider-imap/mime-content.ts +85 -0
- package/packages/bundled-features/src/inbound-provider-imap/parse-message.ts +149 -0
- package/packages/bundled-features/src/inbound-provider-imap/reconnect-account.ts +4 -6
- package/packages/bundled-features/src/inbound-provider-imap/reconnect-mailbox.ts +75 -45
- package/packages/bundled-features/src/inbound-provider-imap/reconnect-prefill.query.ts +19 -8
- package/packages/bundled-features/src/inbound-provider-imap/screens.ts +203 -109
- package/packages/bundled-features/src/inbound-provider-imap/send-mail.ts +11 -2
- package/packages/bundled-features/src/inbound-provider-imap/sender-auth-headers.ts +36 -0
- package/packages/bundled-features/src/inbound-provider-imap/watch-mailbox.ts +109 -0
- package/packages/bundled-features/src/inbound-provider-inmemory/feature.ts +75 -3
- package/packages/bundled-features/src/inbound-provider-inmemory/index.ts +6 -0
- package/packages/bundled-features/src/jobs/changes.json +18 -0
- package/packages/bundled-features/src/managed-pages/changes.json +7 -0
- package/packages/bundled-features/src/managed-pages/constants.ts +7 -0
- package/packages/bundled-features/src/managed-pages/feature.ts +6 -4
- package/packages/bundled-features/src/managed-pages/handlers/by-slug.query.ts +2 -0
- package/packages/bundled-features/src/managed-pages/handlers/page-body-guard.ts +61 -0
- package/packages/bundled-features/src/managed-pages/handlers/page-body-issues.ts +26 -0
- package/packages/bundled-features/src/managed-pages/handlers/page-crud.ts +17 -10
- package/packages/bundled-features/src/managed-pages/handlers/set.write.ts +15 -4
- package/packages/bundled-features/src/managed-pages/render-page-body.ts +28 -0
- package/packages/bundled-features/src/managed-pages/screens/page-screens.ts +6 -2
- package/packages/bundled-features/src/managed-pages/seeding.ts +13 -2
- package/packages/bundled-features/src/managed-pages/table.ts +6 -2
- package/packages/bundled-features/src/managed-pages/web/client-plugin.tsx +6 -4
- package/packages/bundled-features/src/managed-pages/web/constants.ts +14 -0
- package/packages/bundled-features/src/managed-pages/web/i18n.ts +14 -0
- package/packages/bundled-features/src/managed-pages/web/lossy-markdown.ts +71 -0
- package/packages/bundled-features/src/managed-pages/web/markdown-to-document.ts +27 -0
- package/packages/bundled-features/src/managed-pages/web/page-body-section.tsx +73 -0
- package/packages/bundled-features/src/subscription-paddle/changes.json +15 -0
- package/packages/bundled-features/src/subscription-paddle/checkout-config.query.ts +17 -0
- package/packages/bundled-features/src/subscription-paddle/checkout-methods.ts +107 -0
- package/packages/bundled-features/src/subscription-paddle/checkout-page.ts +154 -0
- package/packages/bundled-features/src/subscription-paddle/client.ts +78 -0
- package/packages/bundled-features/src/subscription-paddle/constants.ts +32 -0
- package/packages/bundled-features/src/subscription-paddle/feature.ts +265 -0
- package/packages/bundled-features/src/subscription-paddle/index.ts +17 -0
- package/packages/bundled-features/src/subscription-paddle/plan-switch-methods.ts +135 -0
- package/packages/bundled-features/src/subscription-paddle/price-methods.ts +148 -0
- package/packages/bundled-features/src/subscription-paddle/runtime.ts +203 -0
- package/packages/bundled-features/src/subscription-paddle/subscription-state.ts +73 -0
- package/packages/bundled-features/src/subscription-paddle/tenant-binding.ts +34 -0
- package/packages/bundled-features/src/subscription-paddle/verify-webhook.ts +61 -0
- package/packages/bundled-features/src/subscription-paddle/webhook-events.ts +172 -0
- package/packages/bundled-features/src/template-resolver/README.md +2 -2
- package/packages/bundled-features/src/template-resolver/changes.json +20 -0
- package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +16 -16
- package/packages/bundled-features/src/template-resolver/feature.ts +14 -1
- package/packages/bundled-features/src/template-resolver/handlers/collection-item.query.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/collection-list.query.ts +6 -2
- package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +24 -6
- package/packages/bundled-features/src/template-resolver/handlers/collection-shared.ts +32 -0
- package/packages/bundled-features/src/template-resolver/handlers/set.write.ts +1 -0
- package/packages/bundled-features/src/template-resolver/handlers/shared.ts +9 -3
- package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +13 -480
- package/packages/bundled-features/src/template-resolver/web/collection-entry-create-form.tsx +105 -0
- package/packages/bundled-features/src/template-resolver/web/collection-lookup.ts +33 -0
- package/packages/bundled-features/src/template-resolver/web/constants.ts +3 -0
- package/packages/bundled-features/src/template-resolver/web/content-field.tsx +61 -0
- package/packages/bundled-features/src/template-resolver/web/content-tree-provider.ts +256 -0
- package/packages/bundled-features/src/template-resolver/web/document-reference-name.ts +12 -0
- package/packages/bundled-features/src/template-resolver/web/document-schema.ts +3 -11
- package/packages/bundled-features/src/template-resolver/web/i18n.ts +9 -2
- package/packages/bundled-features/src/template-resolver/web/legacy-variable-text.ts +30 -0
- package/packages/bundled-features/src/template-resolver/web/tenant-override.ts +7 -0
- package/packages/bundled-features/src/template-resolver/web/text-block-editor.tsx +92 -0
- package/packages/bundled-features/src/template-resolver/web/use-collection-editor-context.ts +42 -0
- package/packages/bundled-features/src/template-resolver/web/use-collection-entry-create.ts +92 -0
- package/packages/bundled-features/src/template-resolver/web/use-content-write.ts +81 -0
- package/packages/bundled-features/src/template-resolver/web/use-text-block-editor.ts +127 -0
- package/packages/bundled-features/src/tier-engine/changes.json +6 -0
- package/packages/bundled-features/src/tier-engine/feature.ts +9 -1
- package/packages/bundled-features/src/user-data-rights-defaults/hooks/file-ref.userdata-hook.ts +2 -1
- package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-constants.ts +4 -0
- package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +11 -5
- package/samples/apps/use-all-bundled/e2e-billing-gallery/server.ts +2 -2
- package/samples/apps/use-all-bundled/feature-manifest.json +89 -40
- package/samples/apps/use-all-bundled/kumiko/migrations/0056_inbound-mail-quarantine-retry.rebuild.json +4 -0
- package/samples/apps/use-all-bundled/package.json +1 -0
- package/samples/apps/use-all-bundled/playwright.billing-docs.config.ts +15 -0
- package/samples/apps/use-all-bundled/src/app/cap-overview-caps.ts +16 -2
- package/samples/apps/use-all-bundled/src/app/cap-overview-labels-feature.ts +2 -1
- package/samples/apps/use-all-bundled/src/app/client.tsx +8 -1
- package/samples/apps/use-all-bundled/src/app/screens-feature.ts +14 -2
- package/samples/apps/use-all-bundled/src/app/seed.ts +17 -2
- package/samples/apps/use-all-bundled/src/run-config.ts +19 -11
- package/samples/recipes/cross-feature-events/README.md +10 -0
- package/samples/recipes/cross-feature-events/src/feature.ts +14 -0
- package/samples/recipes/dashboard-stats/README.md +8 -0
- package/samples/recipes/dashboard-stats/src/feature.ts +91 -1
- package/samples/recipes/env-composition/README.md +2 -2
- package/samples/recipes/env-composition/src/env.ts +2 -2
- package/samples/recipes/inbound-mail-triage/README.md +8 -0
- package/samples/recipes/pdf-audit-export/src/feature.ts +71 -48
- package/packages/bundled-features/src/subscription-dodo/changes.json +0 -14
- package/packages/bundled-features/src/subscription-dodo/client.ts +0 -45
- package/packages/bundled-features/src/subscription-dodo/constants.ts +0 -24
- package/packages/bundled-features/src/subscription-dodo/feature.ts +0 -207
- package/packages/bundled-features/src/subscription-dodo/index.ts +0 -16
- package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +0 -345
- package/packages/bundled-features/src/subscription-dodo/runtime.ts +0 -126
- package/packages/bundled-features/src/subscription-dodo/verify-webhook.ts +0 -262
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
type ContentCollectionDefinition,
|
|
3
3
|
createSystemConfig,
|
|
4
4
|
defineFeature,
|
|
5
|
+
type TreeAction,
|
|
5
6
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
6
7
|
import { bySlugQuery } from "./handlers/by-slug.query.ts";
|
|
7
8
|
import { byTenantQuery } from "./handlers/by-tenant.query.ts";
|
|
@@ -109,6 +110,14 @@ function assertTenantEditableValid(entries: readonly TenantEditableTemplate[]):
|
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
112
|
|
|
113
|
+
function newEntryAction(collectionId: string): TreeAction {
|
|
114
|
+
return {
|
|
115
|
+
icon: "plus",
|
|
116
|
+
label: "template-resolver.editor.newEntry",
|
|
117
|
+
target: { featureId: "template-resolver", action: "create", args: { collectionId } },
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
112
121
|
export function createTemplateResolverFeature(opts: TemplateResolverOptions = {}) {
|
|
113
122
|
const collections = opts.collections ?? [];
|
|
114
123
|
assertCollectionKindsAllowed(collections);
|
|
@@ -129,7 +138,7 @@ export function createTemplateResolverFeature(opts: TemplateResolverOptions = {}
|
|
|
129
138
|
"Reading is one call — `ctx.templateResolver.resolveTemplate({ tenantId, slug, kind, locale })`. It walks four levels: tenant+locale, system+locale, tenant+fallback-locale, system+fallback-locale. A tenant overrides a system default by simply having its own record; no application code changes. Writing programmatically goes through the `upsertSystem`, `upsertTenant`, `publish` and `archive` write handlers; a SystemAdmin retires or restores a system default with `publishSystem` and `archiveSystem`, and a tenant drops its override with `resetToSystem`. Saving a draft over an active template keeps the active version served and stores the edit beside it (column `draft`); `publish` makes it the served version, `discardDraft` / `discardDraftSystem` throws it away.",
|
|
130
139
|
'Editing templates is for SystemAdmin only. A TenantAdmin edits only the templates the app unlocks at the mount site, `createTemplateResolverFeature({ tenantEditable: [{ kind: "mail", slug: "status-update", access: { roles: ["TenantAdmin"] } }] })`, and every write handler enforces that on the server (override, draft, publish, archive, reset, copy-locale, import, resources, `set`). Each entry carries the `access` roles allowed to edit it, because a bundled feature cannot know the host\'s roles. The same list decides delivery: a tenant override is served (mail, PDF, layouts, text blocks) only while its template is listed in tenantEditable; otherwise the system version is served. A locked override stays stored, a SystemAdmin sees it in the overview marked as not delivered, and listing the template makes it effective again. Tenant rows of a collection mounted with `access` are always served. A collection mounted without `access` is SystemAdmin only.',
|
|
131
140
|
"Text an editor should be able to change belongs in a collection, declared at mount: `createTemplateResolverFeature({ collections: [{ id, kind, access: { roles }, nav }] })`. It appears in the navigation, and `access` is part of the mount because a bundled feature cannot know the host's roles. Each collection gets its own `<id>-list` / `<id>-item` / `<id>-set` handlers, so the dispatcher enforces the separation.",
|
|
132
|
-
"How a collection is edited follows from `contentFormat`: `plain` gives a text area, `
|
|
141
|
+
"How a collection is edited follows from `contentFormat`: `plain` gives a text area that stores text, `document` a WYSIWYG document editor that stores sanitized HTML (bold, italic, headings, lists, links, variable chips). Both offer the collection's `variableSchema` as insertable chips and a preview rendered with sample data — an editor sees what `{{firstName}}` becomes without sending a mail. An app can register its own editor for a format; the last `clientFeature` that registers a format wins (a conflict logs a warning).",
|
|
133
142
|
'A collection is tenant-wide by default. With `ownership: "user"` every user keeps their own entries — mail signatures being the obvious case. Those rows live in the separate `user-content-entry` entity and count as user data, so mounting one also requires the `template-resolver-user-data` feature and a migration on the app side.',
|
|
134
143
|
"`validate` dry-runs the write pipeline for a draft (sanitizer, layout rules, placeholders) and reports what the template uses and misses. `export-bundle` / `import-bundle` (and `import-bundle-system`) move one template with all locales and resource files between tenants or environments; `copy-locale` starts a translation from an existing locale. Layouts are structured: a mail or PDF layout is `data-header` / `data-content` / `data-signature` / `data-footer` regions, and mjml layout source is system-only.",
|
|
135
144
|
"`preview` renders a draft (or the stored template) with sample values taken from the `example` of each declared variable, through the same gates and the same mail renderer as a real send, and returns subject, html, `displayHtml` (the html with cid images as data URIs for an editor), text, attachments (inline images and files) and `checks` (images resolved, plain text present, size under 10 MiB, required placeholders used); nothing is saved or sent. `send-test` mails that rendering to the calling user's own address only, subject prefixed with `[TEST]`, at most 10 per user and hour; it needs `delivery`, `channel-email` and `renderer-mail-html` to be mounted. `send-test-status` tells an editor beforehand whether that send can work for the caller (`canSend`, else `reason` `no_channel` or `no_address`), never returning the address.",
|
|
@@ -198,6 +207,10 @@ export function createTemplateResolverFeature(opts: TemplateResolverOptions = {}
|
|
|
198
207
|
r.contentCollection({
|
|
199
208
|
...collection,
|
|
200
209
|
access: collection.access ?? DEFAULT_COLLECTION_ACCESS,
|
|
210
|
+
nav: {
|
|
211
|
+
...collection.nav,
|
|
212
|
+
createAction: collection.nav.createAction ?? newEntryAction(collection.id),
|
|
213
|
+
},
|
|
201
214
|
});
|
|
202
215
|
r.queryHandler(makeCollectionListQuery(collection));
|
|
203
216
|
r.queryHandler(makeCollectionItemQuery(collection));
|
|
@@ -8,7 +8,7 @@ import { DEFAULT_COLLECTION_ACCESS } from "../web/constants.ts";
|
|
|
8
8
|
import {
|
|
9
9
|
type CollectionEntryRow,
|
|
10
10
|
collectionStore,
|
|
11
|
-
|
|
11
|
+
readableCollectionEntry,
|
|
12
12
|
} from "./collection-shared.ts";
|
|
13
13
|
|
|
14
14
|
// Single entry of one collection, for the editor behind a tree node. Same
|
|
@@ -40,7 +40,7 @@ export function makeCollectionItemQuery(collection: ContentCollectionDefinition)
|
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
if (!row) return null;
|
|
43
|
-
return
|
|
43
|
+
return readableCollectionEntry(row, store, `${collection.id}-item`);
|
|
44
44
|
},
|
|
45
45
|
});
|
|
46
46
|
}
|
|
@@ -8,7 +8,7 @@ import { DEFAULT_COLLECTION_ACCESS } from "../web/constants.ts";
|
|
|
8
8
|
import {
|
|
9
9
|
type CollectionEntryRow,
|
|
10
10
|
collectionStore,
|
|
11
|
-
|
|
11
|
+
readableCollectionEntry,
|
|
12
12
|
} from "./collection-shared.ts";
|
|
13
13
|
|
|
14
14
|
// One list handler per declared collection: `kind`, `ownership` and `access`
|
|
@@ -42,7 +42,11 @@ export function makeCollectionListQuery(collection: ContentCollectionDefinition)
|
|
|
42
42
|
...store.scopeOf(query.user),
|
|
43
43
|
}),
|
|
44
44
|
);
|
|
45
|
-
return {
|
|
45
|
+
return {
|
|
46
|
+
blocks: await Promise.all(
|
|
47
|
+
rows.map((row) => readableCollectionEntry(row, store, `${collection.id}-list`)),
|
|
48
|
+
),
|
|
49
|
+
};
|
|
46
50
|
},
|
|
47
51
|
});
|
|
48
52
|
}
|
|
@@ -11,8 +11,8 @@ import { systemTemplateExists } from "../api.ts";
|
|
|
11
11
|
import { documentViolationsToFieldIssues, sanitizeTemplateBody } from "../document-sanitizer.ts";
|
|
12
12
|
import { refuseTemplateEdit, type TemplateEditPolicy } from "../template-edit-policy.ts";
|
|
13
13
|
import { DEFAULT_COLLECTION_ACCESS, isCollectionRefusedKind } from "../web/constants.ts";
|
|
14
|
-
import { type CollectionEntryRow, collectionStore } from "./collection-shared.ts";
|
|
15
|
-
import {
|
|
14
|
+
import { type CollectionEntryRow, collectionStore, storageFormatOf } from "./collection-shared.ts";
|
|
15
|
+
import { folderSchema, localeSchema, slugSchema } from "./shared.ts";
|
|
16
16
|
|
|
17
17
|
// Upsert inside one collection. Mirrors set.write for text-blocks, but the
|
|
18
18
|
// kind comes from the declaration and the access rule is the collection's own,
|
|
@@ -26,6 +26,7 @@ export function makeCollectionSetWrite(
|
|
|
26
26
|
editPolicy: TemplateEditPolicy,
|
|
27
27
|
) {
|
|
28
28
|
const store = collectionStore(collection);
|
|
29
|
+
const contentFormat = storageFormatOf(collection);
|
|
29
30
|
return defineWriteHandler({
|
|
30
31
|
name: `${collection.id}-set`,
|
|
31
32
|
schema: z.object({
|
|
@@ -33,8 +34,9 @@ export function makeCollectionSetWrite(
|
|
|
33
34
|
locale: localeSchema,
|
|
34
35
|
title: z.string().min(1).max(200),
|
|
35
36
|
content: z.string().max(200_000).nullable(),
|
|
36
|
-
contentFormat: contentFormatSchema.default("markdown"),
|
|
37
37
|
folder: folderSchema.nullable().optional(),
|
|
38
|
+
/** The create form sets it so a taken slug is reported instead of overwritten. */
|
|
39
|
+
createOnly: z.boolean().optional(),
|
|
38
40
|
/** Optional cross-tenant write — SystemAdmin only. */
|
|
39
41
|
tenantIdOverride: z.string().min(1).optional(),
|
|
40
42
|
}),
|
|
@@ -71,8 +73,9 @@ export function makeCollectionSetWrite(
|
|
|
71
73
|
|
|
72
74
|
const body = sanitizeTemplateBody({
|
|
73
75
|
kind: collection.kind,
|
|
74
|
-
contentFormat
|
|
76
|
+
contentFormat,
|
|
75
77
|
content: event.payload.content,
|
|
78
|
+
alwaysSanitizeHtml: collection.contentFormat === "document",
|
|
76
79
|
});
|
|
77
80
|
if (!body.ok) {
|
|
78
81
|
return writeFailure(
|
|
@@ -100,6 +103,21 @@ export function makeCollectionSetWrite(
|
|
|
100
103
|
...owner,
|
|
101
104
|
});
|
|
102
105
|
|
|
106
|
+
if (existing && event.payload.createOnly === true) {
|
|
107
|
+
return writeFailure(
|
|
108
|
+
new ValidationError({
|
|
109
|
+
fields: [
|
|
110
|
+
{
|
|
111
|
+
path: "slug",
|
|
112
|
+
code: "slug_exists",
|
|
113
|
+
i18nKey: "templateResolver.errors.collection.slugExists",
|
|
114
|
+
params: { detail: event.payload.slug },
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
103
121
|
if (existing) {
|
|
104
122
|
const result = await store.executor.update(
|
|
105
123
|
{
|
|
@@ -111,7 +129,7 @@ export function makeCollectionSetWrite(
|
|
|
111
129
|
changes: {
|
|
112
130
|
title: event.payload.title,
|
|
113
131
|
content: body.content,
|
|
114
|
-
contentFormat
|
|
132
|
+
contentFormat,
|
|
115
133
|
folder: event.payload.folder ?? null,
|
|
116
134
|
},
|
|
117
135
|
},
|
|
@@ -134,7 +152,7 @@ export function makeCollectionSetWrite(
|
|
|
134
152
|
locale: event.payload.locale,
|
|
135
153
|
title: event.payload.title,
|
|
136
154
|
content: body.content,
|
|
137
|
-
contentFormat
|
|
155
|
+
contentFormat,
|
|
138
156
|
folder: event.payload.folder ?? null,
|
|
139
157
|
...store.createDefaults(tenantId),
|
|
140
158
|
...owner,
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { requestContext } from "@cosmicdrift/kumiko-framework/api";
|
|
2
|
+
import {
|
|
3
|
+
configuredPiiSubjectKms,
|
|
4
|
+
decryptPiiFieldValues,
|
|
5
|
+
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
1
6
|
import type { EventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
|
|
2
7
|
import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
|
|
3
8
|
import type {
|
|
@@ -9,6 +14,7 @@ import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
|
9
14
|
import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
10
15
|
import { templateResourceEntity, templateResourcesTable } from "../table.ts";
|
|
11
16
|
import { userContentEntriesTable, userContentEntryEntity } from "../user-content-table.ts";
|
|
17
|
+
import type { ContentFormat } from "../web/constants.ts";
|
|
12
18
|
|
|
13
19
|
const templateExecutor = createEventStoreExecutor(templateResourcesTable, templateResourceEntity, {
|
|
14
20
|
entityName: "template-resource",
|
|
@@ -41,6 +47,8 @@ export type CollectionStore = {
|
|
|
41
47
|
scopeOf(user: SessionUser): Readonly<Record<string, unknown>>;
|
|
42
48
|
/** Columns a create needs beyond the editable ones the payload carries. */
|
|
43
49
|
createDefaults(tenantId: TenantId): Readonly<Record<string, unknown>>;
|
|
50
|
+
/** True when `content` is stored as PII ciphertext and must be decrypted on read. */
|
|
51
|
+
readonly contentIsPii: boolean;
|
|
44
52
|
};
|
|
45
53
|
|
|
46
54
|
// `ownership: "user"` means every user keeps their own entries (signatures);
|
|
@@ -55,6 +63,7 @@ export function collectionStore(collection: ContentCollectionDefinition): Collec
|
|
|
55
63
|
executor: userContentExecutor,
|
|
56
64
|
scopeOf: (user) => ({ ownerId: user.id }),
|
|
57
65
|
createDefaults: () => ({}),
|
|
66
|
+
contentIsPii: true,
|
|
58
67
|
};
|
|
59
68
|
}
|
|
60
69
|
return {
|
|
@@ -68,9 +77,26 @@ export function collectionStore(collection: ContentCollectionDefinition): Collec
|
|
|
68
77
|
parentTemplateId: null,
|
|
69
78
|
status: "active",
|
|
70
79
|
}),
|
|
80
|
+
contentIsPii: false,
|
|
71
81
|
};
|
|
72
82
|
}
|
|
73
83
|
|
|
84
|
+
// Raw reads return the stored column, which is ciphertext for a user-owned entry while a KMS
|
|
85
|
+
// is configured; the API's leak guard rejects any response that still carries it.
|
|
86
|
+
export async function readableCollectionEntry(
|
|
87
|
+
row: CollectionEntryRow,
|
|
88
|
+
store: CollectionStore,
|
|
89
|
+
fallbackRequestId: string,
|
|
90
|
+
) {
|
|
91
|
+
const kms = configuredPiiSubjectKms();
|
|
92
|
+
if (!store.contentIsPii || !kms || row.content === null) return toCollectionEntry(row);
|
|
93
|
+
const decrypted = await decryptPiiFieldValues({ content: row.content }, ["content"], kms, {
|
|
94
|
+
requestId: requestContext.get()?.requestId ?? fallbackRequestId,
|
|
95
|
+
});
|
|
96
|
+
const content = decrypted["content"];
|
|
97
|
+
return toCollectionEntry({ ...row, content: typeof content === "string" ? content : null });
|
|
98
|
+
}
|
|
99
|
+
|
|
74
100
|
export function toCollectionEntry(row: CollectionEntryRow) {
|
|
75
101
|
return {
|
|
76
102
|
slug: row.slug,
|
|
@@ -82,3 +108,9 @@ export function toCollectionEntry(row: CollectionEntryRow) {
|
|
|
82
108
|
modifiedAt: row.modifiedAt,
|
|
83
109
|
};
|
|
84
110
|
}
|
|
111
|
+
|
|
112
|
+
// The client never names the storage format: the declared editor decides it, so a
|
|
113
|
+
// stale or tampered client cannot stamp a "plain" collection with html or the reverse.
|
|
114
|
+
export function storageFormatOf(collection: ContentCollectionDefinition): ContentFormat {
|
|
115
|
+
return collection.contentFormat === "document" ? "html" : "plain";
|
|
116
|
+
}
|
|
@@ -48,6 +48,7 @@ export function makeSetWrite(policy: TemplateEditPolicy) {
|
|
|
48
48
|
kind: z.enum(TEMPLATE_KINDS).optional(),
|
|
49
49
|
title: z.string().min(1).max(200),
|
|
50
50
|
content: z.string().max(200_000).nullable(),
|
|
51
|
+
// Provisioning callers omit it and rely on markdown.
|
|
51
52
|
contentFormat: contentFormatSchema.default("markdown"),
|
|
52
53
|
/** Folder path for the content tree. Optional + null = root node. */
|
|
53
54
|
folder: folderSchema.nullable().optional(),
|
|
@@ -2,7 +2,13 @@ import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
|
|
|
2
2
|
import * as z from "zod";
|
|
3
3
|
import { templateResourceEntity, templateResourcesTable } from "../table.ts";
|
|
4
4
|
import { templateSlugSchema } from "../template-meta.ts";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
CONTENT_FORMATS,
|
|
7
|
+
SLUG_MAX_LENGTH,
|
|
8
|
+
SLUG_PATTERN,
|
|
9
|
+
TEMPLATE_STATUSES,
|
|
10
|
+
UPSERT_KINDS,
|
|
11
|
+
} from "../web/constants.ts";
|
|
6
12
|
|
|
7
13
|
// One executor per bundle, shared by all four handlers for create/update via
|
|
8
14
|
// event store + optimistic lock.
|
|
@@ -14,8 +20,8 @@ export const executor = createEventStoreExecutor(templateResourcesTable, templat
|
|
|
14
20
|
export const slugSchema = z
|
|
15
21
|
.string()
|
|
16
22
|
.min(1)
|
|
17
|
-
.max(
|
|
18
|
-
.regex(
|
|
23
|
+
.max(SLUG_MAX_LENGTH)
|
|
24
|
+
.regex(SLUG_PATTERN, "slug must be kebab-case (lowercase, digits, dashes)");
|
|
19
25
|
|
|
20
26
|
export { templateSlugSchema };
|
|
21
27
|
|