@cosmicdrift/kumiko-samples 0.383.1 → 0.385.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/packages/bundled-features/package.json +6 -1
- package/packages/bundled-features/src/audit/changes.json +6 -0
- package/packages/bundled-features/src/audit/event-classification.ts +83 -0
- package/packages/bundled-features/src/audit/feature.ts +3 -2
- package/packages/bundled-features/src/audit/handlers/audit-events.ts +63 -5
- package/packages/bundled-features/src/audit/handlers/details-all-tenants.query.ts +6 -5
- package/packages/bundled-features/src/audit/handlers/details.query.ts +12 -6
- package/packages/bundled-features/src/audit/handlers/list-all-tenants.query.ts +20 -12
- package/packages/bundled-features/src/audit/handlers/list.query.ts +14 -3
- package/packages/bundled-features/src/audit/screens.ts +32 -4
- package/packages/bundled-features/src/audit/web/audit-event-cell.tsx +22 -0
- package/packages/bundled-features/src/audit/web/audit-event-labels.ts +30 -0
- package/packages/bundled-features/src/audit/web/client-plugin.tsx +10 -1
- package/packages/bundled-features/src/audit/web/constants.ts +11 -0
- package/packages/bundled-features/src/audit/web/i18n.ts +48 -1
- package/packages/bundled-features/src/auth-email-password/changes.json +12 -0
- package/packages/bundled-features/src/auth-email-password/feature.ts +3 -2
- package/packages/bundled-features/src/auth-email-password/handlers/login-options.query.ts +19 -0
- package/packages/bundled-features/src/auth-email-password/index.ts +7 -1
- package/packages/bundled-features/src/auth-email-password/password-policy.ts +196 -2087
- package/packages/bundled-features/src/auth-email-password/web/auth-form-primitives.tsx +9 -12
- package/packages/bundled-features/src/auth-email-password/{auth-paths.ts → web/auth-paths.ts} +7 -4
- package/packages/bundled-features/src/auth-email-password/web/client-plugin.ts +7 -0
- package/packages/bundled-features/src/auth-email-password/web/constants.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/web/default-auth-pages-gate.tsx +31 -0
- package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +68 -42
- package/packages/bundled-features/src/channel-in-app/handlers/unread-count.query.ts +1 -1
- package/packages/bundled-features/src/custom-fields/db/queries/projection.ts +1 -1
- package/packages/bundled-features/src/file-derivatives/feature.ts +1 -1
- package/packages/bundled-features/src/jobs/changes.json +6 -0
- package/packages/bundled-features/src/jobs/feature.ts +34 -127
- package/packages/bundled-features/src/jobs/handlers/consumer.write.ts +98 -0
- package/packages/bundled-features/src/jobs/handlers/consumers.query.ts +137 -0
- package/packages/bundled-features/src/jobs/screens.ts +262 -0
- package/packages/bundled-features/src/jobs/web/constants.ts +29 -0
- package/packages/bundled-features/src/jobs/web/i18n.ts +41 -0
- package/packages/bundled-features/src/ledger/web/index.ts +1 -1
- package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/changes.json +7 -0
- package/packages/bundled-features/src/personal-access-tokens/i18n.ts +4 -0
- package/packages/bundled-features/src/personal-access-tokens/index.ts +1 -0
- package/packages/bundled-features/src/secrets/i18n.ts +0 -3
- package/packages/bundled-features/src/sessions/changes.json +6 -0
- package/packages/bundled-features/src/sessions/constants.ts +3 -1
- package/packages/bundled-features/src/sessions/handlers/detail.query.ts +7 -3
- package/packages/bundled-features/src/sessions/handlers/list.query.ts +16 -10
- package/packages/bundled-features/src/sessions/handlers/mine.query.ts +15 -9
- package/packages/bundled-features/src/sessions/screens.ts +23 -8
- package/packages/bundled-features/src/sessions/web/client-plugin.tsx +17 -0
- package/packages/bundled-features/src/sessions/web/constants.ts +3 -0
- package/packages/bundled-features/src/sessions/web/default-translations.ts +8 -0
- package/packages/bundled-features/src/sessions/web/i18n.ts +5 -1
- package/packages/bundled-features/src/sessions/web/index.ts +1 -0
- package/packages/bundled-features/src/sessions/web/session-device-cell.tsx +19 -0
- package/packages/bundled-features/src/sessions/web/user-agent.ts +74 -0
- package/packages/bundled-features/src/shared/run-in-sub-transaction.ts +1 -1
- package/packages/bundled-features/src/template-resolver/changes.json +6 -0
- package/packages/bundled-features/src/template-resolver/feature.ts +2 -0
- package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +34 -1
- package/packages/bundled-features/src/template-resolver/handlers/tenant-override-counts.query.ts +53 -0
- package/packages/bundled-features/src/template-resolver/handlers/tenant-override-selection.ts +51 -0
- package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +7 -38
- package/packages/bundled-features/src/template-resolver/web/qualified-names.ts +1 -0
- package/packages/bundled-features/src/user-data-rights/changes.json +18 -0
- package/packages/bundled-features/src/user-data-rights/feature.ts +33 -33
- package/packages/bundled-features/src/user-data-rights/web/i18n.ts +4 -1
- package/samples/apps/showcase/src/features/items/schema/item.ts +8 -2
- package/samples/apps/ui-walkthrough/src/features/tasks/schema/task.ts +2 -2
- package/samples/apps/use-all-bundled/bin/main.ts +7 -1
- package/samples/apps/use-all-bundled/feature-manifest.json +19 -1
- package/samples/apps/use-all-bundled/kumiko/schema.ts +3 -1
- package/samples/apps/use-all-bundled/src/app/client.tsx +2 -0
- package/samples/apps/use-all-bundled/src/app/dev-app-options.ts +4 -2
- package/samples/apps/use-all-bundled/src/app/invoice-reports-feature.ts +30 -0
- package/samples/apps/use-all-bundled/src/app/pat-scope-labels-feature.ts +21 -0
- package/samples/apps/use-all-bundled/src/app/seed.ts +51 -0
- package/samples/apps/use-all-bundled/src/run-config.ts +5 -0
- package/samples/apps/workspaces/src/features/demo/schema/order.ts +2 -2
- package/samples/recipes/custom-fields-basic/src/feature.ts +13 -3
- package/samples/recipes/designer-demo/src/feature.ts +6 -2
- package/samples/recipes/i18n/README.md +15 -0
- package/samples/recipes/i18n/src/feature.ts +4 -0
|
@@ -399,7 +399,9 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
399
399
|
},
|
|
400
400
|
},
|
|
401
401
|
dormant: true,
|
|
402
|
-
description:
|
|
402
|
+
description: showDeletion
|
|
403
|
+
? "userDataRights.privacyCenter.subtitle"
|
|
404
|
+
: "userDataRights.privacyCenter.subtitleWithoutDeletion",
|
|
403
405
|
fieldLabels: {
|
|
404
406
|
status: i18nKey("userDataRights.privacyCenter.field.status"),
|
|
405
407
|
gracePeriodEnd: i18nKey("userDataRights.privacyCenter.field.gracePeriodEnd"),
|
|
@@ -425,6 +427,17 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
425
427
|
renderer: { format: "enumOption", keyPrefix: STATUS_OPTION_KEY_PREFIX },
|
|
426
428
|
},
|
|
427
429
|
],
|
|
430
|
+
actions: [
|
|
431
|
+
{
|
|
432
|
+
id: "restrict",
|
|
433
|
+
icon: "lock",
|
|
434
|
+
label: i18nKey("userDataRights.privacyCenter.restriction.restrict"),
|
|
435
|
+
handler: UserDataRightsHandlers.restrictAccount,
|
|
436
|
+
confirm: i18nKey("userDataRights.privacyCenter.restriction.dialogDescription"),
|
|
437
|
+
visible: { field: "status", ne: "restricted" },
|
|
438
|
+
style: "secondary",
|
|
439
|
+
},
|
|
440
|
+
],
|
|
428
441
|
},
|
|
429
442
|
...(showDeletion
|
|
430
443
|
? [
|
|
@@ -441,43 +454,30 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
441
454
|
visible: { field: "status", eq: "deletionRequested" },
|
|
442
455
|
},
|
|
443
456
|
],
|
|
457
|
+
actions: [
|
|
458
|
+
{
|
|
459
|
+
id: "request-deletion",
|
|
460
|
+
label: i18nKey("userDataRights.privacyCenter.deletion.delete"),
|
|
461
|
+
handler: UserDataRightsHandlers.requestDeletion,
|
|
462
|
+
confirm: i18nKey("userDataRights.privacyCenter.deletion.dialogDescription"),
|
|
463
|
+
visible: { field: "status", ne: "deletionRequested" },
|
|
464
|
+
style: "danger" as const,
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
id: "cancel-deletion",
|
|
468
|
+
label: i18nKey("userDataRights.privacyCenter.deletion.cancel"),
|
|
469
|
+
handler: UserDataRightsHandlers.cancelDeletion,
|
|
470
|
+
visible: { field: "status", eq: "deletionRequested" },
|
|
471
|
+
style: "secondary" as const,
|
|
472
|
+
// Declared: the id's last segment ("deletion") would otherwise resolve to the destructive trash icon.
|
|
473
|
+
icon: "x" as const,
|
|
474
|
+
},
|
|
475
|
+
],
|
|
444
476
|
},
|
|
445
477
|
]
|
|
446
478
|
: []),
|
|
447
479
|
],
|
|
448
480
|
},
|
|
449
|
-
actions: [
|
|
450
|
-
{
|
|
451
|
-
id: "restrict",
|
|
452
|
-
icon: "lock",
|
|
453
|
-
label: i18nKey("userDataRights.privacyCenter.restriction.restrict"),
|
|
454
|
-
handler: UserDataRightsHandlers.restrictAccount,
|
|
455
|
-
confirm: i18nKey("userDataRights.privacyCenter.restriction.dialogDescription"),
|
|
456
|
-
visible: { field: "status", ne: "restricted" },
|
|
457
|
-
style: "danger",
|
|
458
|
-
},
|
|
459
|
-
...(showDeletion
|
|
460
|
-
? [
|
|
461
|
-
{
|
|
462
|
-
id: "request-deletion",
|
|
463
|
-
label: i18nKey("userDataRights.privacyCenter.deletion.delete"),
|
|
464
|
-
handler: UserDataRightsHandlers.requestDeletion,
|
|
465
|
-
confirm: i18nKey("userDataRights.privacyCenter.deletion.dialogDescription"),
|
|
466
|
-
visible: { field: "status", ne: "deletionRequested" },
|
|
467
|
-
style: "danger" as const,
|
|
468
|
-
},
|
|
469
|
-
{
|
|
470
|
-
id: "cancel-deletion",
|
|
471
|
-
label: i18nKey("userDataRights.privacyCenter.deletion.cancel"),
|
|
472
|
-
handler: UserDataRightsHandlers.cancelDeletion,
|
|
473
|
-
visible: { field: "status", eq: "deletionRequested" },
|
|
474
|
-
style: "secondary" as const,
|
|
475
|
-
// Declared: the id's last segment ("deletion") would otherwise resolve to the destructive trash icon.
|
|
476
|
-
icon: "x" as const,
|
|
477
|
-
},
|
|
478
|
-
]
|
|
479
|
-
: []),
|
|
480
|
-
],
|
|
481
481
|
});
|
|
482
482
|
|
|
483
483
|
// Magic-link path (anonymous): the email link carries the token as a
|
|
@@ -14,7 +14,10 @@ export const USER_DATA_RIGHTS_I18N: Readonly<Record<string, LocalizedString>> =
|
|
|
14
14
|
"screen:download-attempt-list.title": { en: "Download attempts" },
|
|
15
15
|
"screen:privacy-center.title": { en: "Privacy" },
|
|
16
16
|
"userDataRights.privacyCenter.subtitle": {
|
|
17
|
-
en: "
|
|
17
|
+
en: "Download a copy of your data (Art. 20), restrict processing of your account (Art. 18) or delete it (Art. 17).",
|
|
18
|
+
},
|
|
19
|
+
"userDataRights.privacyCenter.subtitleWithoutDeletion": {
|
|
20
|
+
en: "Download a copy of your data (Art. 20) or restrict processing of your account (Art. 18).",
|
|
18
21
|
},
|
|
19
22
|
"user-data-rights:entity:export-job:field:userId": { en: "User" },
|
|
20
23
|
"user-data-rights:entity:export-job:field:status": { en: "Status" },
|
|
@@ -19,8 +19,14 @@ export const itemEntity = {
|
|
|
19
19
|
description:
|
|
20
20
|
"A showcase task item carrying a title, free-text notes, a numeric priority, a done flag, a due date and a draft/active/blocked/done status, plus an optional parent item and a list of related items.",
|
|
21
21
|
fields: {
|
|
22
|
-
title: {
|
|
23
|
-
|
|
22
|
+
title: {
|
|
23
|
+
type: "text",
|
|
24
|
+
required: true,
|
|
25
|
+
sortable: true,
|
|
26
|
+
searchable: true,
|
|
27
|
+
allowPlaintext: "is_business_data",
|
|
28
|
+
},
|
|
29
|
+
notes: { type: "text", multiline: { rows: 4 }, allowPlaintext: "is_business_data" },
|
|
24
30
|
priority: { type: "number", default: 1, sortable: true, filterable: true },
|
|
25
31
|
isDone: { type: "boolean", default: false, sortable: true, filterable: true },
|
|
26
32
|
dueDate: { type: "date", sortable: true, filterable: true },
|
|
@@ -29,12 +29,12 @@ export const taskEntity = {
|
|
|
29
29
|
table: "read_ui_walkthrough_tasks",
|
|
30
30
|
|
|
31
31
|
fields: {
|
|
32
|
-
title: { type: "text", required: true, sortable: true },
|
|
32
|
+
title: { type: "text", required: true, sortable: true, allowPlaintext: "is_business_data" },
|
|
33
33
|
status: { type: "select", options: TASK_STATUSES, display: "radio", sortable: true },
|
|
34
34
|
area: { type: "select", options: TASK_AREAS },
|
|
35
35
|
priority: { type: "number" },
|
|
36
36
|
isUrgent: { type: "boolean", default: false },
|
|
37
|
-
notes: { type: "text" },
|
|
37
|
+
notes: { type: "text", allowPlaintext: "is_business_data" },
|
|
38
38
|
},
|
|
39
39
|
} as unknown as EntityDefinition;
|
|
40
40
|
|
|
@@ -8,7 +8,12 @@ import { InMemoryKmsAdapter } from "@cosmicdrift/kumiko-framework/crypto";
|
|
|
8
8
|
import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
9
9
|
import { composeEnvSchema } from "@cosmicdrift/kumiko-framework/env";
|
|
10
10
|
import { runProdApp } from "@cosmicdrift/kumiko-server-runtime";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
APP_FEATURES,
|
|
13
|
+
AUTH_COMPOSE_OPTIONS,
|
|
14
|
+
BOOT_FEATURES,
|
|
15
|
+
VALIDATE_BOOT_OPTIONS,
|
|
16
|
+
} from "../src/run-config";
|
|
12
17
|
|
|
13
18
|
const SMOKE_TENANT_ID = "00000000-0000-4000-8000-000000000001" as TenantId;
|
|
14
19
|
|
|
@@ -21,6 +26,7 @@ await runProdApp({
|
|
|
21
26
|
features: BOOT_FEATURES,
|
|
22
27
|
featureToggles: true,
|
|
23
28
|
envSchema,
|
|
29
|
+
validateBootOptions: VALIDATE_BOOT_OPTIONS,
|
|
24
30
|
// Smoke-only: exercises the hard PII boot gate + blind-index gate with
|
|
25
31
|
// every bundled feature mounted. Real apps wire createPgKmsAdapter(...).
|
|
26
32
|
kms: new InMemoryKmsAdapter(),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"source": "samples/apps/use-all-bundled APP_FEATURES (composeFeatures includeBundled + signup)",
|
|
3
|
-
"featureCount":
|
|
3
|
+
"featureCount": 87,
|
|
4
4
|
"features": [
|
|
5
5
|
{
|
|
6
6
|
"name": "account-security",
|
|
@@ -1473,6 +1473,10 @@
|
|
|
1473
1473
|
"configKeys": [],
|
|
1474
1474
|
"secrets": [],
|
|
1475
1475
|
"writeHandlers": [
|
|
1476
|
+
"jobs:write:consumer-disable",
|
|
1477
|
+
"jobs:write:consumer-enable",
|
|
1478
|
+
"jobs:write:consumer-restart",
|
|
1479
|
+
"jobs:write:consumer-skip",
|
|
1476
1480
|
"jobs:write:retry",
|
|
1477
1481
|
"jobs:write:trigger"
|
|
1478
1482
|
],
|
|
@@ -2066,6 +2070,20 @@
|
|
|
2066
2070
|
"secrets": [],
|
|
2067
2071
|
"writeHandlers": []
|
|
2068
2072
|
},
|
|
2073
|
+
{
|
|
2074
|
+
"name": "pat-scope-labels",
|
|
2075
|
+
"description": "German labels for the token scopes this sample app declares.",
|
|
2076
|
+
"toggleableDefault": null,
|
|
2077
|
+
"requires": [],
|
|
2078
|
+
"optionalRequires": [],
|
|
2079
|
+
"configReads": [],
|
|
2080
|
+
"exposesApis": [],
|
|
2081
|
+
"usesApis": [],
|
|
2082
|
+
"extensionsUsed": [],
|
|
2083
|
+
"configKeys": [],
|
|
2084
|
+
"secrets": [],
|
|
2085
|
+
"writeHandlers": []
|
|
2086
|
+
},
|
|
2069
2087
|
{
|
|
2070
2088
|
"name": "personal-access-tokens",
|
|
2071
2089
|
"description": "Long-lived, revocable Personal Access Tokens for headless HTTP-API access. Stores SHA-256 token hashes in the `store_api_tokens` direct-write table; the plaintext is returned once at creation. `create`/`revoke`/`mine` manage a user's own tokens and `available-scopes` lists the app-declared scope catalog. Bearer tokens carrying the PAT prefix are resolved before jwt.verify (roles resolved live, granted scopes enforced fail-closed at the API boundary) — registered as an auth-foundation tokenVerifier provider, resolved generically by the middleware. Pass { toggleable: { default: false } } to tier-gate the whole feature.",
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
import { collectTableMetas, type EntityTableMeta } from "@cosmicdrift/kumiko-framework/db";
|
|
12
12
|
import type { FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
13
13
|
import { composeFeatures } from "@cosmicdrift/kumiko-server-runtime/compose-features";
|
|
14
|
-
import { APP_FEATURES, AUTH_COMPOSE_OPTIONS } from "../src/run-config";
|
|
14
|
+
import { APP_FEATURES, AUTH_COMPOSE_OPTIONS, VALIDATE_BOOT_OPTIONS } from "../src/run-config";
|
|
15
|
+
|
|
16
|
+
export { VALIDATE_BOOT_OPTIONS };
|
|
15
17
|
|
|
16
18
|
export const FEATURES: readonly FeatureDefinition[] = composeFeatures(APP_FEATURES, {
|
|
17
19
|
includeBundled: true,
|
|
@@ -27,6 +27,7 @@ import { deliveryClient } from "@cosmicdrift/kumiko-bundled-features/delivery/we
|
|
|
27
27
|
import { featureTogglesClient } from "@cosmicdrift/kumiko-bundled-features/feature-toggles/web";
|
|
28
28
|
import { foldersClient } from "@cosmicdrift/kumiko-bundled-features/folders/web";
|
|
29
29
|
import { jobsClient } from "@cosmicdrift/kumiko-bundled-features/jobs/web";
|
|
30
|
+
import { sessionsClient } from "@cosmicdrift/kumiko-bundled-features/sessions/web";
|
|
30
31
|
import { tagsClient } from "@cosmicdrift/kumiko-bundled-features/tags/web";
|
|
31
32
|
import { textBlocksClient } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
|
|
32
33
|
import { tenantClient } from "@cosmicdrift/kumiko-bundled-features/tenant/web";
|
|
@@ -103,6 +104,7 @@ createKumikoApp({
|
|
|
103
104
|
adminShellClient(),
|
|
104
105
|
tenantClient(),
|
|
105
106
|
auditClient(),
|
|
107
|
+
sessionsClient(),
|
|
106
108
|
billingFoundationClient(),
|
|
107
109
|
jobsClient(),
|
|
108
110
|
deliveryClient(),
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
import { createTemplateResolverApi } from "@cosmicdrift/kumiko-bundled-features/template-resolver";
|
|
14
14
|
import type { RunDevAppOptions } from "@cosmicdrift/kumiko-dev-server";
|
|
15
15
|
import { SYSTEM_TENANT_ID, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
16
|
-
import { AUTH_COMPOSE_OPTIONS, BOOT_FEATURES } from "../run-config";
|
|
16
|
+
import { AUTH_COMPOSE_OPTIONS, BOOT_FEATURES, VALIDATE_BOOT_OPTIONS } from "../run-config";
|
|
17
17
|
import { ADMIN_EMAIL, ADMIN_PASSWORD, BETA_TENANT_ID, DEV_TENANT_ID } from "./auth-constants";
|
|
18
|
+
import { invoiceReportsFeature } from "./invoice-reports-feature";
|
|
18
19
|
import { notesFeature } from "./notes-feature";
|
|
19
20
|
import { appScreensFeature } from "./screens-feature";
|
|
20
21
|
import { seedScreenshotData } from "./seed";
|
|
@@ -27,8 +28,9 @@ import { seedScreenshotData } from "./seed";
|
|
|
27
28
|
export const devAppOptions = {
|
|
28
29
|
// notesFeature is dev-only (app-owned tag host) — see notes-feature.ts for why
|
|
29
30
|
// it stays out of APP_FEATURES. appScreensFeature places the user-profile screen.
|
|
30
|
-
features: [...BOOT_FEATURES, appScreensFeature, notesFeature],
|
|
31
|
+
features: [...BOOT_FEATURES, appScreensFeature, notesFeature, invoiceReportsFeature],
|
|
31
32
|
featureToggles: true,
|
|
33
|
+
validateBootOptions: VALIDATE_BOOT_OPTIONS,
|
|
32
34
|
port: Number.parseInt(process.env["PORT"] ?? "4186", 10),
|
|
33
35
|
clientEntry: "./src/app/client.tsx",
|
|
34
36
|
htmlPath: "./public/index.html",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// @runtime dev
|
|
2
|
+
//
|
|
3
|
+
// Dev-/screenshot-only host for realistic event consumers. The consumers never
|
|
4
|
+
// receive an event here; the screenshot seed parks them in the dead/disabled
|
|
5
|
+
// states the jobs:event-consumers screen is meant to show.
|
|
6
|
+
|
|
7
|
+
import { entityEventName } from "@cosmicdrift/kumiko-framework/db";
|
|
8
|
+
import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
9
|
+
|
|
10
|
+
export const INVOICE_TOTALS_CONSUMER = "invoice-totals";
|
|
11
|
+
export const OVERDUE_REMINDERS_CONSUMER = "overdue-reminders";
|
|
12
|
+
export const CUSTOMER_STATEMENTS_CONSUMER = "customer-statements";
|
|
13
|
+
|
|
14
|
+
const noop = async (): Promise<void> => {};
|
|
15
|
+
|
|
16
|
+
export const invoiceReportsFeature: FeatureDefinition = defineFeature("invoice-reports", (r) => {
|
|
17
|
+
r.describe(
|
|
18
|
+
"Dev-only host for the invoice read models whose consumer state the screenshot seed sets.",
|
|
19
|
+
);
|
|
20
|
+
for (const name of [
|
|
21
|
+
INVOICE_TOTALS_CONSUMER,
|
|
22
|
+
OVERDUE_REMINDERS_CONSUMER,
|
|
23
|
+
CUSTOMER_STATEMENTS_CONSUMER,
|
|
24
|
+
]) {
|
|
25
|
+
r.multiStreamProjection({
|
|
26
|
+
name,
|
|
27
|
+
apply: { [entityEventName("invoice", "created")]: noop },
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { patScopeOptionKey } from "@cosmicdrift/kumiko-bundled-features/personal-access-tokens";
|
|
2
|
+
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
|
|
4
|
+
// The scope domains in run-config.ts are the app's choice, so their German
|
|
5
|
+
// labels are too: personal-access-tokens only generates the English ones.
|
|
6
|
+
export const patScopeLabelsFeature = defineFeature("pat-scope-labels", (r) => {
|
|
7
|
+
r.describe("German labels for the token scopes this sample app declares.");
|
|
8
|
+
r.translations({
|
|
9
|
+
keys: {
|
|
10
|
+
[patScopeOptionKey("pages", "read")]: { de: "Seiten (lesen)" },
|
|
11
|
+
[patScopeOptionKey("pages", "write")]: { de: "Seiten (lesen & schreiben)" },
|
|
12
|
+
[patScopeOptionKey("tags", "read")]: { de: "Tags (lesen)" },
|
|
13
|
+
[patScopeOptionKey("tags", "write")]: { de: "Tags (lesen & schreiben)" },
|
|
14
|
+
[patScopeOptionKey("notesHistory", "read")]: { de: "Notizen (lesen)" },
|
|
15
|
+
[patScopeOptionKey("notesHistory", "write")]: { de: "Notizen (lesen & schreiben)" },
|
|
16
|
+
[patScopeOptionKey("ledger", "read")]: { de: "Hauptbuch (lesen)" },
|
|
17
|
+
[patScopeOptionKey("ledger", "write")]: { de: "Hauptbuch (lesen & schreiben)" },
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
return {};
|
|
21
|
+
});
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
type DbConnection,
|
|
31
31
|
fetchOne,
|
|
32
32
|
insertOne,
|
|
33
|
+
upsertOnConflict,
|
|
33
34
|
} from "@cosmicdrift/kumiko-framework/db";
|
|
34
35
|
import {
|
|
35
36
|
createEntityExecutor,
|
|
@@ -37,11 +38,17 @@ import {
|
|
|
37
38
|
SYSTEM_TENANT_ID,
|
|
38
39
|
type TenantId,
|
|
39
40
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
41
|
+
import {
|
|
42
|
+
ConsumerStatuses,
|
|
43
|
+
eventConsumerStateTable,
|
|
44
|
+
SHARED_INSTANCE_SENTINEL,
|
|
45
|
+
} from "@cosmicdrift/kumiko-framework/pipeline";
|
|
40
46
|
import { TestUsers } from "@cosmicdrift/kumiko-framework/seeding";
|
|
41
47
|
import { generateId } from "@cosmicdrift/kumiko-framework/utils";
|
|
42
48
|
import { unsafeCreateEntityTable } from "@cosmicdrift/kumiko-testing/stack";
|
|
43
49
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
44
50
|
import { ADMIN_EMAIL, BETA_TENANT_ID, DEMO_NOTE_ID, DEV_TENANT_ID } from "./auth-constants";
|
|
51
|
+
import { INVOICE_TOTALS_CONSUMER, OVERDUE_REMINDERS_CONSUMER } from "./invoice-reports-feature";
|
|
45
52
|
import { noteEntity } from "./notes-feature";
|
|
46
53
|
import { seedPdfDocuments } from "./seed-documents";
|
|
47
54
|
|
|
@@ -266,6 +273,48 @@ const PRIVACY_BODY = [
|
|
|
266
273
|
"Access, rectification, erasure, restriction, portability, objection.",
|
|
267
274
|
].join("\n");
|
|
268
275
|
|
|
276
|
+
// Dispatcher state, not an event-sourced table: parks two invoice consumers the
|
|
277
|
+
// way a failing handler and an operator pause would leave them, so the
|
|
278
|
+
// jobs:event-consumers screens have a dead and a paused row to show.
|
|
279
|
+
async function seedEventConsumerStates(stack: Parameters<SeedFn>[0]): Promise<void> {
|
|
280
|
+
const qualifiedNameOf = (consumer: string): string => {
|
|
281
|
+
const found = [...stack.registry.getAllMultiStreamProjections().keys()].find(
|
|
282
|
+
(name) => name === consumer || name.endsWith(`:${consumer}`),
|
|
283
|
+
);
|
|
284
|
+
if (!found) throw new Error(`seedEventConsumerStates: consumer "${consumer}" not registered`);
|
|
285
|
+
return found;
|
|
286
|
+
};
|
|
287
|
+
const parked = [
|
|
288
|
+
{
|
|
289
|
+
consumer: INVOICE_TOTALS_CONSUMER,
|
|
290
|
+
status: ConsumerStatuses.dead,
|
|
291
|
+
attempts: 5,
|
|
292
|
+
lastError:
|
|
293
|
+
'TypeError: Cannot read properties of undefined (reading "amount") while applying invoice.created (event 48213): payload.lineItems[2].netPrice.amount is missing',
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
consumer: OVERDUE_REMINDERS_CONSUMER,
|
|
297
|
+
status: ConsumerStatuses.disabled,
|
|
298
|
+
attempts: 0,
|
|
299
|
+
lastError: null,
|
|
300
|
+
},
|
|
301
|
+
];
|
|
302
|
+
for (const { consumer, status, attempts, lastError } of parked) {
|
|
303
|
+
await upsertOnConflict(
|
|
304
|
+
stack.db,
|
|
305
|
+
eventConsumerStateTable,
|
|
306
|
+
{
|
|
307
|
+
name: qualifiedNameOf(consumer),
|
|
308
|
+
instanceId: SHARED_INSTANCE_SENTINEL,
|
|
309
|
+
status,
|
|
310
|
+
attempts,
|
|
311
|
+
lastError,
|
|
312
|
+
},
|
|
313
|
+
{ conflictKeys: ["name", "instanceId"], update: { status, attempts, lastError } },
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
269
318
|
export const seedScreenshotData: SeedFn = async (stack) => {
|
|
270
319
|
const devTenant = DEV_TENANT_ID as TenantId;
|
|
271
320
|
const betaTenant = BETA_TENANT_ID as TenantId;
|
|
@@ -384,6 +433,8 @@ export const seedScreenshotData: SeedFn = async (stack) => {
|
|
|
384
433
|
// custom-fields + folders extension sections on note-edit.
|
|
385
434
|
await seedCustomFieldsAndFolders(stack, devTenant, devAdminId);
|
|
386
435
|
|
|
436
|
+
await seedEventConsumerStates(stack);
|
|
437
|
+
|
|
387
438
|
// personal-access-tokens list for the admin (active tenant = dev).
|
|
388
439
|
await seedApiTokens(stack.db, devTenant);
|
|
389
440
|
|
|
@@ -102,6 +102,7 @@ import { DEV_TENANT_ID } from "./app/auth-constants";
|
|
|
102
102
|
import { CAP_OVERVIEW_CAPS } from "./app/cap-overview-caps";
|
|
103
103
|
import { capOverviewLabelsFeature } from "./app/cap-overview-labels-feature";
|
|
104
104
|
import { collectionLabelsFeature } from "./app/collection-labels-feature";
|
|
105
|
+
import { patScopeLabelsFeature } from "./app/pat-scope-labels-feature";
|
|
105
106
|
|
|
106
107
|
// Smoke-only stubs. Boot-mode skipt jede operative Methode — diese werden
|
|
107
108
|
// nie aufgerufen, nur typecheck'd.
|
|
@@ -161,6 +162,7 @@ export const APP_FEATURES = [
|
|
|
161
162
|
ledger: { label: "Ledger", read: ["ledger:query:*"], write: ["ledger:write:*"] },
|
|
162
163
|
},
|
|
163
164
|
}),
|
|
165
|
+
patScopeLabelsFeature,
|
|
164
166
|
createReadinessFeature(),
|
|
165
167
|
|
|
166
168
|
// delivery + channels
|
|
@@ -417,6 +419,9 @@ export const BOOT_FEATURES = APP_FEATURES.filter(
|
|
|
417
419
|
// Smoke signup — enables createAuthSelfRegistrationToggleFeature via
|
|
418
420
|
// composeFeatures when passed as authOptions (#1521 Option A). Same shape
|
|
419
421
|
// as run{Dev,Prod}App's auth.signup; appUrl is a stub (no real mail in boot).
|
|
422
|
+
// locale-de is mounted, so every translation key must exist in German and English.
|
|
423
|
+
export const VALIDATE_BOOT_OPTIONS = { requiredLocales: ["de", "en"] } as const;
|
|
424
|
+
|
|
420
425
|
export const AUTH_COMPOSE_OPTIONS = {
|
|
421
426
|
signup: {
|
|
422
427
|
appUrl: "http://localhost:4186/signup",
|
|
@@ -12,8 +12,8 @@ import type {
|
|
|
12
12
|
export const orderEntity = {
|
|
13
13
|
table: "read_ws_orders",
|
|
14
14
|
fields: {
|
|
15
|
-
label: { type: "text", required: true, sortable: true },
|
|
16
|
-
status: { type: "text", sortable: true },
|
|
15
|
+
label: { type: "text", required: true, sortable: true, allowPlaintext: "is_business_data" },
|
|
16
|
+
status: { type: "text", sortable: true, allowPlaintext: "is_business_data" },
|
|
17
17
|
notes: { type: "text", allowPlaintext: "is_business_data" },
|
|
18
18
|
},
|
|
19
19
|
} as unknown as EntityDefinition;
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
wireCustomFieldsFor,
|
|
25
25
|
} from "@cosmicdrift/kumiko-bundled-features/custom-fields";
|
|
26
26
|
import { buildEntityTable, createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
|
|
27
|
-
import { createEntity, defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
27
|
+
import { createEntity, createTextField, defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
28
28
|
import { z } from "zod";
|
|
29
29
|
|
|
30
30
|
// --- Entity ---
|
|
@@ -39,7 +39,12 @@ import { z } from "zod";
|
|
|
39
39
|
export const propertyEntity = createEntity({
|
|
40
40
|
table: "read_sample_cf_properties",
|
|
41
41
|
fields: {
|
|
42
|
-
name: {
|
|
42
|
+
name: createTextField({
|
|
43
|
+
required: true,
|
|
44
|
+
maxLength: 200,
|
|
45
|
+
personal: false,
|
|
46
|
+
reason: "property_name_is_business_data",
|
|
47
|
+
}),
|
|
43
48
|
customFields: customFieldsField(),
|
|
44
49
|
},
|
|
45
50
|
});
|
|
@@ -59,7 +64,12 @@ export const propertyFeature = defineFeature("property-management", (r) => {
|
|
|
59
64
|
name: "property",
|
|
60
65
|
table: "read_sample_cf_properties",
|
|
61
66
|
fields: {
|
|
62
|
-
name: {
|
|
67
|
+
name: createTextField({
|
|
68
|
+
required: true,
|
|
69
|
+
maxLength: 200,
|
|
70
|
+
personal: false,
|
|
71
|
+
reason: "property_name_is_business_data",
|
|
72
|
+
}),
|
|
63
73
|
customFields: { type: "jsonb" },
|
|
64
74
|
},
|
|
65
75
|
});
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// - nav → ID/Label/Screen-Card
|
|
7
7
|
// Plus optionalRequires + metric als JSON-Dump-Fallback.
|
|
8
8
|
|
|
9
|
-
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
9
|
+
import { createTextField, defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
|
|
12
12
|
defineFeature("designerDemo", (r) => {
|
|
@@ -15,7 +15,11 @@ defineFeature("designerDemo", (r) => {
|
|
|
15
15
|
r.entity({
|
|
16
16
|
name: "task",
|
|
17
17
|
fields: {
|
|
18
|
-
title: {
|
|
18
|
+
title: createTextField({
|
|
19
|
+
required: true,
|
|
20
|
+
personal: false,
|
|
21
|
+
reason: "demo_task_title_is_not_personal_data",
|
|
22
|
+
}),
|
|
19
23
|
done: { type: "boolean", default: false },
|
|
20
24
|
priority: { type: "select", options: ["low", "medium", "high"], default: "medium" },
|
|
21
25
|
},
|
|
@@ -6,6 +6,7 @@ Feature-scoped translations — 'Hallo' and 'Hello' in the same app.
|
|
|
6
6
|
|
|
7
7
|
- `r.translations()` for multi-language feature keys
|
|
8
8
|
- Plurals
|
|
9
|
+
- `requiredLocales`: boot fails when a key misses one of the app's languages
|
|
9
10
|
|
|
10
11
|
## Plurals
|
|
11
12
|
|
|
@@ -24,6 +25,20 @@ exist where the locale's grammar needs them:
|
|
|
24
25
|
`Intl.PluralRules(locale)`, then interpolates `{count}` (and any other
|
|
25
26
|
`{name}` placeholder) into the chosen form.
|
|
26
27
|
|
|
28
|
+
## Required locales
|
|
29
|
+
|
|
30
|
+
Without further options boot only checks that each feature is consistent with
|
|
31
|
+
itself. An app that ships fixed languages lists them, and boot then fails on any
|
|
32
|
+
key that misses one of them, including framework keys. Locale packages such as
|
|
33
|
+
`locale-de` count:
|
|
34
|
+
|
|
35
|
+
```ts illustration
|
|
36
|
+
runProdApp({ ...options, validateBootOptions: { requiredLocales: ["de", "en"] } });
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Define the options once and re-export them as `VALIDATE_BOOT_OPTIONS` from
|
|
40
|
+
`kumiko/schema.ts`, so `kumiko-schema validate` checks the same list in CI.
|
|
41
|
+
|
|
27
42
|
## Source
|
|
28
43
|
|
|
29
44
|
Feature entry point: `src/feature.ts`.
|
|
@@ -11,20 +11,24 @@ export const greetingFeature = defineFeature("greeting", (r) => {
|
|
|
11
11
|
de: "Willkommen",
|
|
12
12
|
en: "Welcome",
|
|
13
13
|
fr: "Bienvenue",
|
|
14
|
+
pl: "Witamy",
|
|
14
15
|
},
|
|
15
16
|
"greeting.goodbye": {
|
|
16
17
|
de: "Auf Wiedersehen",
|
|
17
18
|
en: "Goodbye",
|
|
18
19
|
fr: "Au revoir",
|
|
20
|
+
pl: "Do widzenia",
|
|
19
21
|
},
|
|
20
22
|
"greeting.hello_name": {
|
|
21
23
|
de: "Hallo, {name}!",
|
|
22
24
|
en: "Hello, {name}!",
|
|
23
25
|
fr: "Bonjour, {name}!",
|
|
26
|
+
pl: "Cześć, {name}!",
|
|
24
27
|
},
|
|
25
28
|
"greeting.unread_count": {
|
|
26
29
|
de: { one: "{count} ungelesene Nachricht", other: "{count} ungelesene Nachrichten" },
|
|
27
30
|
en: { one: "{count} unread message", other: "{count} unread messages" },
|
|
31
|
+
fr: { one: "{count} message non lu", other: "{count} messages non lus" },
|
|
28
32
|
pl: {
|
|
29
33
|
one: "{count} nieprzeczytana wiadomość",
|
|
30
34
|
few: "{count} nieprzeczytane wiadomości",
|