@cosmicdrift/kumiko-framework 0.363.0 → 0.365.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/AGENTS.md +338 -0
- package/agents/examples/access-control.md +355 -0
- package/agents/examples/anonymous-access-multitenant.md +446 -0
- package/agents/examples/anonymous-access.md +288 -0
- package/agents/examples/apex-landing.md +528 -0
- package/agents/examples/apex-surface-auth.md +355 -0
- package/agents/examples/api-worker-split.md +434 -0
- package/agents/examples/auth-claims.md +351 -0
- package/agents/examples/auth-foundation-providers.md +155 -0
- package/agents/examples/auth-mfa.md +319 -0
- package/agents/examples/basic-entity.md +420 -0
- package/agents/examples/boot-check.md +122 -0
- package/agents/examples/chat-channels.md +254 -0
- package/agents/examples/compliance-profiles-demo.md +307 -0
- package/agents/examples/cross-feature-events.md +317 -0
- package/agents/examples/crypto-shredding-hr.md +314 -0
- package/agents/examples/currencies-global.md +609 -0
- package/agents/examples/currencies-per-tenant.md +453 -0
- package/agents/examples/custom-fields-basic.md +315 -0
- package/agents/examples/custom-handlers.md +444 -0
- package/agents/examples/dashboard-stats.md +229 -0
- package/agents/examples/delivery-notifications.md +432 -0
- package/agents/examples/designer-demo.md +86 -0
- package/agents/examples/e2e-generator.md +211 -0
- package/agents/examples/embedded-entity-form.md +598 -0
- package/agents/examples/embedded.md +649 -0
- package/agents/examples/encrypted-tenant-config.md +414 -0
- package/agents/examples/error-contract.md +535 -0
- package/agents/examples/event-sourcing.md +1287 -0
- package/agents/examples/feature-toggles.md +274 -0
- package/agents/examples/field-access.md +271 -0
- package/agents/examples/files-post-processing.md +433 -0
- package/agents/examples/i18n.md +166 -0
- package/agents/examples/idempotency.md +179 -0
- package/agents/examples/inbound-mail-triage.md +315 -0
- package/agents/examples/lane-routing.md +246 -0
- package/agents/examples/ledger-banking.md +215 -0
- package/agents/examples/ledger-invoicing.md +236 -0
- package/agents/examples/legal-pages.md +393 -0
- package/agents/examples/lifecycle-hooks.md +368 -0
- package/agents/examples/managed-config.md +428 -0
- package/agents/examples/managed-pages.md +353 -0
- package/agents/examples/notes-history-basic.md +346 -0
- package/agents/examples/ownership.md +404 -0
- package/agents/examples/pdf-audit-export.md +306 -0
- package/agents/examples/pipeline-basics.md +792 -0
- package/agents/examples/public-share-token.md +351 -0
- package/agents/examples/rate-limiting.md +211 -0
- package/agents/examples/realtime-sse.md +187 -0
- package/agents/examples/record-detail-layout.md +297 -0
- package/agents/examples/reference-data.md +151 -0
- package/agents/examples/relations.md +280 -0
- package/agents/examples/screens-nav.md +353 -0
- package/agents/examples/search.md +233 -0
- package/agents/examples/secrets-demo.md +417 -0
- package/agents/examples/security-baseline.md +187 -0
- package/agents/examples/seed-migration.md +99 -0
- package/agents/examples/session-revocation.md +250 -0
- package/agents/examples/state-machine.md +920 -0
- package/agents/examples/tags-basic.md +401 -0
- package/agents/examples/tenant-isolation.md +173 -0
- package/agents/examples/tenant-settings.md +275 -0
- package/agents/examples/tier-admin.md +347 -0
- package/agents/examples/timezones.md +366 -0
- package/agents/examples/user-data-rights.md +418 -0
- package/agents/examples/user-profile.md +226 -0
- package/agents/examples/webhook-step.md +597 -0
- package/agents/examples/wizard-form.md +379 -0
- package/agents/examples/workflow-engine.md +1731 -0
- package/agents/examples/writeform-section.md +300 -0
- package/agents/examples.md +75 -0
- package/agents/features.md +768 -0
- package/agents/widgets.md +181 -0
- package/dist/api/auth-middleware.d.ts +2 -0
- package/dist/api/auth-middleware.js +7 -0
- package/dist/api/auth-routes.d.ts +0 -1
- package/dist/api/auth-routes.js +49 -46
- package/dist/api/extra-route.d.ts +3 -0
- package/dist/api/index.d.ts +2 -2
- package/dist/api/index.js +1 -1
- package/dist/api/routes.js +46 -17
- package/dist/api/server.d.ts +4 -3
- package/dist/api/server.js +39 -10
- package/dist/api/session-cookies.d.ts +1 -0
- package/dist/api/session-cookies.js +20 -5
- package/dist/api/sse-route.js +16 -1
- package/dist/bun-db/connection.js +3 -16
- package/dist/bun-db/index.d.ts +1 -1
- package/dist/bun-db/index.js +1 -1
- package/dist/bun-db/query.d.ts +15 -3
- package/dist/bun-db/query.js +76 -24
- package/dist/crypto/owner-transition-rekey.d.ts +1 -0
- package/dist/crypto/owner-transition-rekey.js +7 -3
- package/dist/crypto/pii-field-encryption.d.ts +1 -0
- package/dist/crypto/pii-field-encryption.js +9 -2
- package/dist/crypto/subject-resolver.d.ts +1 -0
- package/dist/crypto/subject-resolver.js +7 -0
- package/dist/db/api.d.ts +4 -3
- package/dist/db/apply-entity-event.js +26 -2
- package/dist/db/blind-index-cleanup.js +2 -1
- package/dist/db/bun-provider.js +9 -18
- package/dist/db/collect-table-metas.js +11 -7
- package/dist/db/dialect.d.ts +1 -1
- package/dist/db/dialect.js +2 -0
- package/dist/db/eagerload.js +4 -3
- package/dist/db/event-store-executor-context.d.ts +6 -6
- package/dist/db/event-store-executor-context.js +25 -22
- package/dist/db/event-store-executor-read.js +31 -16
- package/dist/db/event-store-executor-write.d.ts +1 -1
- package/dist/db/event-store-executor-write.js +84 -63
- package/dist/db/index.d.ts +2 -1
- package/dist/db/index.js +2 -1
- package/dist/db/lock-guard.d.ts +4 -0
- package/dist/db/lock-guard.js +14 -0
- package/dist/db/migrate-generator.js +1 -1
- package/dist/db/migrate-runner.d.ts +1 -0
- package/dist/db/migrate-runner.js +64 -38
- package/dist/db/queries/backfill-pii.js +63 -25
- package/dist/db/queries/event-consumer.d.ts +6 -6
- package/dist/db/queries/event-consumer.js +2 -1
- package/dist/db/queries/event-store-transfer.js +15 -4
- package/dist/db/queries/event-store.d.ts +1 -1
- package/dist/db/queries/event-store.js +11 -10
- package/dist/db/queries/pii-backfill-state.js +3 -2
- package/dist/db/queries/postgres-array-literal.d.ts +2 -0
- package/dist/db/queries/postgres-array-literal.js +6 -0
- package/dist/db/queries/projection-rebuild.d.ts +1 -1
- package/dist/db/queries/projection-rebuild.js +3 -2
- package/dist/db/queries/shadow-swap.d.ts +1 -1
- package/dist/db/queries/shadow-swap.js +12 -8
- package/dist/db/query.d.ts +1 -1
- package/dist/db/query.js +1 -1
- package/dist/db/retired-framework-tables.js +22 -4
- package/dist/db/table-builder.js +6 -2
- package/dist/db/tenant-db-runner.d.ts +4 -0
- package/dist/db/tenant-db-runner.js +17 -0
- package/dist/db/tenant-db.d.ts +4 -0
- package/dist/db/tenant-db.js +50 -5
- package/dist/derivatives/delete-stored-file.d.ts +4 -0
- package/dist/derivatives/delete-stored-file.js +9 -0
- package/dist/derivatives/derivatives-context.js +1 -1
- package/dist/derivatives/index.d.ts +1 -1
- package/dist/derivatives/index.js +1 -1
- package/dist/engine/boot-validator/access-declarations.js +2 -2
- package/dist/engine/boot-validator/api-ext.js +3 -3
- package/dist/engine/boot-validator/boot-check.js +1 -1
- package/dist/engine/boot-validator/config-deps.js +13 -13
- package/dist/engine/boot-validator/detail-screens.js +1 -1
- package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
- package/dist/engine/boot-validator/entity-handler.js +87 -35
- package/dist/engine/boot-validator/entity-list-screens.js +2 -2
- package/dist/engine/boot-validator/handler-field-references.js +19 -9
- package/dist/engine/boot-validator/i18n-keys.js +2 -2
- package/dist/engine/boot-validator/nav.js +1 -1
- package/dist/engine/boot-validator/pii-retention.js +8 -8
- package/dist/engine/boot-validator/projection-list-screens.js +5 -5
- package/dist/engine/boot-validator/query-output-columns.js +7 -7
- package/dist/engine/boot-validator/query-refs.js +14 -2
- package/dist/engine/boot-validator/screens.d.ts +2 -1
- package/dist/engine/boot-validator/screens.js +153 -95
- package/dist/engine/boot-validator/url-prefill-fields.js +54 -35
- package/dist/engine/dedupe-features.js +31 -4
- package/dist/engine/define-handler.d.ts +2 -2
- package/dist/engine/define-handler.js +8 -1
- package/dist/engine/entity-handlers.js +14 -7
- package/dist/engine/feature-ast/extractors/handlers.js +2 -0
- package/dist/engine/feature-ast/pattern-change-schema.d.ts +2 -0
- package/dist/engine/feature-ast/pattern-change-schema.js +1 -0
- package/dist/engine/feature-ast/render.js +10 -2
- package/dist/engine/feature-entity-handlers.d.ts +2 -2
- package/dist/engine/feature-entity-handlers.js +12 -3
- package/dist/engine/handler-field-references.d.ts +21 -2
- package/dist/engine/handler-field-references.js +55 -17
- package/dist/engine/index.d.ts +3 -3
- package/dist/engine/index.js +1 -1
- package/dist/engine/schema-builder.js +2 -1
- package/dist/engine/screen-helpers.d.ts +1 -0
- package/dist/engine/screen-helpers.js +2 -2
- package/dist/engine/soft-delete-cleanup.js +10 -3
- package/dist/engine/steps/_read-source.js +5 -2
- package/dist/engine/steps/retry.js +2 -1
- package/dist/engine/steps/unsafe-projection-delete.js +2 -2
- package/dist/engine/steps/unsafe-projection-upsert.js +2 -2
- package/dist/engine/template-variables.d.ts +3 -0
- package/dist/engine/template-variables.js +3 -0
- package/dist/engine/tier-resolver-extension.d.ts +1 -0
- package/dist/engine/tier-resolver-extension.js +6 -0
- package/dist/engine/types/index.d.ts +1 -1
- package/dist/entrypoint/index.d.ts +2 -2
- package/dist/env/_zod-introspect.d.ts +1 -0
- package/dist/errors/classes.js +1 -0
- package/dist/errors/docs-url.js +6 -6
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/errors/kumiko-error.d.ts +2 -0
- package/dist/errors/kumiko-error.js +2 -0
- package/dist/errors/subject-erased.d.ts +1 -0
- package/dist/errors/subject-erased.js +6 -0
- package/dist/errors/write-error-info.js +2 -0
- package/dist/event-store/admin-api.js +3 -2
- package/dist/event-store/errors.d.ts +9 -0
- package/dist/event-store/errors.js +20 -0
- package/dist/event-store/event-store.d.ts +6 -0
- package/dist/event-store/event-store.js +28 -6
- package/dist/event-store/index.d.ts +1 -1
- package/dist/event-store/index.js +1 -1
- package/dist/event-store/tenant-db-append.js +2 -2
- package/dist/files/assert-file-refs-belong-to-tenant.d.ts +4 -5
- package/dist/files/assert-file-refs-belong-to-tenant.js +7 -15
- package/dist/files/bind-pending-file-refs.d.ts +2 -0
- package/dist/files/bind-pending-file-refs.js +3 -3
- package/dist/files/bind-referenced-files.write.d.ts +1 -1
- package/dist/files/bind-referenced-files.write.js +22 -13
- package/dist/files/create-file-ref-from-bytes.d.ts +6 -3
- package/dist/files/create-file-ref-from-bytes.js +67 -4
- package/dist/files/create-file-ref.d.ts +24 -1
- package/dist/files/create-file-ref.js +67 -25
- package/dist/files/dispose-removed-file-refs.js +21 -6
- package/dist/files/feature.d.ts +1 -0
- package/dist/files/feature.js +2 -1
- package/dist/files/file-handle.d.ts +1 -1
- package/dist/files/file-handle.js +12 -0
- package/dist/files/file-routes.js +10 -6
- package/dist/files/index.d.ts +3 -3
- package/dist/files/index.js +2 -2
- package/dist/files/load-save-file-refs.d.ts +10 -0
- package/dist/files/load-save-file-refs.js +47 -0
- package/dist/files/purge-expired-file-ref-bytes.d.ts +4 -1
- package/dist/files/purge-expired-file-ref-bytes.js +46 -21
- package/dist/files/resolve-content-type.js +40 -4
- package/dist/files/storage-tracking.js +5 -4
- package/dist/files/types.js +5 -8
- package/dist/files/zip-entry-names.d.ts +1 -0
- package/dist/files/zip-entry-names.js +46 -0
- package/dist/jobs/index.d.ts +1 -1
- package/dist/jobs/job-runner.d.ts +5 -1
- package/dist/jobs/job-runner.js +124 -37
- package/dist/observability/escape-hatch-report.d.ts +2 -1
- package/dist/observability/escape-hatch-report.js +48 -7
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.js +1 -1
- package/dist/observability/metrics-handle.d.ts +1 -0
- package/dist/observability/metrics-handle.js +7 -0
- package/dist/observability/standard-metrics.js +4 -4
- package/dist/pipeline/cascade-handler.js +5 -2
- package/dist/pipeline/dispatch-batch.js +79 -31
- package/dist/pipeline/dispatch-query.d.ts +2 -1
- package/dist/pipeline/dispatch-query.js +6 -6
- package/dist/pipeline/dispatch-shared.d.ts +7 -2
- package/dist/pipeline/dispatch-shared.js +136 -48
- package/dist/pipeline/dispatch-stream.js +10 -4
- package/dist/pipeline/dispatch-write.js +34 -22
- package/dist/pipeline/dispatcher.js +3 -1
- package/dist/pipeline/event-dispatcher.d.ts +2 -2
- package/dist/pipeline/event-dispatcher.js +11 -9
- package/dist/pipeline/event-retention.d.ts +1 -0
- package/dist/pipeline/event-retention.js +7 -1
- package/dist/pipeline/index.d.ts +1 -0
- package/dist/pipeline/index.js +1 -0
- package/dist/pipeline/notify-scope.js +3 -1
- package/dist/pipeline/pre-transaction-reservations.d.ts +3 -1
- package/dist/pipeline/pre-transaction-reservations.js +29 -9
- package/dist/rate-limit/bucket.d.ts +3 -3
- package/dist/rate-limit/bucket.js +14 -4
- package/dist/redis/cache-sync-bus.js +9 -3
- package/dist/schema-cli.js +12 -1
- package/dist/search/meilisearch-adapter.js +36 -9
- package/dist/stack/db.d.ts +2 -2
- package/dist/stack/test-stack.js +2 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +1 -0
- package/dist/testing/multipart-helper.d.ts +6 -0
- package/dist/testing/multipart-helper.js +1 -1
- package/dist/testing/zip-fixture.d.ts +1 -0
- package/dist/testing/zip-fixture.js +33 -0
- package/dist/ui-types/card-column-roles.js +4 -2
- package/dist/ui-types/index.d.ts +1 -1
- package/dist/ui-types/index.js +1 -1
- package/dist/upgrade-cli.js +46 -13
- package/package.json +7 -5
- package/src/changes.json +512 -8
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
<!-- Generated by `bun run gen:agent-docs` from samples/recipes/legal-pages. Do not edit. -->
|
|
2
|
+
|
|
3
|
+
# legal-pages
|
|
4
|
+
|
|
5
|
+
Auto-rendered Impressum + Datenschutzerklärung pages — template-resolver + legal-pages bundled features wired up with anonymousAccess for DACH-compliance.
|
|
6
|
+
|
|
7
|
+
## README
|
|
8
|
+
|
|
9
|
+
# Recipe: legal-pages + template-resolver
|
|
10
|
+
|
|
11
|
+
DACH compliance stack: two opt-in bundled features wired up to
|
|
12
|
+
auto-rendered Imprint + Privacy-Policy pages, with Markdown authoring
|
|
13
|
+
and a boot check for required content.
|
|
14
|
+
|
|
15
|
+
**What the recipe demonstrates:**
|
|
16
|
+
- Activate both features in `runProdApp({ features: [...] })`
|
|
17
|
+
- Required wirings: `anonymousAccess` + `extraContext.templateResolver`
|
|
18
|
+
- Initial seed of the DE required blocks (Imprint + Privacy) for SYSTEM_TENANT
|
|
19
|
+
- 5 integration tests proving end-to-end behavior
|
|
20
|
+
|
|
21
|
+
## Layout
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
samples/recipes/legal-pages/
|
|
25
|
+
├── package.json # workspace deps
|
|
26
|
+
├── README.md # this file
|
|
27
|
+
└── src/
|
|
28
|
+
├── feature.ts # the two features re-exported for tests
|
|
29
|
+
└── __tests__/
|
|
30
|
+
└── feature.integration.test.ts # 5 tests: routes + boot check
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`feature.ts` is intentionally thin (re-export). In a real app this
|
|
34
|
+
lives in `bin/main.ts` (see "Integration into a real app" below).
|
|
35
|
+
|
|
36
|
+
## Run tests
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# From the repo root:
|
|
40
|
+
bun kumiko test all samples/recipes/legal-pages/
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Expected output:
|
|
44
|
+
```
|
|
45
|
+
Test Files 1 passed (1)
|
|
46
|
+
Tests 5 passed (5)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
When the tests are green:
|
|
50
|
+
- template-resolver + legal-pages are compatible
|
|
51
|
+
- Table schema is clean via `unsafeCreateEntityTable(stack.db, textBlockEntity)`
|
|
52
|
+
- Routes are reachable via `stack.app.request("/legal/impressum")`
|
|
53
|
+
- Markdown rendering produces valid HTML
|
|
54
|
+
- Boot check correctly detects missing blocks
|
|
55
|
+
|
|
56
|
+
## Integration into a real app
|
|
57
|
+
|
|
58
|
+
Step-by-step for an existing Kumiko app (e.g.
|
|
59
|
+
`samples/showcases/your-app/`):
|
|
60
|
+
|
|
61
|
+
### 1. Activate features in `runProdApp`
|
|
62
|
+
|
|
63
|
+
```typescript illustration
|
|
64
|
+
// bin/main.ts
|
|
65
|
+
import { runProdApp } from "@cosmicdrift/kumiko-server-runtime";
|
|
66
|
+
import {
|
|
67
|
+
createTemplateResolverApi,
|
|
68
|
+
createTemplateResolverFeature,
|
|
69
|
+
} from "@cosmicdrift/kumiko-bundled-features/template-resolver";
|
|
70
|
+
import { createLegalPagesFeature } from "@cosmicdrift/kumiko-bundled-features/legal-pages";
|
|
71
|
+
import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
72
|
+
|
|
73
|
+
await runProdApp({
|
|
74
|
+
features: [
|
|
75
|
+
createTemplateResolverFeature(),
|
|
76
|
+
createLegalPagesFeature(),
|
|
77
|
+
/* ... your other features */
|
|
78
|
+
],
|
|
79
|
+
// Required (1): routes run anonymous, need tenant resolution
|
|
80
|
+
anonymousAccess: { defaultTenantId: SYSTEM_TENANT_ID },
|
|
81
|
+
// Required (2): boot check + internal lookup use ctx.templateResolver
|
|
82
|
+
extraContext: ({ db }) => ({
|
|
83
|
+
templateResolver: createTemplateResolverApi(db),
|
|
84
|
+
}),
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
→ For host-based multi-tenant apps (like publicstatus.eu),
|
|
89
|
+
`SYSTEM_TENANT_ID` always stays correct for legal-pages — the routes
|
|
90
|
+
internally set `X-Tenant: SYSTEM_TENANT_ID` and override any
|
|
91
|
+
host-based `tenantResolver`. This is the "1 app = X tenants = 1
|
|
92
|
+
imprint" decision.
|
|
93
|
+
|
|
94
|
+
### 2. Create the table
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# In the app workspace:
|
|
98
|
+
bunx kumiko-schema generate <name> # detects text-block entity → SQL migration
|
|
99
|
+
bunx kumiko-schema apply # one-time (pre-deploy step in prod)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 3. Initial seed of the required blocks
|
|
103
|
+
|
|
104
|
+
A one-shot setup routine that runs on first boot or via the CLI:
|
|
105
|
+
|
|
106
|
+
```typescript illustration
|
|
107
|
+
// bin/seed-legal.ts
|
|
108
|
+
import { seedTextBlock } from "@cosmicdrift/kumiko-bundled-features/template-resolver/seeding";
|
|
109
|
+
import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
110
|
+
import { createDb } from "@cosmicdrift/kumiko-framework/db";
|
|
111
|
+
|
|
112
|
+
const db = createDb(process.env.DATABASE_URL!);
|
|
113
|
+
|
|
114
|
+
await seedTextBlock(db, {
|
|
115
|
+
tenantId: SYSTEM_TENANT_ID,
|
|
116
|
+
slug: "imprint",
|
|
117
|
+
locale: "de",
|
|
118
|
+
title: "Impressum",
|
|
119
|
+
content: `## Angaben gemäß § 5 TMG
|
|
120
|
+
|
|
121
|
+
**[Dein Name / GmbH]**
|
|
122
|
+
|
|
123
|
+
[Strasse + Nr]
|
|
124
|
+
[PLZ Ort]
|
|
125
|
+
Deutschland
|
|
126
|
+
|
|
127
|
+
## Kontakt
|
|
128
|
+
|
|
129
|
+
E-Mail: [hello@example.com](mailto:hello@example.com)
|
|
130
|
+
|
|
131
|
+
## Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV
|
|
132
|
+
|
|
133
|
+
[Dein Name, Adresse]`,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
await seedTextBlock(db, {
|
|
137
|
+
tenantId: SYSTEM_TENANT_ID,
|
|
138
|
+
slug: "privacy",
|
|
139
|
+
locale: "de",
|
|
140
|
+
title: "Datenschutzerklärung",
|
|
141
|
+
content: `## 1. Verantwortlicher
|
|
142
|
+
|
|
143
|
+
[Dein Name + Anschrift]
|
|
144
|
+
|
|
145
|
+
## 2. Erhobene Daten
|
|
146
|
+
|
|
147
|
+
[...]`,
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
→ Templates for full legally-sound texts: e-recht24.de
|
|
152
|
+
or datenschutz-generator.de by Dr. Schwenke.
|
|
153
|
+
|
|
154
|
+
### 4. Visit the pages
|
|
155
|
+
|
|
156
|
+
After the seed these URLs are immediately live:
|
|
157
|
+
|
|
158
|
+
| URL | Content |
|
|
159
|
+
|---|---|
|
|
160
|
+
| `https://your-app.example/legal/impressum` | Impressum (DE) |
|
|
161
|
+
| `https://your-app.example/legal/datenschutz` | Datenschutzerklärung (DE) |
|
|
162
|
+
| `https://your-app.example/legal/imprint` | Imprint (EN, if seeded) |
|
|
163
|
+
| `https://your-app.example/legal/privacy` | Privacy Policy (EN, if seeded) |
|
|
164
|
+
|
|
165
|
+
→ Footer links are set per app (legal-pages does not ship a footer
|
|
166
|
+
component — deliberately, every app has its own layout).
|
|
167
|
+
|
|
168
|
+
### 5. Editing texts later (TenantAdmin maintenance)
|
|
169
|
+
|
|
170
|
+
Through the standard write API:
|
|
171
|
+
|
|
172
|
+
```typescript illustration
|
|
173
|
+
await fetch("/api/write", {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${jwt}` },
|
|
176
|
+
body: JSON.stringify({
|
|
177
|
+
type: "template-resolver:write:set",
|
|
178
|
+
payload: { slug: "imprint", locale: "de", title: "Impressum", content: "..." },
|
|
179
|
+
}),
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
ACL: `["TenantAdmin", "SystemAdmin"]`. Cache header `public, max-age=300`
|
|
184
|
+
— visitors see updates within 5 minutes at most.
|
|
185
|
+
|
|
186
|
+
## Boot-check behavior
|
|
187
|
+
|
|
188
|
+
When the required blocks (`imprint/de` + `privacy/de`) are missing
|
|
189
|
+
from SYSTEM_TENANT:
|
|
190
|
+
|
|
191
|
+
| Mode | Behavior |
|
|
192
|
+
|---|---|
|
|
193
|
+
| `NODE_ENV=production` | App boot throws an error with a slug list — container exits |
|
|
194
|
+
| otherwise (dev/test) | `console.warn` with a slug list, app starts anyway |
|
|
195
|
+
|
|
196
|
+
→ Safety net: no production deploy without populated legal pages.
|
|
197
|
+
|
|
198
|
+
## Troubleshooting
|
|
199
|
+
|
|
200
|
+
| Symptom | Cause | Fix |
|
|
201
|
+
|---|---|---|
|
|
202
|
+
| Route returns `503 legal page unavailable` | `anonymousAccess` not configured in `runProdApp` | Set `anonymousAccess: { defaultTenantId: SYSTEM_TENANT_ID }` |
|
|
203
|
+
| Boot check throws `ctx.templateResolver missing` | `extraContext.templateResolver` not wired | Set `extraContext: ({ db }) => ({ templateResolver: createTemplateResolverApi(db) })` |
|
|
204
|
+
| Route returns `404 not configured` | Required block doesn't exist or has `content=null` | `seedTextBlock` with a content string |
|
|
205
|
+
| Multi-tenant app: tenant subdomain shows an empty page | (Bug regression?) Routes should ALWAYS show SYSTEM_TENANT texts | The `legal-pages.integration.test.ts` test "SYSTEM_TENANT routing" covers this — should be green |
|
|
206
|
+
| `<script>` tags in a Markdown body land 1:1 in the HTML | Deliberately accepted right now — see [legal-pages/README.md XSS section](../../../packages/bundled-features/src/legal-pages/README.md#xss--currently-not-secured-by-design) | DOMPurify is a Phase 2 once a multi-author setup arrives |
|
|
207
|
+
|
|
208
|
+
## Cross-refs
|
|
209
|
+
|
|
210
|
+
- [packages/bundled-features/src/template-resolver/README.md](../../../packages/bundled-features/src/template-resolver/README.md) — content store
|
|
211
|
+
- [packages/bundled-features/src/legal-pages/README.md](../../../packages/bundled-features/src/legal-pages/README.md) — DACH compliance wrapper
|
|
212
|
+
|
|
213
|
+
## Source files
|
|
214
|
+
|
|
215
|
+
### src/__tests__/feature.integration.test.ts
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
219
|
+
import {
|
|
220
|
+
createTemplateResolverApi,
|
|
221
|
+
templateResourceEntity,
|
|
222
|
+
} from "@cosmicdrift/kumiko-bundled-features/template-resolver";
|
|
223
|
+
import { seedTextBlock } from "@cosmicdrift/kumiko-bundled-features/template-resolver/seeding";
|
|
224
|
+
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
225
|
+
import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
226
|
+
import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
227
|
+
import {
|
|
228
|
+
setupTestStack,
|
|
229
|
+
type TestStack,
|
|
230
|
+
unsafeCreateEntityTable,
|
|
231
|
+
} from "@cosmicdrift/kumiko-framework/stack";
|
|
232
|
+
import { LEGAL_ROUTES, legalPagesFeature, templateResolverFeature } from "../feature";
|
|
233
|
+
|
|
234
|
+
let stack: TestStack;
|
|
235
|
+
let db: DbConnection;
|
|
236
|
+
|
|
237
|
+
beforeAll(async () => {
|
|
238
|
+
stack = await setupTestStack({
|
|
239
|
+
features: [templateResolverFeature, legalPagesFeature],
|
|
240
|
+
anonymousAccess: { defaultTenantId: SYSTEM_TENANT_ID },
|
|
241
|
+
extraContext: ({ db }) => ({
|
|
242
|
+
templateResolver: createTemplateResolverApi(db),
|
|
243
|
+
}),
|
|
244
|
+
});
|
|
245
|
+
db = stack.db;
|
|
246
|
+
await unsafeCreateEntityTable(db, templateResourceEntity);
|
|
247
|
+
await createEventsTable(db);
|
|
248
|
+
|
|
249
|
+
// Seed Pflicht-Blocks für SYSTEM_TENANT (DE Impressum + DSE)
|
|
250
|
+
await seedTextBlock(db, {
|
|
251
|
+
tenantId: SYSTEM_TENANT_ID,
|
|
252
|
+
slug: "imprint",
|
|
253
|
+
locale: "de",
|
|
254
|
+
title: "Impressum",
|
|
255
|
+
content: [
|
|
256
|
+
"## Angaben gemäß § 5 TMG",
|
|
257
|
+
"",
|
|
258
|
+
"**Alex Morgan**",
|
|
259
|
+
"Musterstraße 1",
|
|
260
|
+
"12345 Musterstadt",
|
|
261
|
+
"Deutschland",
|
|
262
|
+
"",
|
|
263
|
+
"## Kontakt",
|
|
264
|
+
"",
|
|
265
|
+
"E-Mail: [hello@example.com](mailto:hello@example.com)",
|
|
266
|
+
].join("\n"),
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
await seedTextBlock(db, {
|
|
270
|
+
tenantId: SYSTEM_TENANT_ID,
|
|
271
|
+
slug: "privacy",
|
|
272
|
+
locale: "de",
|
|
273
|
+
title: "Datenschutzerklärung",
|
|
274
|
+
content: [
|
|
275
|
+
"## 1. Verantwortlicher",
|
|
276
|
+
"",
|
|
277
|
+
"Alex Morgan, Musterstraße 1, 12345 Musterstadt.",
|
|
278
|
+
"",
|
|
279
|
+
"## 2. Erhobene Daten",
|
|
280
|
+
"",
|
|
281
|
+
"Diese App setzt **keine Tracking-Cookies** und kein Drittanbieter-Tracking ein.",
|
|
282
|
+
"",
|
|
283
|
+
"## 3. Deine Rechte (Art. 15-22 DSGVO)",
|
|
284
|
+
"",
|
|
285
|
+
"Auskunft, Berichtigung, Löschung, Einschränkung, Datenübertragbarkeit, Widerspruch.",
|
|
286
|
+
].join("\n"),
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
afterAll(async () => {
|
|
291
|
+
await stack.cleanup();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe("legal-pages sample", () => {
|
|
295
|
+
test("alle LEGAL_ROUTES sind über stack.app erreichbar", async () => {
|
|
296
|
+
// Smoke-Test: jede Route gibt entweder 200 (gerendert) oder 404
|
|
297
|
+
// (Block fehlt). Nie 5xx — die Server-Pipeline läuft sauber durch.
|
|
298
|
+
for (const route of LEGAL_ROUTES) {
|
|
299
|
+
const res = await stack.app.request(route.path);
|
|
300
|
+
expect([200, 404]).toContain(res.status);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("DE-Pflicht-Pages rendern HTML", async () => {
|
|
305
|
+
const imprint = await stack.app.request("/legal/impressum");
|
|
306
|
+
expect(imprint.status).toBe(200);
|
|
307
|
+
const imprintBody = await imprint.text();
|
|
308
|
+
expect(imprintBody).toContain("<!doctype html>");
|
|
309
|
+
expect(imprintBody).toContain("Alex Morgan");
|
|
310
|
+
expect(imprintBody).toContain("Musterstadt");
|
|
311
|
+
|
|
312
|
+
const privacy = await stack.app.request("/legal/datenschutz");
|
|
313
|
+
expect(privacy.status).toBe(200);
|
|
314
|
+
const privacyBody = await privacy.text();
|
|
315
|
+
expect(privacyBody).toContain("Tracking-Cookies");
|
|
316
|
+
expect(privacyBody).toContain("Datenschutzerklärung");
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test("EN-Variante 404 (nicht geseedet) zeigt hilfreiche Meldung", async () => {
|
|
320
|
+
const res = await stack.app.request("/legal/imprint");
|
|
321
|
+
expect(res.status).toBe(404);
|
|
322
|
+
const body = await res.text();
|
|
323
|
+
expect(body).toContain("Tenant-Admin");
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test("Markdown-Headings werden zu HTML-h2-Tags", async () => {
|
|
327
|
+
const res = await stack.app.request("/legal/impressum");
|
|
328
|
+
const body = await res.text();
|
|
329
|
+
expect(body).toContain("<h2>Angaben gemäß § 5 TMG</h2>");
|
|
330
|
+
expect(body).toContain("<strong>Alex Morgan</strong>");
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test("Cache-Header zeigt 60s public-cache", async () => {
|
|
334
|
+
const res = await stack.app.request("/legal/impressum");
|
|
335
|
+
expect(res.headers.get("cache-control")).toBe("public, max-age=60, must-revalidate");
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### src/feature.ts
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
// Legal-Pages Sample
|
|
344
|
+
//
|
|
345
|
+
// DACH-Apps (DE/AT/CH) sind verpflichtet ein Impressum (TMG/DDG §5) und
|
|
346
|
+
// eine Datenschutzerklärung (DSGVO Art. 13) öffentlich zugänglich zu
|
|
347
|
+
// haben. Das ist 1) für jede App identisch und 2) nervig manuell pro
|
|
348
|
+
// App neu zu basteln.
|
|
349
|
+
//
|
|
350
|
+
// Lösung: zwei opt-in bundled-features kombinieren:
|
|
351
|
+
//
|
|
352
|
+
// - `template-resolver` — der Content-Store (Entity `template-resource`,
|
|
353
|
+
// kind `text-block` mit slug+locale+title+content, scoped per Tenant).
|
|
354
|
+
// Auch nutzbar für FAQ, About, ToS, Marketing-Snippets — nicht
|
|
355
|
+
// legal-spezifisch.
|
|
356
|
+
//
|
|
357
|
+
// - `legal-pages` — opt-in-Wrapper darauf, der vier feste Public-
|
|
358
|
+
// Routes (`/legal/impressum`, `/legal/datenschutz`, `/legal/imprint`,
|
|
359
|
+
// `/legal/privacy`) registriert und Markdown→HTML rendered. Plus
|
|
360
|
+
// Boot-Check der in Production hart fehlt wenn die DE-Pflicht-Blocks
|
|
361
|
+
// fehlen.
|
|
362
|
+
//
|
|
363
|
+
// Tenant-Modell: 1 App = X Tenants = 1 Impressum. Alle Subdomains
|
|
364
|
+
// teilen sich die SYSTEM_TENANT_ID-Version. Wer pro-Tenant-Impressums
|
|
365
|
+
// braucht, muss sein eigenes Routing davorsetzen oder die by-slug-
|
|
366
|
+
// query mit tenant-specific tenantId nutzen.
|
|
367
|
+
//
|
|
368
|
+
// Voraussetzungen für Production:
|
|
369
|
+
// - `anonymousAccess` muss in runProdApp/runDevApp konfiguriert sein
|
|
370
|
+
// (defaultTenantId = SYSTEM_TENANT_ID), sonst antworten die
|
|
371
|
+
// legal-pages-Routes mit 503
|
|
372
|
+
// - `extraContext.templateResolver = createTemplateResolverApi(db)` muss
|
|
373
|
+
// gewired sein, sonst wirft der Boot-Check mit Wiring-Hinweis
|
|
374
|
+
// - Beim ersten Boot müssen die TextBlocks geseedet sein —
|
|
375
|
+
// template-resolver/seeding `seedTextBlock` oder via API
|
|
376
|
+
// `template-resolver:write:set` mit TenantAdmin-Token
|
|
377
|
+
|
|
378
|
+
import {
|
|
379
|
+
createLegalPagesFeature,
|
|
380
|
+
LEGAL_REQUIRED_BLOCKS,
|
|
381
|
+
LEGAL_ROUTES,
|
|
382
|
+
} from "@cosmicdrift/kumiko-bundled-features/legal-pages";
|
|
383
|
+
import { createTemplateResolverFeature } from "@cosmicdrift/kumiko-bundled-features/template-resolver";
|
|
384
|
+
|
|
385
|
+
// Beide Features aktivieren — template-resolver ist Foundation, legal-pages
|
|
386
|
+
// requires sie. r.requires("template-resolver") greift automatisch im
|
|
387
|
+
// legal-pages-Feature.
|
|
388
|
+
export const templateResolverFeature = createTemplateResolverFeature();
|
|
389
|
+
export const legalPagesFeature = createLegalPagesFeature();
|
|
390
|
+
|
|
391
|
+
// Re-exports für Tests + andere Demos
|
|
392
|
+
export { LEGAL_REQUIRED_BLOCKS, LEGAL_ROUTES };
|
|
393
|
+
```
|