@cosmicdrift/kumiko-bundled-features 1.0.0 → 2.0.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 +41 -7
- package/src/__tests__/access-invalidation-consumer.integration.test.ts +279 -0
- package/src/__tests__/access-invalidation-stream.integration.test.ts +217 -0
- package/src/admin-shell/__tests__/admin-shell-security.integration.test.ts +200 -0
- package/src/admin-shell/__tests__/admin-shell.boot.test.ts +147 -0
- package/src/admin-shell/__tests__/overview-allowlist.test.ts +43 -0
- package/src/admin-shell/__tests__/overview-screens.boot.test.ts +35 -0
- package/src/admin-shell/constants.ts +11 -0
- package/src/admin-shell/feature.ts +135 -0
- package/src/admin-shell/i18n.ts +36 -0
- package/src/admin-shell/index.ts +17 -0
- package/src/admin-shell/overview-allowlist.ts +37 -0
- package/src/admin-shell/web/client-plugin.tsx +26 -0
- package/src/admin-shell/web/i18n.ts +16 -0
- package/src/admin-shell/web/index.ts +2 -0
- package/src/admin-shell/web/overview-layout.tsx +69 -0
- package/src/admin-shell/web/overview-query.ts +15 -0
- package/src/admin-shell/web/platform-overview-screen.tsx +90 -0
- package/src/admin-shell/web/tenant-overview-screen.tsx +90 -0
- package/src/agent-tools/__tests__/tool-catalog.test.ts +179 -0
- package/src/agent-tools/__tests__/tool-dispatch.integration.test.ts +100 -0
- package/src/agent-tools/__tests__/tool-dispatch.test.ts +126 -0
- package/src/agent-tools/index.ts +9 -0
- package/src/agent-tools/tool-catalog.ts +130 -0
- package/src/agent-tools/tool-dispatch.ts +82 -0
- package/src/agent-tools/types.ts +38 -0
- package/src/audit/__tests__/audit-screens.boot.test.ts +51 -0
- package/src/audit/__tests__/audit-security.integration.test.ts +201 -0
- package/src/audit/__tests__/audit.integration.test.ts +50 -25
- package/src/audit/constants.ts +9 -2
- package/src/audit/feature.ts +33 -1
- package/src/audit/handlers/details.query.ts +47 -0
- package/src/audit/handlers/list.query.ts +3 -2
- package/src/audit/i18n.ts +31 -0
- package/src/audit/web/audit-log-detail-screen.tsx +131 -0
- package/src/audit/web/audit-log-screen.tsx +250 -0
- package/src/audit/web/client-plugin.tsx +22 -0
- package/src/audit/web/i18n.ts +12 -0
- package/src/audit/web/index.ts +5 -0
- package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.test.ts +4 -4
- package/src/auth-email-password/__tests__/account-lockout.integration.test.ts +4 -4
- package/src/auth-email-password/__tests__/account-unlock.integration.test.ts +283 -0
- package/src/auth-email-password/__tests__/auth-claims.integration.test.ts +10 -11
- package/src/auth-email-password/__tests__/auth-user-row.test.ts +36 -0
- package/src/auth-email-password/__tests__/auth.integration.test.ts +33 -6
- package/src/auth-email-password/__tests__/email-verification.integration.test.ts +9 -6
- package/src/auth-email-password/__tests__/feature-options.test.ts +33 -0
- package/src/auth-email-password/__tests__/identity-v3-login.integration.test.ts +3 -3
- package/src/auth-email-password/__tests__/invite-flow-kms.integration.test.ts +283 -0
- package/src/auth-email-password/__tests__/invite-flow.integration.test.ts +35 -1
- package/src/auth-email-password/__tests__/login-mfa-setup-required.integration.test.ts +129 -0
- package/src/auth-email-password/__tests__/multi-roles.integration.test.ts +1 -1
- package/src/auth-email-password/__tests__/password-policy.test.ts +15 -0
- package/src/auth-email-password/__tests__/password-reset.integration.test.ts +19 -6
- package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.test.ts +3 -3
- package/src/auth-email-password/__tests__/seed-admin.integration.test.ts +1 -1
- package/src/auth-email-password/__tests__/self-registration-toggle.integration.test.ts +264 -0
- package/src/auth-email-password/__tests__/session-callbacks.integration.test.ts +20 -4
- package/src/auth-email-password/__tests__/session-strict-mode.integration.test.ts +7 -14
- package/src/auth-email-password/__tests__/signup-flow.integration.test.ts +91 -0
- package/src/auth-email-password/constants.ts +25 -0
- package/src/auth-email-password/email-templates.ts +32 -0
- package/src/auth-email-password/errors.ts +9 -0
- package/src/auth-email-password/feature.ts +59 -11
- package/src/auth-email-password/handlers/change-password.write.ts +14 -13
- package/src/auth-email-password/handlers/confirm-account-unlock.write.ts +59 -0
- package/src/auth-email-password/handlers/confirm-token-flow.ts +34 -105
- package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +12 -5
- package/src/auth-email-password/handlers/invite-accept.write.ts +9 -2
- package/src/auth-email-password/handlers/invite-create.write.ts +2 -2
- package/src/auth-email-password/handlers/invite-signup-complete.write.ts +12 -4
- package/src/auth-email-password/handlers/login.write.ts +86 -16
- package/src/auth-email-password/handlers/logout.write.ts +4 -2
- package/src/auth-email-password/handlers/request-account-unlock.write.ts +38 -0
- package/src/auth-email-password/handlers/reset-password.write.ts +3 -2
- package/src/auth-email-password/handlers/self-registration-status.query.ts +23 -0
- package/src/auth-email-password/handlers/signup-confirm.write.ts +19 -11
- package/src/auth-email-password/handlers/signup-request.write.ts +22 -0
- package/src/auth-email-password/handlers/token-request-handler.ts +4 -2
- package/src/auth-email-password/i18n.ts +52 -0
- package/src/auth-email-password/index.ts +13 -2
- package/src/auth-email-password/lockout-store.ts +10 -5
- package/src/auth-email-password/password-policy.ts +2108 -0
- package/src/auth-email-password/seeding.ts +55 -27
- package/src/auth-email-password/self-registration-toggle.ts +25 -0
- package/src/auth-email-password/signed-token.ts +1 -0
- package/src/auth-email-password/unlock-token.ts +28 -0
- package/src/auth-email-password/web/__tests__/auth-client.test.ts +542 -0
- package/src/auth-email-password/web/__tests__/auth-form-logic.test.ts +44 -0
- package/src/auth-email-password/web/__tests__/auth-gate.test.tsx +194 -6
- package/src/auth-email-password/web/__tests__/client-plugin.test.tsx +34 -0
- package/src/auth-email-password/web/__tests__/confirm-account-unlock-screen.test.tsx +64 -0
- package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +5 -1
- package/src/auth-email-password/web/__tests__/invite-accept-screen.test.tsx +129 -2
- package/src/auth-email-password/web/__tests__/login-screen.test.tsx +149 -5
- package/src/auth-email-password/web/__tests__/request-account-unlock-screen.test.tsx +137 -0
- package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +5 -1
- package/src/auth-email-password/web/__tests__/signup-complete-screen.test.tsx +134 -0
- package/src/auth-email-password/web/__tests__/signup-screen.test.tsx +93 -0
- package/src/auth-email-password/web/__tests__/test-utils.tsx +12 -2
- package/src/auth-email-password/web/__tests__/use-url-token.test.tsx +29 -0
- package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +5 -1
- package/src/auth-email-password/web/auth-client.ts +96 -18
- package/src/auth-email-password/web/auth-form-logic.ts +30 -0
- package/src/auth-email-password/web/auth-form-primitives.tsx +22 -20
- package/src/auth-email-password/web/auth-gate.tsx +140 -19
- package/src/auth-email-password/web/client-plugin.ts +24 -2
- package/src/auth-email-password/web/confirm-account-unlock-screen.tsx +95 -0
- package/src/auth-email-password/web/forgot-password-screen.tsx +6 -6
- package/src/auth-email-password/web/index.ts +16 -4
- package/src/auth-email-password/web/invite-accept-screen.tsx +5 -5
- package/src/auth-email-password/web/login-screen.tsx +78 -22
- package/src/auth-email-password/web/request-account-unlock-screen.tsx +112 -0
- package/src/auth-email-password/web/reset-password-screen.tsx +15 -18
- package/src/auth-email-password/web/session.tsx +11 -6
- package/src/auth-email-password/web/signup-complete-screen.tsx +13 -15
- package/src/auth-email-password/web/signup-screen.tsx +8 -10
- package/src/auth-email-password/web/use-shell-user.ts +5 -2
- package/src/auth-email-password/web/user-menu.tsx +3 -1
- package/src/auth-email-password/web/verify-email-screen.tsx +10 -14
- package/src/auth-foundation/__tests__/feature.test.ts +46 -0
- package/src/auth-foundation/__tests__/session-store.test.ts +77 -0
- package/src/auth-foundation/__tests__/tenant-providers.test.ts +160 -0
- package/src/auth-foundation/__tests__/token-verifier.test.ts +150 -0
- package/src/auth-foundation/boot-checks.ts +150 -0
- package/src/auth-foundation/feature.ts +157 -0
- package/src/auth-foundation/index.ts +37 -0
- package/src/auth-foundation/resolve-anonymous-access.ts +62 -0
- package/src/auth-foundation/types.ts +170 -0
- package/src/auth-mfa/__tests__/base32.test.ts +28 -0
- package/src/auth-mfa/__tests__/disable-and-regenerate.integration.test.ts +265 -0
- package/src/auth-mfa/__tests__/enable-confirm-preauth.integration.test.ts +321 -0
- package/src/auth-mfa/__tests__/enable-flow.integration.test.ts +322 -0
- package/src/auth-mfa/__tests__/enable-start-preauth.integration.test.ts +237 -0
- package/src/auth-mfa/__tests__/errors-i18n-parity.test.ts +39 -0
- package/src/auth-mfa/__tests__/mfa-preauth-setup-token.test.ts +113 -0
- package/src/auth-mfa/__tests__/no-redis.integration.test.ts +209 -0
- package/src/auth-mfa/__tests__/pii-subject-encryption.integration.test.ts +139 -0
- package/src/auth-mfa/__tests__/reencrypt-job.integration.test.ts +247 -0
- package/src/auth-mfa/__tests__/session-auto-revoke.integration.test.ts +169 -0
- package/src/auth-mfa/__tests__/totp.test.ts +131 -0
- package/src/auth-mfa/__tests__/verify.integration.test.ts +319 -0
- package/src/auth-mfa/base32.ts +51 -0
- package/src/auth-mfa/config.ts +26 -0
- package/src/auth-mfa/constants.ts +45 -0
- package/src/auth-mfa/db/queries.ts +45 -0
- package/src/auth-mfa/errors.ts +66 -0
- package/src/auth-mfa/feature.ts +170 -0
- package/src/auth-mfa/handlers/disable.write.ts +67 -0
- package/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +200 -0
- package/src/auth-mfa/handlers/enable-confirm.write.ts +106 -0
- package/src/auth-mfa/handlers/enable-start-preauth.write.ts +87 -0
- package/src/auth-mfa/handlers/enable-start.write.ts +66 -0
- package/src/auth-mfa/handlers/reencrypt.job.ts +234 -0
- package/src/auth-mfa/handlers/regenerate-recovery.write.ts +77 -0
- package/src/auth-mfa/handlers/status.query.ts +23 -0
- package/src/auth-mfa/handlers/verify.write.ts +179 -0
- package/src/auth-mfa/i18n.ts +12 -0
- package/src/auth-mfa/index.ts +19 -0
- package/src/auth-mfa/mfa-challenge-token.ts +90 -0
- package/src/auth-mfa/mfa-preauth-setup-token.ts +86 -0
- package/src/auth-mfa/mfa-setup-token.ts +96 -0
- package/src/auth-mfa/mfa-status-checker.ts +84 -0
- package/src/auth-mfa/mfa-verify-attempts.ts +90 -0
- package/src/auth-mfa/otpauth-uri.ts +21 -0
- package/src/auth-mfa/recovery-codes.ts +54 -0
- package/src/auth-mfa/schema/user-mfa.ts +60 -0
- package/src/auth-mfa/testing.ts +5 -0
- package/src/auth-mfa/totp.ts +67 -0
- package/src/auth-mfa/verify-factor.ts +58 -0
- package/src/auth-mfa/web/__tests__/mfa-client.test.ts +265 -0
- package/src/auth-mfa/web/__tests__/mfa-enable-screen.test.tsx +182 -0
- package/src/auth-mfa/web/__tests__/mfa-setup-preauth-screen.test.tsx +379 -0
- package/src/auth-mfa/web/__tests__/mfa-verify-screen.test.tsx +104 -0
- package/src/auth-mfa/web/client-plugin.ts +37 -0
- package/src/auth-mfa/web/i18n.ts +136 -0
- package/src/auth-mfa/web/index.ts +27 -0
- package/src/auth-mfa/web/mfa-client.ts +184 -0
- package/src/auth-mfa/web/mfa-disable-dialog.tsx +69 -0
- package/src/auth-mfa/web/mfa-enable-screen.tsx +228 -0
- package/src/auth-mfa/web/mfa-recovery-codes-reveal.tsx +53 -0
- package/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +68 -0
- package/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +267 -0
- package/src/auth-mfa/web/mfa-verify-screen.tsx +117 -0
- package/src/auth-mfa/web/qrcode-browser.d.ts +12 -0
- package/src/auth-mfa-user-data/__tests__/hooks.integration.test.ts +134 -0
- package/src/auth-mfa-user-data/hooks.ts +53 -0
- package/src/auth-mfa-user-data/index.ts +21 -0
- package/src/billing-foundation/__tests__/billing-foundation.integration.test.ts +206 -2
- package/src/billing-foundation/__tests__/subscription-provider-contract.ts +93 -0
- package/src/billing-foundation/entities.ts +28 -4
- package/src/billing-foundation/events.ts +6 -2
- package/src/billing-foundation/feature.ts +9 -1
- package/src/billing-foundation/get-subscription-for-tenant.ts +16 -5
- package/src/billing-foundation/handlers/create-portal-session.write.ts +17 -1
- package/src/billing-foundation/handlers/list-subscriptions.query.ts +15 -1
- package/src/billing-foundation/handlers/process-event.write.ts +39 -3
- package/src/billing-foundation/tenant-destroy-hook.ts +46 -0
- package/src/cap-counter/__tests__/cap-list.boot.test.ts +27 -0
- package/src/cap-counter/__tests__/cap-list.security.integration.test.ts +53 -0
- package/src/cap-counter/__tests__/entity-ddl.test.ts +15 -0
- package/src/cap-counter/__tests__/with-cap-enforcement.integration.test.ts +8 -5
- package/src/cap-counter/constants.ts +2 -0
- package/src/cap-counter/enforce-cap.ts +15 -18
- package/src/cap-counter/entity.ts +3 -3
- package/src/cap-counter/feature.ts +28 -1
- package/src/cap-counter/i18n.ts +12 -0
- package/src/cap-counter/with-cap-enforcement.ts +2 -2
- package/src/channel-email/__tests__/pii-guard.test.ts +56 -0
- package/src/channel-email/email-channel.ts +2 -1
- package/src/channel-email/index.ts +1 -0
- package/src/channel-email/pii-guard.ts +39 -0
- package/src/compliance-profiles/__tests__/profile-picker.boot.test.ts +28 -0
- package/src/compliance-profiles/__tests__/profile-picker.security.integration.test.ts +91 -0
- package/src/compliance-profiles/constants.ts +15 -0
- package/src/compliance-profiles/feature.ts +22 -1
- package/src/compliance-profiles/handlers/set-profile.write.ts +2 -2
- package/src/compliance-profiles/i18n.ts +6 -0
- package/src/compliance-profiles/index.ts +6 -0
- package/src/compliance-profiles/web/client-plugin.tsx +22 -0
- package/src/compliance-profiles/web/compliance-profile-screen.tsx +152 -0
- package/src/compliance-profiles/web/i18n.ts +25 -0
- package/src/compliance-profiles/web/index.ts +6 -0
- package/src/config/__tests__/app-overrides.test.ts +11 -0
- package/src/config/__tests__/backing-secrets.integration.test.ts +22 -0
- package/src/config/__tests__/cascade.integration.test.ts +35 -0
- package/src/config/__tests__/config.integration.test.ts +71 -10
- package/src/config/__tests__/deserialize-value.test.ts +5 -8
- package/src/config/__tests__/inherited-redaction.integration.test.ts +4 -3
- package/src/config/__tests__/pii-encrypted.integration.test.ts +179 -0
- package/src/config/__tests__/reencrypt-job-concurrent-write-race.integration.test.ts +234 -0
- package/src/config/__tests__/reencrypt-job-kek-rotation.integration.test.ts +243 -0
- package/src/config/__tests__/write-helpers.test.ts +23 -0
- package/src/config/db/queries/resolver.ts +3 -3
- package/src/config/feature.ts +15 -8
- package/src/config/handlers/cascade.query.ts +2 -1
- package/src/config/handlers/reencrypt.job.ts +209 -0
- package/src/config/handlers/set.write.ts +55 -3
- package/src/config/handlers/values.query.ts +2 -1
- package/src/config/i18n.ts +9 -0
- package/src/config/index.ts +5 -4
- package/src/config/resolver.ts +75 -61
- package/src/config/table.ts +1 -1
- package/src/config/write-helpers.ts +22 -0
- package/src/crypto-shredding/__tests__/forget-subject.integration.test.ts +148 -0
- package/src/crypto-shredding/constants.ts +5 -0
- package/src/crypto-shredding/feature.ts +18 -0
- package/src/crypto-shredding/handlers/forget-subject.write.ts +91 -0
- package/src/crypto-shredding/index.ts +11 -0
- package/src/custom-fields/__tests__/audit-integration.integration.test.ts +1 -1
- package/src/custom-fields/__tests__/custom-fields.integration.test.ts +92 -53
- package/src/custom-fields/__tests__/field-access.integration.test.ts +25 -0
- package/src/custom-fields/__tests__/parse-serialized-field.test.ts +11 -3
- package/src/custom-fields/__tests__/retention.integration.test.ts +40 -0
- package/src/custom-fields/__tests__/user-data-rights.integration.test.ts +26 -79
- package/src/custom-fields/constants.ts +1 -0
- package/src/custom-fields/db/queries/user-data-rights.ts +0 -20
- package/src/custom-fields/entity.ts +1 -1
- package/src/custom-fields/events.ts +4 -3
- package/src/custom-fields/feature.ts +8 -0
- package/src/custom-fields/handlers/delete-system-field.write.ts +2 -1
- package/src/custom-fields/handlers/delete-tenant-field.write.ts +2 -1
- package/src/custom-fields/handlers/set-custom-field.write.ts +3 -44
- package/src/custom-fields/handlers/update-tenant-field.write.ts +0 -17
- package/src/custom-fields/lib/field-access.ts +8 -1
- package/src/custom-fields/lib/parse-serialized-field.ts +12 -4
- package/src/custom-fields/lib/value-schema.ts +1 -1
- package/src/custom-fields/run-retention.ts +9 -1
- package/src/custom-fields/schemas.ts +9 -9
- package/src/custom-fields/wire-for-entity.ts +110 -78
- package/src/custom-fields/wire-user-data-rights.ts +5 -54
- package/src/data-retention/__tests__/parse-override.test.ts +14 -13
- package/src/data-retention/__tests__/retention-cleanup.integration.test.ts +145 -8
- package/src/data-retention/feature.ts +3 -3
- package/src/data-retention/run-retention-cleanup.ts +102 -19
- package/src/delivery/__tests__/delivery-pii-kms.integration.test.ts +171 -0
- package/src/delivery/__tests__/delivery-screens.boot.test.ts +27 -0
- package/src/delivery/__tests__/delivery-security.integration.test.ts +106 -0
- package/src/delivery/constants.ts +13 -51
- package/src/delivery/feature.ts +29 -5
- package/src/delivery/handlers/log.query.ts +28 -7
- package/src/delivery/i18n.ts +6 -0
- package/src/delivery/index.ts +6 -1
- package/src/delivery/public-names.ts +36 -0
- package/src/delivery/tables.ts +3 -3
- package/src/delivery/web/client-plugin.tsx +20 -0
- package/src/delivery/web/delivery-log-screen.tsx +103 -0
- package/src/delivery/web/i18n.ts +25 -0
- package/src/delivery/web/index.ts +3 -0
- package/src/document-ingest-foundation/__tests__/feature.integration.test.ts +147 -0
- package/src/document-ingest-foundation/__tests__/feature.test.ts +105 -0
- package/src/document-ingest-foundation/__tests__/pages.test.ts +37 -0
- package/src/document-ingest-foundation/entity.ts +43 -0
- package/src/document-ingest-foundation/events.ts +25 -0
- package/src/document-ingest-foundation/feature.ts +126 -0
- package/src/document-ingest-foundation/index.ts +12 -0
- package/src/document-ingest-foundation/pages.ts +34 -0
- package/src/feature-toggles/__tests__/compose-tier-resolver.test.ts +304 -0
- package/src/feature-toggles/__tests__/feature-toggles.integration.test.ts +7 -6
- package/src/feature-toggles/__tests__/toggle-admin.boot.test.ts +31 -0
- package/src/feature-toggles/__tests__/toggle-admin.security.integration.test.ts +69 -0
- package/src/feature-toggles/compose-tier-resolver.ts +134 -0
- package/src/feature-toggles/constants.ts +14 -0
- package/src/feature-toggles/feature.ts +23 -3
- package/src/feature-toggles/global-feature-state-table.ts +20 -1
- package/src/feature-toggles/handlers/list.query.ts +1 -1
- package/src/feature-toggles/handlers/registered.query.ts +1 -1
- package/src/feature-toggles/i18n.ts +6 -0
- package/src/feature-toggles/index.ts +8 -0
- package/src/feature-toggles/toggle-runtime.ts +14 -0
- package/src/feature-toggles/web/client-plugin.tsx +22 -0
- package/src/feature-toggles/web/i18n.ts +29 -0
- package/src/feature-toggles/web/index.ts +6 -0
- package/src/feature-toggles/web/toggle-admin-screen.tsx +136 -0
- package/src/file-foundation/__tests__/feature.test.ts +5 -5
- package/src/file-foundation/__tests__/file-foundation.integration.test.ts +4 -3
- package/src/file-foundation/feature.ts +10 -11
- package/src/files-provider-s3/__tests__/s3-provider.integration.test.ts +27 -2
- package/src/files-provider-s3/s3-provider.ts +12 -1
- package/src/folders/__tests__/folders.integration.test.ts +3 -0
- package/src/folders/web/__tests__/folder-manager.test.tsx +43 -2
- package/src/folders/web/__tests__/tree.test.ts +8 -0
- package/src/folders/web/folder-manager.tsx +2 -3
- package/src/folders/web/i18n.ts +0 -2
- package/src/folders/web/tree.ts +13 -2
- package/src/folders-user-data/__tests__/hooks.integration.test.ts +1 -0
- package/src/folders-user-data/index.ts +4 -4
- package/src/inbound-mail-foundation/__tests__/account-state.test.ts +30 -0
- package/src/inbound-mail-foundation/__tests__/connect-routes.integration.test.ts +307 -0
- package/src/inbound-mail-foundation/__tests__/feature.test.ts +169 -0
- package/src/inbound-mail-foundation/__tests__/inbound-mail-foundation.integration.test.ts +644 -0
- package/src/inbound-mail-foundation/__tests__/inbound-mail-provider-contract.ts +111 -0
- package/src/inbound-mail-foundation/__tests__/retention.integration.test.ts +237 -0
- package/src/inbound-mail-foundation/__tests__/watch-supervisor.integration.test.ts +433 -0
- package/src/inbound-mail-foundation/aggregate-id.ts +40 -0
- package/src/inbound-mail-foundation/connect-routes.ts +230 -0
- package/src/inbound-mail-foundation/constants.ts +80 -0
- package/src/inbound-mail-foundation/db/queries/inbound-projections.ts +38 -0
- package/src/inbound-mail-foundation/entities.ts +172 -0
- package/src/inbound-mail-foundation/events.ts +147 -0
- package/src/inbound-mail-foundation/feature.ts +243 -0
- package/src/inbound-mail-foundation/handlers/account-state.ts +23 -0
- package/src/inbound-mail-foundation/handlers/connect-account.write.ts +112 -0
- package/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +69 -0
- package/src/inbound-mail-foundation/handlers/ingest-message.write.ts +333 -0
- package/src/inbound-mail-foundation/handlers/list-accounts.query.ts +44 -0
- package/src/inbound-mail-foundation/handlers/list-messages.query.ts +58 -0
- package/src/inbound-mail-foundation/handlers/scope-visibility.ts +14 -0
- package/src/inbound-mail-foundation/handlers/update-account.write.ts +78 -0
- package/src/inbound-mail-foundation/index.ts +97 -0
- package/src/inbound-mail-foundation/oauth-state.ts +103 -0
- package/src/inbound-mail-foundation/projection.ts +152 -0
- package/src/inbound-mail-foundation/provider-factory.ts +53 -0
- package/src/inbound-mail-foundation/retention-sweep.ts +168 -0
- package/src/inbound-mail-foundation/tenant-destroy-hook.ts +93 -0
- package/src/inbound-mail-foundation/types.ts +250 -0
- package/src/inbound-mail-foundation/watch-supervisor.ts +482 -0
- package/src/inbound-provider-imap/__tests__/feature.test.ts +115 -0
- package/src/inbound-provider-imap/__tests__/imap-foundation.integration.test.ts +302 -0
- package/src/inbound-provider-imap/__tests__/imap-live.integration.test.ts +206 -0
- package/src/inbound-provider-imap/__tests__/plugin-mocked.test.ts +358 -0
- package/src/inbound-provider-imap/credential-document.ts +51 -0
- package/src/inbound-provider-imap/feature.ts +283 -0
- package/src/inbound-provider-imap/imap-client.ts +158 -0
- package/src/inbound-provider-imap/index.ts +17 -0
- package/src/inbound-provider-inmemory/__tests__/feature.test.ts +52 -0
- package/src/inbound-provider-inmemory/feature.ts +154 -0
- package/src/inbound-provider-inmemory/index.ts +12 -0
- package/src/jobs/__tests__/entity-ddl.test.ts +15 -0
- package/src/jobs/__tests__/job-instant.test.ts +15 -0
- package/src/jobs/__tests__/job-system-user.integration.test.ts +4 -8
- package/src/jobs/__tests__/jobs-events.integration.test.ts +37 -1
- package/src/jobs/__tests__/jobs-pii-kms.integration.test.ts +120 -0
- package/src/jobs/__tests__/jobs-screens.boot.test.ts +36 -0
- package/src/jobs/__tests__/jobs-security.integration.test.ts +108 -0
- package/src/jobs/__tests__/reindex-entity-job.integration.test.ts +169 -0
- package/src/jobs/constants.ts +8 -1
- package/src/jobs/feature.ts +52 -8
- package/src/jobs/handlers/detail.query.ts +6 -0
- package/src/jobs/handlers/list.query.ts +8 -1
- package/src/jobs/handlers/projection-rebuild.job.ts +14 -4
- package/src/jobs/handlers/reindex-entity.job.ts +49 -0
- package/src/jobs/i18n.ts +37 -0
- package/src/jobs/job-instant.ts +9 -0
- package/src/jobs/job-run-table.ts +4 -4
- package/src/jobs/web/client-plugin.tsx +22 -0
- package/src/jobs/web/i18n.ts +12 -0
- package/src/jobs/web/index.ts +4 -0
- package/src/jobs/web/job-run-detail-screen.tsx +180 -0
- package/src/jobs/web/job-runs-screen.tsx +146 -0
- package/src/ledger/__tests__/ledger.integration.test.ts +8 -3
- package/src/ledger/handlers/confirm-schedule-period.write.ts +17 -4
- package/src/ledger/recurring.ts +17 -8
- package/src/ledger/web/index.ts +3 -2
- package/src/legal-pages/__tests__/legal-pages.integration.test.ts +55 -0
- package/src/legal-pages/feature.ts +24 -31
- package/src/legal-pages/index.ts +1 -0
- package/src/mail-foundation/__tests__/feature.test.ts +5 -6
- package/src/mail-foundation/__tests__/mail-foundation.integration.test.ts +4 -3
- package/src/mail-foundation/__tests__/mail-transport-contract.ts +58 -0
- package/src/mail-foundation/feature.ts +26 -24
- package/src/mail-transport-inmemory/__tests__/feature.test.ts +23 -0
- package/src/mail-transport-smtp/__tests__/feature.test.ts +65 -0
- package/src/managed-pages/__tests__/branding-coerce.test.ts +2 -6
- package/src/managed-pages/__tests__/i18n-surface.boot.test.ts +30 -0
- package/src/managed-pages/__tests__/managed-pages.integration.test.ts +137 -0
- package/src/managed-pages/feature.ts +45 -47
- package/src/managed-pages/handlers/branding.query.ts +5 -1
- package/src/managed-pages/handlers/by-tenant-published.query.ts +52 -0
- package/src/managed-pages/i18n.ts +66 -0
- package/src/managed-pages/screens/page-screens.ts +5 -1
- package/src/managed-pages/web/client-plugin.tsx +22 -0
- package/src/managed-pages/web/i18n.ts +24 -0
- package/src/managed-pages/web/index.ts +3 -0
- package/src/page-render/__tests__/layout.test.ts +63 -0
- package/src/page-render/__tests__/markdown.test.ts +29 -0
- package/src/page-render/branding.ts +4 -3
- package/src/page-render/index.ts +1 -1
- package/src/page-render/layout.ts +18 -3
- package/src/page-render/markdown.ts +1 -9
- package/src/page-render/web.ts +13 -0
- package/src/personal-access-tokens/__tests__/feature.test.ts +42 -0
- package/src/personal-access-tokens/__tests__/pat.integration.test.ts +239 -0
- package/src/personal-access-tokens/__tests__/scopes.test.ts +42 -0
- package/src/personal-access-tokens/constants.ts +33 -0
- package/src/personal-access-tokens/feature.ts +105 -0
- package/src/personal-access-tokens/handlers/available-scopes.query.ts +21 -0
- package/src/personal-access-tokens/handlers/create.write.ts +47 -0
- package/src/personal-access-tokens/handlers/list.query.ts +50 -0
- package/src/personal-access-tokens/handlers/revoke.write.ts +37 -0
- package/src/personal-access-tokens/hash.ts +17 -0
- package/src/personal-access-tokens/i18n.ts +5 -0
- package/src/personal-access-tokens/index.ts +31 -0
- package/src/personal-access-tokens/resolver.ts +58 -0
- package/src/personal-access-tokens/roles.ts +29 -0
- package/src/personal-access-tokens/schema/api-token.ts +63 -0
- package/src/personal-access-tokens/scopes.ts +43 -0
- package/src/personal-access-tokens/web/client-plugin.tsx +29 -0
- package/src/personal-access-tokens/web/i18n.ts +69 -0
- package/src/personal-access-tokens/web/index.ts +7 -0
- package/src/personal-access-tokens/web/pat-tokens-screen.tsx +265 -0
- package/src/readiness/__tests__/readiness.integration.test.ts +4 -3
- package/src/renderer-simple/__tests__/resolve-variables.test.ts +37 -0
- package/src/renderer-simple/resolve-variables.ts +3 -1
- package/src/secrets/__tests__/rotate.integration.test.ts +115 -18
- package/src/secrets/handlers/rotate.job.ts +77 -98
- package/src/secrets/secrets-context.ts +7 -38
- package/src/secrets/table.ts +7 -8
- package/src/seo/__tests__/llms-txt.test.ts +38 -0
- package/src/seo/__tests__/robots-txt.test.ts +18 -0
- package/src/seo/__tests__/schema-builders.test.ts +57 -0
- package/src/seo/__tests__/seo.integration.test.ts +350 -0
- package/src/seo/__tests__/sitemap.test.ts +38 -0
- package/src/seo/constants.ts +60 -0
- package/src/seo/feature.ts +316 -0
- package/src/seo/handlers/seo-config.query.ts +35 -0
- package/src/seo/index.ts +19 -0
- package/src/seo/llms-txt.ts +33 -0
- package/src/seo/robots-txt.ts +13 -0
- package/src/seo/schema-builders.ts +57 -0
- package/src/seo/sitemap.ts +33 -0
- package/src/sessions/__tests__/auto-revoke-binding.test.ts +71 -0
- package/src/sessions/__tests__/cleanup.integration.test.ts +2 -1
- package/src/sessions/__tests__/password-auto-revoke.integration.test.ts +11 -7
- package/src/sessions/__tests__/rebuild-survival.integration.test.ts +18 -7
- package/src/sessions/__tests__/session-store-registration.test.ts +21 -0
- package/src/sessions/__tests__/sessions-screens.boot.test.ts +55 -0
- package/src/sessions/__tests__/sessions.integration.test.ts +272 -20
- package/src/sessions/__tests__/test-helpers.ts +16 -3
- package/src/sessions/constants.ts +5 -0
- package/src/sessions/db/queries/cleanup.ts +2 -2
- package/src/sessions/feature.ts +175 -26
- package/src/sessions/handlers/detail.query.ts +36 -0
- package/src/sessions/handlers/list.query.ts +14 -9
- package/src/sessions/handlers/mine.query.ts +13 -8
- package/src/sessions/handlers/revoke-all-for-user.write.ts +51 -12
- package/src/sessions/handlers/revoke-all-others.write.ts +19 -1
- package/src/sessions/handlers/revoke.write.ts +13 -0
- package/src/sessions/i18n.ts +23 -0
- package/src/sessions/index.ts +9 -2
- package/src/sessions/schema/user-session.ts +8 -2
- package/src/sessions/session-callbacks.ts +99 -14
- package/src/sessions/session-checker-fail-open.test.ts +77 -0
- package/src/sessions/session-revoked-event.ts +29 -0
- package/src/sessions/testing.ts +14 -1
- package/src/shared/__tests__/chunked-entity-migration.test.ts +145 -0
- package/src/shared/chunked-entity-migration.ts +89 -0
- package/src/shared/decrypt-stored-pii.ts +26 -0
- package/src/shared/encrypt-for-direct-write.ts +23 -0
- package/src/shared/entities-of.ts +17 -0
- package/src/shared/grace-period.test.ts +26 -0
- package/src/shared/grace-period.ts +20 -0
- package/src/{auth-email-password/__tests__ → shared}/identity-v3-hash.test.ts +2 -2
- package/src/shared/index.ts +16 -0
- package/src/shared/map-with-concurrency.test.ts +104 -0
- package/src/shared/map-with-concurrency.ts +38 -0
- package/src/shared/password-hashing.test.ts +35 -0
- package/src/{auth-email-password → shared}/password-hashing.ts +11 -0
- package/src/shared/system-query.ts +5 -0
- package/src/{auth-email-password → shared}/token-burn-store.ts +1 -1
- package/src/subscription-mollie/__tests__/feature.test.ts +97 -0
- package/src/subscription-mollie/__tests__/mollie-foundation.integration.test.ts +12 -1
- package/src/subscription-mollie/__tests__/verify-webhook.test.ts +21 -0
- package/src/subscription-mollie/verify-webhook.ts +1 -0
- package/src/subscription-stripe/__tests__/feature.test.ts +127 -1
- package/src/subscription-stripe/__tests__/runtime.test.ts +14 -24
- package/src/subscription-stripe/__tests__/stripe-foundation.integration.test.ts +15 -5
- package/src/subscription-stripe/__tests__/verify-webhook.test.ts +20 -0
- package/src/subscription-stripe/feature.ts +2 -0
- package/src/subscription-stripe/verify-webhook.ts +3 -2
- package/src/tags/__tests__/schemas.test.ts +34 -0
- package/src/tags/__tests__/tags.integration.test.ts +12 -1
- package/src/tags/entity.ts +6 -1
- package/src/tags/feature.ts +2 -0
- package/src/tags/i18n.ts +5 -0
- package/src/tags/schemas.ts +9 -2
- package/src/tags/web/__tests__/tag-manager.test.tsx +333 -0
- package/src/tags/web/__tests__/tag-section.test.tsx +47 -1
- package/src/tags/web/tag-manager.tsx +1 -1
- package/src/tags/web/tag-section.tsx +6 -3
- package/src/template-resolver/testing.ts +2 -1
- package/src/tenant/__tests__/members-query-kms.integration.test.ts +98 -0
- package/src/tenant/__tests__/members-screens.boot.test.ts +51 -0
- package/src/tenant/__tests__/multi-tenant.integration.test.ts +1 -1
- package/src/tenant/__tests__/tenant-security.integration.test.ts +310 -0
- package/src/tenant/__tests__/tenant.integration.test.ts +8 -4
- package/src/tenant/constants.ts +5 -0
- package/src/tenant/feature.ts +19 -0
- package/src/tenant/handlers/active-tenant-ids.query.ts +8 -2
- package/src/tenant/handlers/cancel-invitation.write.ts +2 -2
- package/src/tenant/handlers/invitations.query.ts +21 -4
- package/src/tenant/handlers/members.query.ts +41 -6
- package/src/tenant/i18n.ts +28 -0
- package/src/tenant/index.ts +7 -2
- package/src/tenant/invitation-table.ts +3 -2
- package/src/tenant/schema/tenant.ts +27 -2
- package/src/tenant/screens.ts +3 -2
- package/src/tenant/seeding.ts +136 -3
- package/src/tenant/web/client-plugin.tsx +20 -0
- package/src/tenant/web/i18n.ts +43 -0
- package/src/tenant/web/index.ts +4 -0
- package/src/tenant/web/members-screen.tsx +270 -0
- package/src/tenant-lifecycle/__tests__/stages.test.ts +22 -0
- package/src/tenant-lifecycle/__tests__/tenant-lifecycle.integration.test.ts +471 -0
- package/src/tenant-lifecycle/boot-checks.ts +45 -0
- package/src/tenant-lifecycle/constants.ts +56 -0
- package/src/tenant-lifecycle/events.ts +46 -0
- package/src/tenant-lifecycle/feature.ts +112 -0
- package/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +79 -0
- package/src/tenant-lifecycle/handlers/request-destruction.write.ts +83 -0
- package/src/tenant-lifecycle/index.ts +7 -0
- package/src/tenant-lifecycle/lib/revoke-tenant-sessions.ts +16 -0
- package/src/tenant-lifecycle/lifecycle-gate.ts +65 -0
- package/src/tenant-lifecycle/run-tenant-destroy.ts +368 -0
- package/src/tenant-lifecycle/stages.ts +206 -0
- package/src/tenant-settings/__tests__/tenant-settings.integration.test.ts +115 -0
- package/src/tenant-settings/config.ts +44 -0
- package/src/tenant-settings/constants.ts +16 -0
- package/src/tenant-settings/feature.ts +49 -0
- package/src/tenant-settings/index.ts +8 -0
- package/src/tenant-settings/tenant-defaults.ts +67 -0
- package/src/text-content/web/__tests__/editor-read-only.test.tsx +33 -6
- package/src/text-content/web/__tests__/group-blocks.test.ts +28 -3
- package/src/text-content/web/client-plugin.tsx +126 -104
- package/src/tier-engine/__tests__/auto-default-tier.integration.test.ts +1 -1
- package/src/tier-engine/__tests__/resolver.integration.test.ts +1 -1
- package/src/tier-engine/__tests__/tier-engine.integration.test.ts +7 -4
- package/src/tier-engine/__tests__/trial-config-validation.test.ts +30 -0
- package/src/tier-engine/feature.ts +13 -6
- package/src/tier-engine/i18n.ts +8 -0
- package/src/tier-engine/web/tier-admin-screen.tsx +26 -19
- package/src/user/__tests__/stream-tenant-backfill.integration.test.ts +186 -0
- package/src/user/__tests__/user.integration.test.ts +26 -0
- package/src/user/constants.ts +1 -0
- package/src/user/db/queries/stream-tenant-backfill.ts +112 -0
- package/src/user/feature.ts +3 -0
- package/src/user/handlers/find-for-auth.query.ts +8 -6
- package/src/user/i18n.ts +20 -0
- package/src/user/index.ts +4 -0
- package/src/user/schema/user.ts +6 -2
- package/src/user/screens.ts +3 -15
- package/src/user/seeding.ts +8 -0
- package/src/user-data-rights/COMPLIANCE.md +21 -1
- package/src/user-data-rights/__tests__/anonymous-deletion-kms.integration.test.ts +145 -0
- package/src/user-data-rights/__tests__/anonymous-deletion.integration.test.ts +3 -0
- package/src/user-data-rights/__tests__/audit-log.integration.test.ts +67 -0
- package/src/user-data-rights/__tests__/boot-checks.test.ts +170 -0
- package/src/user-data-rights/__tests__/cross-data-matrix.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/download-by-token-resolver-trust.integration.test.ts +169 -0
- package/src/user-data-rights/__tests__/download.integration.test.ts +101 -33
- package/src/user-data-rights/__tests__/email-templates.test.ts +15 -0
- package/src/user-data-rights/__tests__/export-encrypted-fields.integration.test.ts +154 -0
- package/src/user-data-rights/__tests__/export-job-idempotency.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/export-job-list-cross-tenant.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/file-binary-forget-cleanup.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/file-binary-forget-failure.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/file-retention.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/file-storage-unification.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/forget-cleanup-hook-ordering.integration.test.ts +41 -62
- package/src/user-data-rights/__tests__/forget-hook-registry.integration.test.ts +110 -0
- package/src/user-data-rights/__tests__/inspector-screens.boot.test.ts +14 -0
- package/src/user-data-rights/__tests__/mail-default-bridge.integration.test.ts +19 -2
- package/src/user-data-rights/__tests__/read-users-rebuild-survives-lifecycle.integration.test.ts +25 -14
- package/src/user-data-rights/__tests__/request-cancel-deletion.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/request-deletion-callback.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/request-export.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/restriction-flow.integration.test.ts +172 -19
- package/src/user-data-rights/__tests__/run-export-jobs-cron-context.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/run-export-jobs.integration.test.ts +257 -26
- package/src/user-data-rights/__tests__/run-forget-cleanup.integration.test.ts +339 -5
- package/src/user-data-rights/__tests__/run-user-export.integration.test.ts +77 -1
- package/src/user-data-rights/__tests__/tenant-model-erasure.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/user-data-rights.integration.test.ts +9 -1
- package/src/user-data-rights/boot-checks.ts +70 -0
- package/src/user-data-rights/db/queries/__tests__/forget-cleanup.integration.test.ts +69 -0
- package/src/user-data-rights/db/queries/forget-cleanup.ts +9 -2
- package/src/user-data-rights/email-templates.ts +6 -6
- package/src/user-data-rights/feature.ts +162 -43
- package/src/user-data-rights/handlers/cancel-deletion.write.ts +8 -11
- package/src/user-data-rights/handlers/deletion-grace-period.ts +4 -1
- package/src/user-data-rights/handlers/download-by-job.query.ts +26 -18
- package/src/user-data-rights/handlers/download-by-token.query.ts +23 -15
- package/src/user-data-rights/handlers/lift-restriction.write.ts +44 -19
- package/src/user-data-rights/handlers/list-download-attempts.query.ts +1 -0
- package/src/user-data-rights/handlers/my-audit-log.query.ts +1 -0
- package/src/user-data-rights/handlers/request-deletion-by-email.write.ts +3 -1
- package/src/user-data-rights/handlers/restrict-account.write.ts +58 -7
- package/src/user-data-rights/handlers/run-forget-cleanup.write.ts +2 -0
- package/src/user-data-rights/i18n.ts +35 -32
- package/src/user-data-rights/lib/is-admin-actor.ts +19 -0
- package/src/user-data-rights/lib/storage-provider-resolver.ts +6 -5
- package/src/user-data-rights/lib/update-user-lifecycle.ts +8 -24
- package/src/user-data-rights/run-export-jobs.ts +55 -19
- package/src/user-data-rights/run-forget-cleanup.ts +112 -7
- package/src/user-data-rights/run-user-export.ts +70 -4
- package/src/user-data-rights/schema/download-token.ts +5 -3
- package/src/user-data-rights/schema/export-job.ts +2 -0
- package/src/user-data-rights/screens.ts +2 -1
- package/src/user-data-rights/web/__tests__/privacy-center-screen.test.tsx +1 -0
- package/src/user-data-rights/web/__tests__/public-deletion-gate.test.tsx +7 -1
- package/src/user-data-rights/web/client-plugin.tsx +6 -1
- package/src/user-data-rights/web/i18n.ts +30 -10
- package/src/user-data-rights/web/privacy-center-screen.tsx +5 -3
- package/src/user-data-rights-defaults/__tests__/bundled-entities.userdata-hooks.integration.test.ts +455 -0
- package/src/user-data-rights-defaults/__tests__/feature-mounted.test.ts +24 -0
- package/src/user-data-rights-defaults/__tests__/user-data-rights-defaults.integration.test.ts +33 -9
- package/src/user-data-rights-defaults/feature.ts +78 -1
- package/src/user-data-rights-defaults/hooks/api-token.userdata-hook.ts +37 -0
- package/src/user-data-rights-defaults/hooks/config-value.userdata-hook.ts +51 -0
- package/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +37 -0
- package/src/user-data-rights-defaults/hooks/feature-mounted.ts +10 -0
- package/src/user-data-rights-defaults/hooks/in-app-message.userdata-hook.ts +38 -0
- package/src/user-data-rights-defaults/hooks/job-run.userdata-hook.ts +35 -0
- package/src/user-data-rights-defaults/hooks/notification-preference.userdata-hook.ts +54 -0
- package/src/user-data-rights-defaults/hooks/tenant-invitation.userdata-hook.ts +112 -0
- package/src/user-data-rights-defaults/hooks/user-session.userdata-hook.ts +42 -0
- package/src/user-data-rights-defaults/index.ts +26 -0
- package/src/user-profile/__tests__/change-email.integration.test.ts +7 -1
- package/src/user-profile/__tests__/profile-screen.test.tsx +5 -0
- package/src/user-profile/feature.ts +4 -1
- package/src/user-profile/handlers/change-email.write.ts +15 -10
- package/src/user-profile/web/profile-screen.tsx +15 -3
- package/src/auth-email-password/stream-tenant.ts +0 -31
- package/src/feature-toggles/db/queries/toggle-state.ts +0 -25
- package/src/legal-pages/security-headers.ts +0 -1
- package/src/sessions/schema/index.ts +0 -5
- package/src/tenant/schema/index.ts +0 -5
- package/src/user/schema/index.ts +0 -5
- /package/src/{auth-email-password → shared}/identity-v3-hash.ts +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
access,
|
|
3
|
+
type ConfigKeyDefinition,
|
|
4
|
+
createTenantConfig,
|
|
5
|
+
DEFAULT_CURRENCIES,
|
|
6
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
7
|
+
|
|
8
|
+
// ISO-639-1, optional ISO-3166-1 region ("de" or "de-DE") — apps constrain
|
|
9
|
+
// further via their own i18n setup; this only guards against garbage input.
|
|
10
|
+
const LOCALE_PATTERN = { regex: "^[a-z]{2}(-[A-Z]{2})?$" } as const;
|
|
11
|
+
|
|
12
|
+
export type TenantSettingsKeyOptions = {
|
|
13
|
+
/** Selectable currencies. Default: DEFAULT_CURRENCIES (engine). */
|
|
14
|
+
readonly currencies?: readonly string[];
|
|
15
|
+
/** Fallback when a tenant never set the key. Default: "EUR". */
|
|
16
|
+
readonly defaultCurrency?: string;
|
|
17
|
+
/** Fallback when a tenant never set the key. Default: "en". */
|
|
18
|
+
readonly defaultLocale?: string;
|
|
19
|
+
/** Who may change the tenant's settings. Default: access.admin. */
|
|
20
|
+
readonly write?: readonly string[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function buildTenantSettingsKeys(
|
|
24
|
+
opts: TenantSettingsKeyOptions = {},
|
|
25
|
+
): Record<string, ConfigKeyDefinition> {
|
|
26
|
+
const write = opts.write ?? access.admin;
|
|
27
|
+
return {
|
|
28
|
+
// `mask` surfaces the key in the self-populating Settings-Hub (Tenant-
|
|
29
|
+
// Audience) without a hand-written r.screen/r.nav — see
|
|
30
|
+
// samples/recipes/managed-config for the mechanism.
|
|
31
|
+
currency: createTenantConfig("select", {
|
|
32
|
+
default: opts.defaultCurrency ?? "EUR",
|
|
33
|
+
options: opts.currencies ?? DEFAULT_CURRENCIES,
|
|
34
|
+
write,
|
|
35
|
+
mask: { title: "tenant-settings.currency", icon: "coins", order: 1 },
|
|
36
|
+
}),
|
|
37
|
+
locale: createTenantConfig("text", {
|
|
38
|
+
default: opts.defaultLocale ?? "en",
|
|
39
|
+
pattern: LOCALE_PATTERN,
|
|
40
|
+
write,
|
|
41
|
+
mask: { title: "tenant-settings.locale", icon: "languages", order: 2 },
|
|
42
|
+
}),
|
|
43
|
+
} satisfies Record<string, ConfigKeyDefinition>;
|
|
44
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// tenant-settings bundle constants — feature-name + qualified config names.
|
|
2
|
+
//
|
|
3
|
+
// Motivation (solon#P19): apps kept hard-coding a single currency/locale
|
|
4
|
+
// literal per money/select field ("EUR", "de") instead of one per-tenant
|
|
5
|
+
// setting — expensive to retrofit once a second tenant needs a different
|
|
6
|
+
// default. This bundle is the one place that setting lives.
|
|
7
|
+
|
|
8
|
+
export const TENANT_SETTINGS_FEATURE_NAME = "tenant-settings";
|
|
9
|
+
|
|
10
|
+
// Qualified config key names (QN format: scope:config:name) for
|
|
11
|
+
// ctx.config(...) reads. Clients reference the object instead of magic
|
|
12
|
+
// strings (mirror tags' Handlers/Queries constants).
|
|
13
|
+
export const TenantSettingsConfig = {
|
|
14
|
+
currency: "tenant-settings:config:currency",
|
|
15
|
+
locale: "tenant-settings:config:locale",
|
|
16
|
+
} as const;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// tenant-settings — per-tenant Currency + Locale defaults.
|
|
2
|
+
//
|
|
3
|
+
// Any handler that needs a default (money-field currency, a locale-select
|
|
4
|
+
// field, ...) reads it via `ctx.config(TenantSettingsConfig.currency)` /
|
|
5
|
+
// `ctx.config(TenantSettingsConfig.locale)` — or use
|
|
6
|
+
// `defineCreateWithTenantDefaults` (tenant-defaults.ts) to wire it into an
|
|
7
|
+
// entity's create-handler without writing that plumbing by hand.
|
|
8
|
+
//
|
|
9
|
+
// No entities, no hand-written screen — `mask` on both keys makes them
|
|
10
|
+
// surface in the self-populating Settings-Hub (Tenant-Audience) instead;
|
|
11
|
+
// `buildConfigFeatureSchema` (config feature) derives the configEdit
|
|
12
|
+
// screen + nav entry from the key's scope/type/mask, same mechanism as
|
|
13
|
+
// samples/recipes/managed-config.
|
|
14
|
+
|
|
15
|
+
import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
16
|
+
import { buildTenantSettingsKeys, type TenantSettingsKeyOptions } from "./config";
|
|
17
|
+
import { TENANT_SETTINGS_FEATURE_NAME } from "./constants";
|
|
18
|
+
|
|
19
|
+
export type TenantSettingsFeatureOptions = TenantSettingsKeyOptions;
|
|
20
|
+
|
|
21
|
+
export function createTenantSettingsFeature(
|
|
22
|
+
opts: TenantSettingsFeatureOptions = {},
|
|
23
|
+
): FeatureDefinition {
|
|
24
|
+
return defineFeature(TENANT_SETTINGS_FEATURE_NAME, (r) => {
|
|
25
|
+
r.requires("config");
|
|
26
|
+
r.describe(
|
|
27
|
+
"Per-tenant Currency + Locale defaults, exposed as two config keys (tenant-settings:config:currency, tenant-settings:config:locale) that surface in the self-populating Settings-Hub via `mask`. Pair with defineCreateWithTenantDefaults to auto-fill a money field's currency or a locale field on create instead of hard-coding a literal per entity.",
|
|
28
|
+
);
|
|
29
|
+
r.config({ keys: buildTenantSettingsKeys(opts) });
|
|
30
|
+
r.translations({
|
|
31
|
+
keys: {
|
|
32
|
+
"tenant-settings.currency": { de: "Standard-Währung", en: "Default Currency" },
|
|
33
|
+
"tenant-settings.locale": { de: "Standard-Sprache", en: "Default Locale" },
|
|
34
|
+
// Settings-Hub-derived configEdit screen title (buildConfigFeatureSchema) —
|
|
35
|
+
// required by the i18n boot-validator whenever a feature masks config keys.
|
|
36
|
+
"screen:tenant-settings-system.title": {
|
|
37
|
+
de: "Tenant-Einstellungen",
|
|
38
|
+
en: "Tenant Settings",
|
|
39
|
+
},
|
|
40
|
+
"screen:tenant-settings-tenant.title": {
|
|
41
|
+
de: "Tenant-Einstellungen",
|
|
42
|
+
en: "Tenant Settings",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const tenantSettingsFeature: FeatureDefinition = createTenantSettingsFeature();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { buildTenantSettingsKeys, type TenantSettingsKeyOptions } from "./config";
|
|
2
|
+
export { TENANT_SETTINGS_FEATURE_NAME, TenantSettingsConfig } from "./constants";
|
|
3
|
+
export {
|
|
4
|
+
createTenantSettingsFeature,
|
|
5
|
+
type TenantSettingsFeatureOptions,
|
|
6
|
+
tenantSettingsFeature,
|
|
7
|
+
} from "./feature";
|
|
8
|
+
export { defineCreateWithTenantDefaults } from "./tenant-defaults";
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Wrapper around defineEntityCreateHandler: fills currency/locale fields from
|
|
2
|
+
// tenant-settings config when the caller omits them — instead of repeating
|
|
3
|
+
// field literals ("EUR"/"de") on every entity.
|
|
4
|
+
//
|
|
5
|
+
// buildInsertSchema always requires `currency` inside a money field (even when
|
|
6
|
+
// the field itself is optional), so the caller cannot simply omit currency
|
|
7
|
+
// without failing schema validation before the handler runs. This helper uses
|
|
8
|
+
// a schema extension that makes those sub-keys optional, instead of the
|
|
9
|
+
// generic defineEntityCreateHandler.
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
type AccessRule,
|
|
13
|
+
buildInsertSchema,
|
|
14
|
+
createEntityExecutor,
|
|
15
|
+
type EntityDefinition,
|
|
16
|
+
type WriteHandlerDef,
|
|
17
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
import { TenantSettingsConfig } from "./constants";
|
|
20
|
+
|
|
21
|
+
const OPTIONAL_CURRENCY_MONEY = z.object({ amount: z.number(), currency: z.string().optional() });
|
|
22
|
+
|
|
23
|
+
export function defineCreateWithTenantDefaults(
|
|
24
|
+
entityName: string,
|
|
25
|
+
entity: EntityDefinition,
|
|
26
|
+
options: {
|
|
27
|
+
readonly access?: AccessRule;
|
|
28
|
+
/** Money field names whose `currency` is filled from tenant-settings when missing. */
|
|
29
|
+
readonly currencyFields?: readonly string[];
|
|
30
|
+
/** Field name (select/text) that receives the tenant locale when missing. */
|
|
31
|
+
readonly localeField?: string;
|
|
32
|
+
},
|
|
33
|
+
): WriteHandlerDef {
|
|
34
|
+
const { executor } = createEntityExecutor(entityName, entity);
|
|
35
|
+
const relax: Record<string, z.ZodTypeAny> = {};
|
|
36
|
+
for (const field of options.currencyFields ?? []) {
|
|
37
|
+
const def = entity.fields[field];
|
|
38
|
+
if (!def) throw new Error(`defineCreateWithTenantDefaults: unknown field "${field}"`);
|
|
39
|
+
const isRequired = "required" in def && def.required === true;
|
|
40
|
+
relax[field] = isRequired ? OPTIONAL_CURRENCY_MONEY : OPTIONAL_CURRENCY_MONEY.optional();
|
|
41
|
+
}
|
|
42
|
+
const baseSchema = buildInsertSchema(entity);
|
|
43
|
+
const schema = Object.keys(relax).length > 0 ? baseSchema.extend(relax) : baseSchema;
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
name: `${entityName}:create`,
|
|
47
|
+
schema,
|
|
48
|
+
...(options.access && { access: options.access }),
|
|
49
|
+
handler: async (event, ctx) => {
|
|
50
|
+
const payload = { ...(event.payload as Record<string, unknown>) };
|
|
51
|
+
|
|
52
|
+
for (const field of options.currencyFields ?? []) {
|
|
53
|
+
const value = payload[field] as { amount?: number; currency?: string } | undefined;
|
|
54
|
+
if (value && typeof value === "object" && !value.currency) {
|
|
55
|
+
const currency = await ctx.config?.(TenantSettingsConfig.currency);
|
|
56
|
+
if (typeof currency === "string") payload[field] = { ...value, currency };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (options.localeField && !payload[options.localeField]) {
|
|
60
|
+
const locale = await ctx.config?.(TenantSettingsConfig.locale);
|
|
61
|
+
if (typeof locale === "string") payload[options.localeField] = locale;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return executor.create(payload, event.user, ctx.db);
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
PrimitivesProvider,
|
|
10
10
|
} from "@cosmicdrift/kumiko-renderer";
|
|
11
11
|
import { defaultPrimitives } from "@cosmicdrift/kumiko-renderer-web";
|
|
12
|
-
import { render, screen } from "@testing-library/react";
|
|
12
|
+
import { act, fireEvent, render, screen } from "@testing-library/react";
|
|
13
13
|
import type { ReactNode } from "react";
|
|
14
14
|
import { textContentClient } from "../client-plugin";
|
|
15
15
|
|
|
@@ -20,18 +20,23 @@ mock.module("@cosmicdrift/kumiko-bundled-features/auth-email-password/web", () =
|
|
|
20
20
|
const actual_renderer = await import("@cosmicdrift/kumiko-renderer");
|
|
21
21
|
mock.module("@cosmicdrift/kumiko-renderer", () => ({
|
|
22
22
|
...actual_renderer,
|
|
23
|
-
useDispatcher: mock(
|
|
24
|
-
write: mock(),
|
|
25
|
-
query: mock(),
|
|
26
|
-
})),
|
|
23
|
+
useDispatcher: mock(),
|
|
27
24
|
useQuery: mock(() => ({
|
|
28
|
-
data: {
|
|
25
|
+
data: {
|
|
26
|
+
slug: "imprint",
|
|
27
|
+
lang: "de",
|
|
28
|
+
title: "Impressum",
|
|
29
|
+
body: "Inhalt",
|
|
30
|
+
folder: "legal",
|
|
31
|
+
},
|
|
29
32
|
loading: false,
|
|
30
33
|
error: null,
|
|
31
34
|
refetch: mock(),
|
|
32
35
|
})),
|
|
33
36
|
}));
|
|
34
37
|
|
|
38
|
+
const { useDispatcher } = await import("@cosmicdrift/kumiko-renderer");
|
|
39
|
+
|
|
35
40
|
const TARGET = {
|
|
36
41
|
featureId: "text-content",
|
|
37
42
|
action: "edit",
|
|
@@ -109,3 +114,25 @@ describe("TextContentEditor — role-based write-access", () => {
|
|
|
109
114
|
expect(screen.queryByRole("button", { name: /^speichern/i })).toBeNull();
|
|
110
115
|
});
|
|
111
116
|
});
|
|
117
|
+
|
|
118
|
+
describe("TextContentEditor — handleSave", () => {
|
|
119
|
+
test("reicht das geladene folder unverändert an den Write-Payload durch (#898)", async () => {
|
|
120
|
+
// biome-ignore lint/suspicious/noExplicitAny: Bun mock function
|
|
121
|
+
(useShellUser as any).mockReturnValue({ id: "u1", roles: ["TenantAdmin"] });
|
|
122
|
+
const write = mock(() => Promise.resolve({ isSuccess: true, data: { isNew: false } }));
|
|
123
|
+
// biome-ignore lint/suspicious/noExplicitAny: Bun mock function
|
|
124
|
+
(useDispatcher as any).mockReturnValue({ write, query: mock() });
|
|
125
|
+
|
|
126
|
+
const Editor = getEditor();
|
|
127
|
+
render(<Editor target={TARGET} onClose={() => {}} />, { wrapper: Wrapper });
|
|
128
|
+
|
|
129
|
+
await act(async () => {
|
|
130
|
+
fireEvent.click(screen.getByRole("button", { name: /speichern/i }));
|
|
131
|
+
await Promise.resolve();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
expect(write).toHaveBeenCalledTimes(1);
|
|
135
|
+
const [, payload] = write.mock.calls[0] as unknown as [unknown, { folder: string | null }];
|
|
136
|
+
expect(payload.folder).toBe("legal");
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -53,7 +53,7 @@ describe("groupBlocksByFolder", () => {
|
|
|
53
53
|
args: { slug: "imprint", lang: "de" },
|
|
54
54
|
});
|
|
55
55
|
expect(root.children).toBeUndefined();
|
|
56
|
-
expect(root.icon).
|
|
56
|
+
expect(root.icon).toBe("file");
|
|
57
57
|
});
|
|
58
58
|
test('folder="page" → Folder-Container mit child', () => {
|
|
59
59
|
const nodes = inside(
|
|
@@ -101,7 +101,7 @@ describe("groupBlocksByFolder", () => {
|
|
|
101
101
|
);
|
|
102
102
|
expect(nodes).toHaveLength(2);
|
|
103
103
|
expect(nodes[0]?.label).toBe("imprint");
|
|
104
|
-
expect(nodes[0]?.icon).
|
|
104
|
+
expect(nodes[0]?.icon).toBe("file");
|
|
105
105
|
expect(nodes[1]?.label).toBe("page");
|
|
106
106
|
expect(nodes[1]?.icon).toBe("folder");
|
|
107
107
|
});
|
|
@@ -184,12 +184,37 @@ describe("groupBlocksByFolder", () => {
|
|
|
184
184
|
const leaf = nodes[0];
|
|
185
185
|
const folder = nodes[1];
|
|
186
186
|
expect(leaf?.label).toBe("Page-Root");
|
|
187
|
-
expect(leaf?.icon).
|
|
187
|
+
expect(leaf?.icon).toBe("file");
|
|
188
188
|
expect(leaf?.target).toBeDefined();
|
|
189
189
|
expect(folder?.label).toBe("page");
|
|
190
190
|
expect(folder?.icon).toBe("folder");
|
|
191
191
|
expect(folder?.target).toBeUndefined();
|
|
192
192
|
});
|
|
193
|
+
test("tenantIdOverride wird in die Edit-Target-Args gereicht (SystemAdmin SYSTEM-Tenant-Content)", () => {
|
|
194
|
+
const nodes = inside(
|
|
195
|
+
groupBlocksByFolder(
|
|
196
|
+
[block({ slug: "imprint", folder: null }), block({ slug: "hero", folder: "page" })],
|
|
197
|
+
"00000000-0000-4000-8000-000000000000",
|
|
198
|
+
),
|
|
199
|
+
);
|
|
200
|
+
// Root-Leaf trägt den Override.
|
|
201
|
+
expect(nodes[0]?.target?.args).toEqual({
|
|
202
|
+
slug: "imprint",
|
|
203
|
+
lang: "de",
|
|
204
|
+
tenantIdOverride: "00000000-0000-4000-8000-000000000000",
|
|
205
|
+
});
|
|
206
|
+
// Auch der Leaf im Folder (rekursiv durchgereicht).
|
|
207
|
+
const folderChild = childrenArray(nodes[1]?.children)[0];
|
|
208
|
+
expect(folderChild?.target?.args).toEqual({
|
|
209
|
+
slug: "hero",
|
|
210
|
+
lang: "de",
|
|
211
|
+
tenantIdOverride: "00000000-0000-4000-8000-000000000000",
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
test("ohne tenantIdOverride bleiben die Args frei davon (Default: Session-Tenant)", () => {
|
|
215
|
+
const nodes = inside(groupBlocksByFolder([block({ slug: "imprint", folder: null })]));
|
|
216
|
+
expect(nodes[0]?.target?.args).toEqual({ slug: "imprint", lang: "de" });
|
|
217
|
+
});
|
|
193
218
|
test("Wrapper-Folder 'Content' umschließt alle blocks", () => {
|
|
194
219
|
// V.1.5d Wrapper-Convention: groupBlocksByFolder gibt EINEN Knoten
|
|
195
220
|
// zurück (den Wrapper), Inhalt liegt eine Ebene tiefer.
|
|
@@ -74,13 +74,21 @@ function newFolderNode(): FolderNode {
|
|
|
74
74
|
return { leaves: [], subFolders: new Map() };
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
function attachBlock(root: FolderNode, block: BlockSummary): void {
|
|
77
|
+
function attachBlock(root: FolderNode, block: BlockSummary, tenantIdOverride?: string): void {
|
|
78
78
|
const leaf: TreeNode = {
|
|
79
79
|
label: block.title || block.slug,
|
|
80
|
+
icon: "file",
|
|
80
81
|
target: {
|
|
81
82
|
featureId: "text-content",
|
|
82
83
|
action: "edit",
|
|
83
|
-
|
|
84
|
+
// tenantIdOverride travels with the node → the editor's by-slug read +
|
|
85
|
+
// set write target the same tenant the tree was loaded from (SystemAdmin
|
|
86
|
+
// editing SYSTEM-tenant content). Omitted → session tenant.
|
|
87
|
+
args: {
|
|
88
|
+
slug: block.slug,
|
|
89
|
+
lang: block.lang,
|
|
90
|
+
...(tenantIdOverride !== undefined && { tenantIdOverride }),
|
|
91
|
+
},
|
|
84
92
|
},
|
|
85
93
|
state: block.body ? "filled" : "stub",
|
|
86
94
|
};
|
|
@@ -116,10 +124,13 @@ function renderFolderNode(node: FolderNode): TreeNode[] {
|
|
|
116
124
|
return result;
|
|
117
125
|
}
|
|
118
126
|
|
|
119
|
-
export function groupBlocksByFolder(
|
|
127
|
+
export function groupBlocksByFolder(
|
|
128
|
+
blocks: readonly BlockSummary[],
|
|
129
|
+
tenantIdOverride?: string,
|
|
130
|
+
): readonly TreeNode[] {
|
|
120
131
|
const root = newFolderNode();
|
|
121
132
|
for (const block of blocks) {
|
|
122
|
-
attachBlock(root, block);
|
|
133
|
+
attachBlock(root, block, tenantIdOverride);
|
|
123
134
|
}
|
|
124
135
|
const rendered = renderFolderNode(root);
|
|
125
136
|
if (rendered.length === 0) return [];
|
|
@@ -133,47 +144,52 @@ export function groupBlocksByFolder(blocks: readonly BlockSummary[]): readonly T
|
|
|
133
144
|
];
|
|
134
145
|
}
|
|
135
146
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const text = await r.text().catch(() => r.statusText);
|
|
155
|
-
throw new Error(`text-content load failed: ${r.status} ${text}`);
|
|
156
|
-
}
|
|
157
|
-
return r.json();
|
|
158
|
-
})
|
|
159
|
-
.then((data: ByTenantResponse) => {
|
|
160
|
-
// Der App-seitige r.nav-Knoten IST der "Content"-Container → die
|
|
161
|
-
// Provider-Kinder sind die Folder/Leaves darunter, nicht der Wrapper.
|
|
162
|
-
const content = groupBlocksByFolder(data.data.blocks)[0];
|
|
163
|
-
emit(content !== undefined && Array.isArray(content.children) ? content.children : []);
|
|
147
|
+
// tenantIdOverride (SystemAdmin-only) lets an app point the Content tree at a
|
|
148
|
+
// tenant other than the session's — publicstatus seeds marketing/legal blocks
|
|
149
|
+
// on SYSTEM_TENANT_ID, so its SystemAdmin must read that tenant, not their own.
|
|
150
|
+
function makeTreeProvider(tenantIdOverride?: string): TreeChildrenSubscribe {
|
|
151
|
+
return () => (emit, emitError) => {
|
|
152
|
+
// CSRF-Header bei authenticated requests pflicht (auth-middleware
|
|
153
|
+
// double-submit pattern). Anonymous/Pre-Login wäre csrf-token=undefined
|
|
154
|
+
// → header weggelassen → server lässt die anonymous-Variante durch.
|
|
155
|
+
const headers: Record<string, string> = { "content-type": "application/json" };
|
|
156
|
+
const csrf = readCsrfToken();
|
|
157
|
+
if (csrf !== undefined) headers[CSRF_HEADER_NAME] = csrf;
|
|
158
|
+
fetch("/api/query", {
|
|
159
|
+
method: "POST",
|
|
160
|
+
headers,
|
|
161
|
+
body: JSON.stringify({
|
|
162
|
+
type: TextContentQueries.byTenant,
|
|
163
|
+
payload: tenantIdOverride !== undefined ? { tenantIdOverride } : {},
|
|
164
|
+
}),
|
|
164
165
|
})
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
166
|
+
.then(async (r) => {
|
|
167
|
+
if (!r.ok) {
|
|
168
|
+
// 403 (CSRF/auth) oder 5xx — explicit error statt silent empty.
|
|
169
|
+
const text = await r.text().catch(() => r.statusText);
|
|
170
|
+
throw new Error(`text-content load failed: ${r.status} ${text}`);
|
|
171
|
+
}
|
|
172
|
+
return r.json();
|
|
173
|
+
})
|
|
174
|
+
.then((data: ByTenantResponse) => {
|
|
175
|
+
// Der App-seitige r.nav-Knoten IST der "Content"-Container → die
|
|
176
|
+
// Provider-Kinder sind die Folder/Leaves darunter, nicht der Wrapper.
|
|
177
|
+
const content = groupBlocksByFolder(data.data.blocks, tenantIdOverride)[0];
|
|
178
|
+
emit(content !== undefined && Array.isArray(content.children) ? content.children : []);
|
|
179
|
+
})
|
|
180
|
+
.catch((e) => {
|
|
181
|
+
// V.1.4: explicit error-Signal via emitError. ProviderBranch zeigt
|
|
182
|
+
// Banner + Retry-Button. Fallback auf emit([]) wenn der Consumer
|
|
183
|
+
// kein emitError unterstützt (Tests etc.).
|
|
184
|
+
if (emitError) {
|
|
185
|
+
emitError(e instanceof Error ? e : new Error(String(e)));
|
|
186
|
+
} else {
|
|
187
|
+
emit([]);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
return () => {};
|
|
191
|
+
};
|
|
192
|
+
}
|
|
177
193
|
|
|
178
194
|
// V.1.3 echte Edit-Form: lädt aktuelle Werte via by-slug-query, lässt
|
|
179
195
|
// TenantAdmin/SystemAdmin title + body editieren, dispatcht set-write
|
|
@@ -195,6 +211,7 @@ type TextBlock = {
|
|
|
195
211
|
readonly lang: string;
|
|
196
212
|
readonly title: string;
|
|
197
213
|
readonly body: string | null;
|
|
214
|
+
readonly folder: string | null;
|
|
198
215
|
readonly updatedAt: string;
|
|
199
216
|
};
|
|
200
217
|
|
|
@@ -202,9 +219,11 @@ type SetResponse = { readonly slug: string; readonly lang: string; readonly isNe
|
|
|
202
219
|
|
|
203
220
|
function TextContentEditor({
|
|
204
221
|
target,
|
|
205
|
-
onClose,
|
|
206
222
|
}: {
|
|
207
223
|
readonly target: TargetRef;
|
|
224
|
+
// onClose bleibt Teil des Resolver-Contracts (Visual-Panel liefert es),
|
|
225
|
+
// wird aber nicht mehr gebraucht: die Tree-Auswahl navigiert, kein
|
|
226
|
+
// Schließen-Button mehr. Nicht destrukturiert → kein unused binding.
|
|
208
227
|
readonly onClose: () => void;
|
|
209
228
|
}): ReactNode {
|
|
210
229
|
// @cast-boundary visual-tree-args — TargetRef.args ist erased zu
|
|
@@ -213,9 +232,12 @@ function TextContentEditor({
|
|
|
213
232
|
// zu Event-Payloads. Optional-Chain absorbiert fehlende Felder ohne
|
|
214
233
|
// throw, damit der Editor auch bei manuellem URL-Tampering nicht
|
|
215
234
|
// crasht (TargetRef könnte aus old localStorage / URL-State stammen).
|
|
216
|
-
const args = target.args as
|
|
235
|
+
const args = target.args as
|
|
236
|
+
| { slug?: string; lang?: string; tenantIdOverride?: string }
|
|
237
|
+
| undefined;
|
|
217
238
|
const slug = args?.slug ?? "";
|
|
218
239
|
const lang = args?.lang ?? "";
|
|
240
|
+
const tenantIdOverride = args?.tenantIdOverride;
|
|
219
241
|
|
|
220
242
|
const { Form, Field, Input, Button, Banner } = usePrimitives();
|
|
221
243
|
const dispatcher = useDispatcher();
|
|
@@ -232,7 +254,7 @@ function TextContentEditor({
|
|
|
232
254
|
error: loadError,
|
|
233
255
|
} = useQuery<TextBlock | null>(
|
|
234
256
|
TextContentQueries.bySlug,
|
|
235
|
-
{ slug, lang },
|
|
257
|
+
{ slug, lang, ...(tenantIdOverride !== undefined && { tenantIdOverride }) },
|
|
236
258
|
{ enabled: slug !== "" && lang !== "" },
|
|
237
259
|
);
|
|
238
260
|
|
|
@@ -266,6 +288,8 @@ function TextContentEditor({
|
|
|
266
288
|
lang,
|
|
267
289
|
title,
|
|
268
290
|
body: body.length > 0 ? body : null,
|
|
291
|
+
folder: loaded?.folder ?? null,
|
|
292
|
+
...(tenantIdOverride !== undefined && { tenantIdOverride }),
|
|
269
293
|
});
|
|
270
294
|
if (result.isSuccess) {
|
|
271
295
|
setSavedMsg(result.data.isNew ? "Neu angelegt." : "Gespeichert.");
|
|
@@ -290,61 +314,53 @@ function TextContentEditor({
|
|
|
290
314
|
const disabled = submitting || loading || !canWrite;
|
|
291
315
|
|
|
292
316
|
return (
|
|
293
|
-
<
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
{canWrite && (
|
|
341
|
-
<Button type="submit" loading={submitting} disabled={disabled}>
|
|
342
|
-
{submitting ? "Speichern…" : "Speichern"}
|
|
343
|
-
</Button>
|
|
344
|
-
)}
|
|
345
|
-
</Form>
|
|
346
|
-
</div>
|
|
347
|
-
</div>
|
|
317
|
+
<Form
|
|
318
|
+
onSubmit={onSubmit}
|
|
319
|
+
testId="text-content-editor"
|
|
320
|
+
title={title || slug || "—"}
|
|
321
|
+
subtitle={lang !== "" ? `${slug} · (${lang})` : slug}
|
|
322
|
+
actions={
|
|
323
|
+
canWrite ? (
|
|
324
|
+
<Button type="submit" loading={submitting} disabled={disabled}>
|
|
325
|
+
{submitting ? "Speichern…" : "Speichern"}
|
|
326
|
+
</Button>
|
|
327
|
+
) : undefined
|
|
328
|
+
}
|
|
329
|
+
>
|
|
330
|
+
{loading && <Banner variant="loading">Lädt aktuellen Stand…</Banner>}
|
|
331
|
+
{loadError !== null && (
|
|
332
|
+
<Banner variant="error">Konnte Block nicht laden: {loadError.code}</Banner>
|
|
333
|
+
)}
|
|
334
|
+
{!canWrite && !loading && (
|
|
335
|
+
<Banner variant="info">
|
|
336
|
+
Read-only — TenantAdmin- oder SystemAdmin-Rolle für Änderungen erforderlich.
|
|
337
|
+
</Banner>
|
|
338
|
+
)}
|
|
339
|
+
<Field id="text-content-title" label="Titel" required>
|
|
340
|
+
<Input
|
|
341
|
+
kind="text"
|
|
342
|
+
id="text-content-title"
|
|
343
|
+
name="text-content-title"
|
|
344
|
+
value={title}
|
|
345
|
+
onChange={setTitle}
|
|
346
|
+
disabled={disabled}
|
|
347
|
+
required
|
|
348
|
+
/>
|
|
349
|
+
</Field>
|
|
350
|
+
<Field id="text-content-body" label="Inhalt">
|
|
351
|
+
<Input
|
|
352
|
+
kind="textarea"
|
|
353
|
+
id="text-content-body"
|
|
354
|
+
name="text-content-body"
|
|
355
|
+
value={body}
|
|
356
|
+
onChange={setBody}
|
|
357
|
+
disabled={disabled}
|
|
358
|
+
rows={14}
|
|
359
|
+
/>
|
|
360
|
+
</Field>
|
|
361
|
+
{saveError !== null && <Banner variant="error">{saveError}</Banner>}
|
|
362
|
+
{savedMsg !== null && <Banner variant="info">{savedMsg}</Banner>}
|
|
363
|
+
</Form>
|
|
348
364
|
);
|
|
349
365
|
}
|
|
350
366
|
|
|
@@ -353,12 +369,18 @@ function TextContentEditor({
|
|
|
353
369
|
// Label/Icon/Access des Knotens (managed-pages-Konvention) — das Feature
|
|
354
370
|
// liefert nur die Kinder + den Editor. Ohne navId: nur der Resolver, kein
|
|
355
371
|
// Sidebar-Knoten (server-only-Consumer wie money-horse leaken nichts).
|
|
356
|
-
|
|
372
|
+
// `tenantId` (SystemAdmin-only): welchen Tenant der Content-Tree + Editor
|
|
373
|
+
// bedienen — SYSTEM_TENANT_ID für Apps, die Marketing/Legal dort seeden.
|
|
374
|
+
// Weglassen → Session-Tenant (Default; kein Cross-Tenant-Zugriff).
|
|
375
|
+
export function textContentClient(opts?: {
|
|
376
|
+
readonly navId?: string;
|
|
377
|
+
readonly tenantId?: string;
|
|
378
|
+
}): ClientFeatureDefinition {
|
|
357
379
|
const navId = opts?.navId;
|
|
358
380
|
return {
|
|
359
381
|
name: "text-content",
|
|
360
382
|
...(navId !== undefined && {
|
|
361
|
-
navProviders: { [navId]:
|
|
383
|
+
navProviders: { [navId]: makeTreeProvider(opts?.tenantId) },
|
|
362
384
|
// SSE-Refresh: jedes text-block-Event re-fired den Provider → Tree live.
|
|
363
385
|
navEntities: { [navId]: ["text-block"] },
|
|
364
386
|
}),
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
// 3. Idempotency: tenant-update fired keinen weiteren row
|
|
20
20
|
|
|
21
21
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
22
|
-
import { composeFeatures } from "@cosmicdrift/kumiko-dev-server/compose-features";
|
|
23
22
|
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
24
23
|
import { buildEntityTable } from "@cosmicdrift/kumiko-framework/db";
|
|
25
24
|
import {
|
|
@@ -28,6 +27,7 @@ import {
|
|
|
28
27
|
type TestStack,
|
|
29
28
|
unsafePushTables,
|
|
30
29
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
30
|
+
import { composeFeatures } from "@cosmicdrift/kumiko-server-runtime/compose-features";
|
|
31
31
|
import { configValuesTable } from "../../config";
|
|
32
32
|
import { TenantHandlers, tenantMembershipsTable, tenantTable } from "../../tenant";
|
|
33
33
|
import { userTable } from "../../user";
|
|
@@ -20,7 +20,6 @@ import { tenantSecretsTable } from "@cosmicdrift/kumiko-bundled-features/secrets
|
|
|
20
20
|
import { tenantMembershipsTable, tenantTable } from "@cosmicdrift/kumiko-bundled-features/tenant";
|
|
21
21
|
import { seedTenant } from "@cosmicdrift/kumiko-bundled-features/tenant/seeding";
|
|
22
22
|
import { userTable } from "@cosmicdrift/kumiko-bundled-features/user";
|
|
23
|
-
import { composeFeatures } from "@cosmicdrift/kumiko-dev-server/compose-features";
|
|
24
23
|
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
25
24
|
import { buildEntityTable } from "@cosmicdrift/kumiko-framework/db";
|
|
26
25
|
import {
|
|
@@ -36,6 +35,7 @@ import {
|
|
|
36
35
|
type TestStack,
|
|
37
36
|
unsafePushTables,
|
|
38
37
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
38
|
+
import { composeFeatures } from "@cosmicdrift/kumiko-server-runtime/compose-features";
|
|
39
39
|
import type { TierMap } from "../compose-app";
|
|
40
40
|
import { tierAssignmentEntity } from "../entity";
|
|
41
41
|
import { createTierEngineFeature } from "../feature";
|