@open-mercato/core 0.6.7-develop.6795.1.8a3f27921c → 0.6.7-develop.6824.1.21fff90682
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/.turbo/turbo-build.log +1 -1
- package/dist/generated/entities/customer_deal_person_link/index.js +2 -0
- package/dist/generated/entities/customer_deal_person_link/index.js.map +2 -2
- package/dist/generated/entities/customer_user_invitation/index.js +2 -0
- package/dist/generated/entities/customer_user_invitation/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/auth/lib/backendChrome.js +10 -9
- package/dist/modules/auth/lib/backendChrome.js.map +2 -2
- package/dist/modules/auth/lib/tenantAccess.js +7 -0
- package/dist/modules/auth/lib/tenantAccess.js.map +2 -2
- package/dist/modules/business_rules/api/openmercato-call-options/route.js +85 -0
- package/dist/modules/business_rules/api/openmercato-call-options/route.js.map +7 -0
- package/dist/modules/business_rules/api/rules/route.js +71 -4
- package/dist/modules/business_rules/api/rules/route.js.map +2 -2
- package/dist/modules/business_rules/components/ActionBuilder.js +47 -4
- package/dist/modules/business_rules/components/ActionBuilder.js.map +3 -3
- package/dist/modules/business_rules/components/ActionRow.js +106 -16
- package/dist/modules/business_rules/components/ActionRow.js.map +2 -2
- package/dist/modules/business_rules/components/utils/actionValidation.js +6 -0
- package/dist/modules/business_rules/components/utils/actionValidation.js.map +2 -2
- package/dist/modules/business_rules/lib/action-executor.js +148 -0
- package/dist/modules/business_rules/lib/action-executor.js.map +2 -2
- package/dist/modules/business_rules/lib/openmercato-call-options-types.js +5 -0
- package/dist/modules/business_rules/lib/openmercato-call-options-types.js.map +7 -0
- package/dist/modules/business_rules/lib/openmercato-call-options.js +231 -0
- package/dist/modules/business_rules/lib/openmercato-call-options.js.map +7 -0
- package/dist/modules/business_rules/lib/rule-engine.js +5 -1
- package/dist/modules/business_rules/lib/rule-engine.js.map +2 -2
- package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js +11 -1
- package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js +11 -6
- package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js.map +2 -2
- package/dist/modules/configs/lib/upgrade-actions.js +12 -0
- package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/users/[id].js +10 -1
- package/dist/modules/customer_accounts/api/admin/users/[id].js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/users-invite.js +61 -4
- package/dist/modules/customer_accounts/api/admin/users-invite.js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/users-invite.js +20 -2
- package/dist/modules/customer_accounts/api/portal/users-invite.js.map +2 -2
- package/dist/modules/customer_accounts/data/entities.js +3 -0
- package/dist/modules/customer_accounts/data/entities.js.map +2 -2
- package/dist/modules/customer_accounts/data/validators.js +1 -0
- package/dist/modules/customer_accounts/data/validators.js.map +2 -2
- package/dist/modules/customer_accounts/emails/CustomerInvitationEmail.js +26 -0
- package/dist/modules/customer_accounts/emails/CustomerInvitationEmail.js.map +7 -0
- package/dist/modules/customer_accounts/lib/customerEntityOwnership.js +29 -3
- package/dist/modules/customer_accounts/lib/customerEntityOwnership.js.map +2 -2
- package/dist/modules/customer_accounts/lib/customerUrl.js +19 -10
- package/dist/modules/customer_accounts/lib/customerUrl.js.map +2 -2
- package/dist/modules/customer_accounts/lib/invitationEmail.js +35 -0
- package/dist/modules/customer_accounts/lib/invitationEmail.js.map +7 -0
- package/dist/modules/customer_accounts/migrations/Migration20260723120000_customer_accounts.js +13 -0
- package/dist/modules/customer_accounts/migrations/Migration20260723120000_customer_accounts.js.map +7 -0
- package/dist/modules/customer_accounts/services/customerInvitationService.js +32 -2
- package/dist/modules/customer_accounts/services/customerInvitationService.js.map +2 -2
- package/dist/modules/customer_accounts/subscribers/autoLinkCrm.js +47 -7
- package/dist/modules/customer_accounts/subscribers/autoLinkCrm.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +2 -2
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection-table.js +1 -1
- package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/people/route.js +4 -2
- package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/route.js +7 -3
- package/dist/modules/customers/api/deals/[id]/route.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +11 -1
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +14 -8
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +21 -6
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +48 -10
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/commands/people.js +2 -0
- package/dist/modules/customers/commands/people.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyDetailTabs.js +26 -6
- package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/DealDetailTabs.js +30 -8
- package/dist/modules/customers/components/detail/DealDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonDetailTabs.js +30 -8
- package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
- package/dist/modules/customers/data/entities.js +8 -0
- package/dist/modules/customers/data/entities.js.map +2 -2
- package/dist/modules/customers/data/validators.js +2 -1
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/dist/modules/customers/migrations/Migration20260723210000_customers.js +15 -0
- package/dist/modules/customers/migrations/Migration20260723210000_customers.js.map +7 -0
- package/dist/modules/directory/api/tenants/route.js +8 -2
- package/dist/modules/directory/api/tenants/route.js.map +2 -2
- package/dist/modules/directory/commands/tenants.js +6 -0
- package/dist/modules/directory/commands/tenants.js.map +2 -2
- package/dist/modules/entities/api/entity-settings.js +59 -9
- package/dist/modules/entities/api/entity-settings.js.map +2 -2
- package/dist/modules/entities/backend/config/settings/page.js +10 -0
- package/dist/modules/entities/backend/config/settings/page.js.map +7 -0
- package/dist/modules/entities/backend/config/settings/page.meta.js +36 -0
- package/dist/modules/entities/backend/config/settings/page.meta.js.map +7 -0
- package/dist/modules/entities/backend/entities/user/create/page.js +16 -7
- package/dist/modules/entities/backend/entities/user/create/page.js.map +2 -2
- package/dist/modules/entities/components/EntitySettingsManager.js +136 -0
- package/dist/modules/entities/components/EntitySettingsManager.js.map +7 -0
- package/dist/modules/inbox_ops/api/webhook/inbound.js +14 -10
- package/dist/modules/inbox_ops/api/webhook/inbound.js.map +2 -2
- package/dist/modules/payment_gateways/api/sessions/route.js +1 -3
- package/dist/modules/payment_gateways/api/sessions/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +11 -1
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +2 -2
- package/dist/modules/payment_gateways/data/entities.js +4 -0
- package/dist/modules/payment_gateways/data/entities.js.map +2 -2
- package/dist/modules/payment_gateways/lib/gateway-service.js +15 -6
- package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
- package/dist/modules/payment_gateways/lib/session-idempotency.js +33 -0
- package/dist/modules/payment_gateways/lib/session-idempotency.js.map +2 -2
- package/dist/modules/payment_gateways/migrations/Migration20260803103035_payment_gateways.js +18 -0
- package/dist/modules/payment_gateways/migrations/Migration20260803103035_payment_gateways.js.map +7 -0
- package/dist/modules/payment_gateways/setup.js +47 -0
- package/dist/modules/payment_gateways/setup.js.map +2 -2
- package/dist/modules/payment_gateways/workers/session-initialization-prune.js +50 -0
- package/dist/modules/payment_gateways/workers/session-initialization-prune.js.map +7 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js +134 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js.map +7 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.meta.js +11 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.meta.js.map +7 -0
- package/dist/modules/portal/lib/navigation.js +7 -0
- package/dist/modules/portal/lib/navigation.js.map +7 -0
- package/dist/modules/query_index/lib/engine.js +43 -0
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/query_index/lib/search-tokens.js +15 -5
- package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
- package/dist/modules/shipping_carriers/api/webhook/[provider]/route.js +11 -1
- package/dist/modules/shipping_carriers/api/webhook/[provider]/route.js.map +2 -2
- package/generated/entities/customer_deal_person_link/index.ts +1 -0
- package/generated/entities/customer_user_invitation/index.ts +1 -0
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +7 -7
- package/src/modules/auth/lib/backendChrome.tsx +15 -9
- package/src/modules/auth/lib/tenantAccess.ts +8 -0
- package/src/modules/business_rules/api/openmercato-call-options/route.ts +94 -0
- package/src/modules/business_rules/api/rules/route.ts +92 -4
- package/src/modules/business_rules/components/ActionBuilder.tsx +54 -5
- package/src/modules/business_rules/components/ActionRow.tsx +156 -18
- package/src/modules/business_rules/components/utils/actionValidation.ts +6 -0
- package/src/modules/business_rules/i18n/de.json +12 -0
- package/src/modules/business_rules/i18n/en.json +12 -0
- package/src/modules/business_rules/i18n/es.json +12 -0
- package/src/modules/business_rules/i18n/pl.json +12 -0
- package/src/modules/business_rules/lib/action-executor.ts +198 -0
- package/src/modules/business_rules/lib/openmercato-call-options-types.ts +31 -0
- package/src/modules/business_rules/lib/openmercato-call-options.ts +297 -0
- package/src/modules/business_rules/lib/rule-engine.ts +4 -0
- package/src/modules/communication_channels/api/post/webhook/[provider]/route.ts +11 -1
- package/src/modules/communication_channels/api/post/webhooks/gmail/route.ts +17 -11
- package/src/modules/configs/lib/upgrade-actions.ts +14 -0
- package/src/modules/customer_accounts/api/admin/users/[id].ts +16 -1
- package/src/modules/customer_accounts/api/admin/users-invite.ts +78 -3
- package/src/modules/customer_accounts/api/portal/users-invite.ts +22 -1
- package/src/modules/customer_accounts/data/entities.ts +3 -0
- package/src/modules/customer_accounts/data/validators.ts +1 -0
- package/src/modules/customer_accounts/emails/CustomerInvitationEmail.tsx +45 -0
- package/src/modules/customer_accounts/i18n/de.json +6 -0
- package/src/modules/customer_accounts/i18n/en.json +6 -0
- package/src/modules/customer_accounts/i18n/es.json +6 -0
- package/src/modules/customer_accounts/i18n/pl.json +6 -0
- package/src/modules/customer_accounts/lib/customerEntityOwnership.ts +68 -2
- package/src/modules/customer_accounts/lib/customerUrl.ts +27 -11
- package/src/modules/customer_accounts/lib/invitationEmail.ts +42 -0
- package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +16 -0
- package/src/modules/customer_accounts/migrations/Migration20260723120000_customer_accounts.ts +13 -0
- package/src/modules/customer_accounts/services/customerInvitationService.ts +56 -3
- package/src/modules/customer_accounts/subscribers/autoLinkCrm.ts +75 -8
- package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +2 -2
- package/src/modules/customer_accounts/widgets/injection-table.ts +4 -1
- package/src/modules/customers/api/deals/[id]/people/route.ts +3 -0
- package/src/modules/customers/api/deals/[id]/route.ts +10 -2
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +12 -1
- package/src/modules/customers/backend/customers/deals/[id]/hooks/types.ts +1 -0
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +15 -9
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +24 -6
- package/src/modules/customers/commands/deals.ts +50 -9
- package/src/modules/customers/commands/people.ts +3 -0
- package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +30 -7
- package/src/modules/customers/components/detail/DealDetailTabs.tsx +34 -9
- package/src/modules/customers/components/detail/PersonDetailTabs.tsx +34 -9
- package/src/modules/customers/data/entities.ts +9 -1
- package/src/modules/customers/data/validators.ts +1 -0
- package/src/modules/customers/i18n/de.json +1 -0
- package/src/modules/customers/i18n/en.json +1 -0
- package/src/modules/customers/i18n/es.json +1 -0
- package/src/modules/customers/i18n/pl.json +1 -0
- package/src/modules/customers/migrations/.snapshot-open-mercato.json +27 -1
- package/src/modules/customers/migrations/Migration20260723210000_customers.ts +15 -0
- package/src/modules/directory/api/tenants/route.ts +9 -1
- package/src/modules/directory/commands/tenants.ts +6 -0
- package/src/modules/entities/api/entity-settings.ts +66 -22
- package/src/modules/entities/backend/config/settings/page.meta.ts +34 -0
- package/src/modules/entities/backend/config/settings/page.tsx +12 -0
- package/src/modules/entities/backend/entities/user/create/page.tsx +16 -7
- package/src/modules/entities/components/EntitySettingsManager.tsx +160 -0
- package/src/modules/entities/i18n/de.json +9 -0
- package/src/modules/entities/i18n/en.json +9 -0
- package/src/modules/entities/i18n/es.json +9 -0
- package/src/modules/entities/i18n/pl.json +9 -0
- package/src/modules/inbox_ops/api/webhook/inbound.ts +14 -12
- package/src/modules/payment_gateways/api/sessions/route.ts +1 -3
- package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +13 -1
- package/src/modules/payment_gateways/data/entities.ts +5 -0
- package/src/modules/payment_gateways/i18n/de.json +5 -1
- package/src/modules/payment_gateways/i18n/en.json +5 -1
- package/src/modules/payment_gateways/i18n/es.json +5 -1
- package/src/modules/payment_gateways/i18n/pl.json +5 -1
- package/src/modules/payment_gateways/lib/gateway-service.ts +17 -8
- package/src/modules/payment_gateways/lib/session-idempotency.ts +36 -0
- package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +10 -1
- package/src/modules/payment_gateways/migrations/Migration20260803103035_payment_gateways.ts +17 -0
- package/src/modules/payment_gateways/setup.ts +62 -0
- package/src/modules/payment_gateways/workers/session-initialization-prune.ts +63 -0
- package/src/modules/portal/frontend/[orgSlug]/portal/invite/page.meta.ts +9 -0
- package/src/modules/portal/frontend/[orgSlug]/portal/invite/page.tsx +164 -0
- package/src/modules/portal/i18n/de.json +18 -0
- package/src/modules/portal/i18n/en.json +18 -0
- package/src/modules/portal/i18n/es.json +18 -0
- package/src/modules/portal/i18n/pl.json +18 -0
- package/src/modules/portal/lib/navigation.ts +3 -0
- package/src/modules/query_index/lib/engine.ts +52 -0
- package/src/modules/query_index/lib/search-tokens.ts +16 -4
- package/src/modules/shipping_carriers/api/webhook/[provider]/route.ts +11 -1
|
@@ -2,6 +2,9 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
4
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
|
+
import { beginEntitiesMutationGuard } from "./definitions.mutation-guard.js";
|
|
6
|
+
import { enforceCommandOptimisticLockWithGuards } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
|
|
7
|
+
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
5
8
|
const CONFIG_MODULE = "entities";
|
|
6
9
|
const NEW_ENTITIES_RESTRICTED_KEY = "newEntitiesRestrictedByDefault";
|
|
7
10
|
const metadata = {
|
|
@@ -24,11 +27,19 @@ async function readPolicy(tenantId) {
|
|
|
24
27
|
async function GET(req) {
|
|
25
28
|
const auth = await getAuthFromRequest(req);
|
|
26
29
|
if (!auth || !auth.tenantId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
27
|
-
const
|
|
28
|
-
|
|
30
|
+
const { resolve } = await createRequestContainer();
|
|
31
|
+
const moduleConfigService = resolve("moduleConfigService");
|
|
32
|
+
const record = await moduleConfigService.getRecord(CONFIG_MODULE, NEW_ENTITIES_RESTRICTED_KEY, {
|
|
33
|
+
tenantId: auth.tenantId ?? null
|
|
34
|
+
});
|
|
35
|
+
return NextResponse.json({
|
|
36
|
+
newEntitiesRestrictedByDefault: record ? record.value === true : false,
|
|
37
|
+
updatedAt: record ? record.updatedAt : null
|
|
38
|
+
});
|
|
29
39
|
}
|
|
30
40
|
const putBodySchema = z.object({
|
|
31
|
-
newEntitiesRestrictedByDefault: z.boolean()
|
|
41
|
+
newEntitiesRestrictedByDefault: z.boolean(),
|
|
42
|
+
expectedUpdatedAt: z.string().optional()
|
|
32
43
|
});
|
|
33
44
|
async function PUT(req) {
|
|
34
45
|
const auth = await getAuthFromRequest(req);
|
|
@@ -43,18 +54,56 @@ async function PUT(req) {
|
|
|
43
54
|
if (!parsed.success) {
|
|
44
55
|
return NextResponse.json({ error: "Validation failed", details: parsed.error.flatten() }, { status: 400 });
|
|
45
56
|
}
|
|
46
|
-
const
|
|
57
|
+
const container = await createRequestContainer();
|
|
58
|
+
const { resolve } = container;
|
|
47
59
|
const moduleConfigService = resolve("moduleConfigService");
|
|
48
|
-
await moduleConfigService.
|
|
60
|
+
const currentRecord = await moduleConfigService.getRecord(
|
|
61
|
+
CONFIG_MODULE,
|
|
62
|
+
NEW_ENTITIES_RESTRICTED_KEY,
|
|
63
|
+
{ tenantId: auth.tenantId ?? null }
|
|
64
|
+
);
|
|
65
|
+
const guard = await beginEntitiesMutationGuard({
|
|
66
|
+
container,
|
|
67
|
+
auth,
|
|
68
|
+
req,
|
|
69
|
+
resourceKind: "entities.settings",
|
|
70
|
+
resourceId: NEW_ENTITIES_RESTRICTED_KEY,
|
|
71
|
+
operation: currentRecord ? "update" : "create",
|
|
72
|
+
mutationPayload: parsed.data
|
|
73
|
+
});
|
|
74
|
+
if (guard.blockedResponse) return guard.blockedResponse;
|
|
75
|
+
const ifMatchHeader = req.headers.get("If-Match") || req.headers.get("x-om-ext-optimistic-lock-expected-updated-at");
|
|
76
|
+
const expectedUpdatedAt = ifMatchHeader || parsed.data.expectedUpdatedAt;
|
|
77
|
+
try {
|
|
78
|
+
await enforceCommandOptimisticLockWithGuards(container, {
|
|
79
|
+
resourceKind: "entities.settings",
|
|
80
|
+
resourceId: NEW_ENTITIES_RESTRICTED_KEY,
|
|
81
|
+
current: currentRecord ? currentRecord.updatedAt : null,
|
|
82
|
+
expected: expectedUpdatedAt,
|
|
83
|
+
request: req
|
|
84
|
+
});
|
|
85
|
+
} catch (lockError) {
|
|
86
|
+
if (isCrudHttpError(lockError)) {
|
|
87
|
+
return NextResponse.json(lockError.body, { status: lockError.status });
|
|
88
|
+
}
|
|
89
|
+
throw lockError;
|
|
90
|
+
}
|
|
91
|
+
const updatedRecord = await moduleConfigService.setValue(
|
|
49
92
|
CONFIG_MODULE,
|
|
50
93
|
NEW_ENTITIES_RESTRICTED_KEY,
|
|
51
94
|
parsed.data.newEntitiesRestrictedByDefault,
|
|
52
95
|
{ tenantId: auth.tenantId ?? null }
|
|
53
96
|
);
|
|
54
|
-
|
|
97
|
+
await guard.runAfterSuccess();
|
|
98
|
+
return NextResponse.json({
|
|
99
|
+
ok: true,
|
|
100
|
+
newEntitiesRestrictedByDefault: parsed.data.newEntitiesRestrictedByDefault,
|
|
101
|
+
updatedAt: updatedRecord ? updatedRecord.updatedAt : null
|
|
102
|
+
});
|
|
55
103
|
}
|
|
56
104
|
const settingsResponseSchema = z.object({
|
|
57
|
-
newEntitiesRestrictedByDefault: z.boolean()
|
|
105
|
+
newEntitiesRestrictedByDefault: z.boolean(),
|
|
106
|
+
updatedAt: z.string().nullable().optional()
|
|
58
107
|
});
|
|
59
108
|
const openApi = {
|
|
60
109
|
tag: "Entities",
|
|
@@ -73,9 +122,10 @@ const openApi = {
|
|
|
73
122
|
description: "Sets the tenant-scoped default-restricted policy for new custom entities.",
|
|
74
123
|
requestBody: { schema: putBodySchema },
|
|
75
124
|
responses: [
|
|
76
|
-
{ status: 200, description: "Updated settings", schema: z.object({ ok: z.boolean(), newEntitiesRestrictedByDefault: z.boolean() }) },
|
|
125
|
+
{ status: 200, description: "Updated settings", schema: z.object({ ok: z.boolean(), newEntitiesRestrictedByDefault: z.boolean(), updatedAt: z.string().nullable().optional() }) },
|
|
77
126
|
{ status: 400, description: "Invalid payload", schema: z.object({ error: z.string() }) },
|
|
78
|
-
{ status: 401, description: "Missing authentication", schema: z.object({ error: z.string() }) }
|
|
127
|
+
{ status: 401, description: "Missing authentication", schema: z.object({ error: z.string() }) },
|
|
128
|
+
{ status: 409, description: "Optimistic lock conflict", schema: z.object({ code: z.string(), error: z.string() }) }
|
|
79
129
|
]
|
|
80
130
|
}
|
|
81
131
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/entities/api/entity-settings.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\n// Tenant-scoped policy: when true, new custom entities are created with\n// `access_restricted = true` unless the create request explicitly sets the flag.\nconst CONFIG_MODULE = 'entities'\nconst NEW_ENTITIES_RESTRICTED_KEY = 'newEntitiesRestrictedByDefault'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['entities.definitions.view'] },\n PUT: { requireAuth: true, requireFeatures: ['entities.definitions.manage'] },\n}\n\
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { beginEntitiesMutationGuard } from './definitions.mutation-guard'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { ModuleConfigService } from '../../configs/lib/module-config-service'\n\n// Tenant-scoped policy: when true, new custom entities are created with\n// `access_restricted = true` unless the create request explicitly sets the flag.\nconst CONFIG_MODULE = 'entities'\nconst NEW_ENTITIES_RESTRICTED_KEY = 'newEntitiesRestrictedByDefault'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['entities.definitions.view'] },\n PUT: { requireAuth: true, requireFeatures: ['entities.definitions.manage'] },\n}\n\nasync function readPolicy(tenantId: string | null): Promise<boolean> {\n try {\n const { resolve } = await createRequestContainer()\n const moduleConfigService = resolve('moduleConfigService') as ModuleConfigService\n const value = await moduleConfigService.getValue(CONFIG_MODULE, NEW_ENTITIES_RESTRICTED_KEY, {\n defaultValue: false,\n scope: { tenantId },\n })\n return value === true\n } catch {\n return false\n }\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const { resolve } = await createRequestContainer()\n const moduleConfigService = resolve('moduleConfigService') as ModuleConfigService\n const record = await moduleConfigService.getRecord(CONFIG_MODULE, NEW_ENTITIES_RESTRICTED_KEY, {\n tenantId: auth.tenantId ?? null,\n })\n return NextResponse.json({\n newEntitiesRestrictedByDefault: record ? record.value === true : false,\n updatedAt: record ? record.updatedAt : null,\n })\n}\n\nconst putBodySchema = z.object({\n newEntitiesRestrictedByDefault: z.boolean(),\n expectedUpdatedAt: z.string().optional(),\n})\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n let body: unknown\n try { body = await req.json() } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n const parsed = putBodySchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Validation failed', details: parsed.error.flatten() }, { status: 400 })\n }\n const container = await createRequestContainer()\n const { resolve } = container\n const moduleConfigService = resolve('moduleConfigService') as ModuleConfigService\n\n const currentRecord = await moduleConfigService.getRecord(\n CONFIG_MODULE,\n NEW_ENTITIES_RESTRICTED_KEY,\n { tenantId: auth.tenantId ?? null },\n )\n\n const guard = await beginEntitiesMutationGuard({\n container,\n auth,\n req,\n resourceKind: 'entities.settings',\n resourceId: NEW_ENTITIES_RESTRICTED_KEY,\n operation: currentRecord ? 'update' : 'create',\n mutationPayload: parsed.data as unknown as Record<string, unknown>,\n })\n if (guard.blockedResponse) return guard.blockedResponse\n\n const ifMatchHeader = req.headers.get('If-Match') || req.headers.get('x-om-ext-optimistic-lock-expected-updated-at')\n const expectedUpdatedAt = ifMatchHeader || parsed.data.expectedUpdatedAt\n\n try {\n await enforceCommandOptimisticLockWithGuards(container, {\n resourceKind: 'entities.settings',\n resourceId: NEW_ENTITIES_RESTRICTED_KEY,\n current: currentRecord ? currentRecord.updatedAt : null,\n expected: expectedUpdatedAt,\n request: req,\n })\n } catch (lockError) {\n if (isCrudHttpError(lockError)) {\n return NextResponse.json(lockError.body, { status: lockError.status })\n }\n throw lockError\n }\n\n const updatedRecord = await moduleConfigService.setValue(\n CONFIG_MODULE,\n NEW_ENTITIES_RESTRICTED_KEY,\n parsed.data.newEntitiesRestrictedByDefault,\n { tenantId: auth.tenantId ?? null },\n )\n\n await guard.runAfterSuccess()\n\n return NextResponse.json({\n ok: true,\n newEntitiesRestrictedByDefault: parsed.data.newEntitiesRestrictedByDefault,\n updatedAt: updatedRecord ? updatedRecord.updatedAt : null,\n })\n}\n\nconst settingsResponseSchema = z.object({\n newEntitiesRestrictedByDefault: z.boolean(),\n updatedAt: z.string().nullable().optional(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Entities',\n summary: 'Custom entity workspace settings',\n methods: {\n GET: {\n summary: 'Get custom entity settings',\n description: 'Returns the tenant-scoped default-restricted policy for new custom entities.',\n responses: [\n { status: 200, description: 'Current settings', schema: settingsResponseSchema },\n { status: 401, description: 'Missing authentication', schema: z.object({ error: z.string() }) },\n ],\n },\n PUT: {\n summary: 'Update custom entity settings',\n description: 'Sets the tenant-scoped default-restricted policy for new custom entities.',\n requestBody: { schema: putBodySchema },\n responses: [\n { status: 200, description: 'Updated settings', schema: z.object({ ok: z.boolean(), newEntitiesRestrictedByDefault: z.boolean(), updatedAt: z.string().nullable().optional() }) },\n { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Missing authentication', schema: z.object({ error: z.string() }) },\n { status: 409, description: 'Optimistic lock conflict', schema: z.object({ code: z.string(), error: z.string() }) },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Page, PageBody } from "@open-mercato/ui/backend/Page";
|
|
3
|
+
import { EntitySettingsManager } from "../../../components/EntitySettingsManager.js";
|
|
4
|
+
function EntitySettingsPage() {
|
|
5
|
+
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { className: "space-y-8", children: /* @__PURE__ */ jsx(EntitySettingsManager, {}) }) });
|
|
6
|
+
}
|
|
7
|
+
export {
|
|
8
|
+
EntitySettingsPage as default
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=page.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/entities/backend/config/settings/page.tsx"],
|
|
4
|
+
"sourcesContent": ["import { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { EntitySettingsManager } from '../../../components/EntitySettingsManager'\n\nexport default function EntitySettingsPage() {\n return (\n <Page>\n <PageBody className=\"space-y-8\">\n <EntitySettingsManager />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "AAOQ;AAPR,SAAS,MAAM,gBAAgB;AAC/B,SAAS,6BAA6B;AAEvB,SAAR,qBAAsC;AAC3C,SACE,oBAAC,QACC,8BAAC,YAAS,WAAU,aAClB,8BAAC,yBAAsB,GACzB,GACF;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
const settingsIcon = React.createElement(
|
|
3
|
+
"svg",
|
|
4
|
+
{
|
|
5
|
+
width: 16,
|
|
6
|
+
height: 16,
|
|
7
|
+
viewBox: "0 0 24 24",
|
|
8
|
+
fill: "none",
|
|
9
|
+
stroke: "currentColor",
|
|
10
|
+
strokeWidth: 2,
|
|
11
|
+
strokeLinecap: "round",
|
|
12
|
+
strokeLinejoin: "round"
|
|
13
|
+
},
|
|
14
|
+
React.createElement("circle", { cx: 12, cy: 12, r: 3 }),
|
|
15
|
+
React.createElement("path", {
|
|
16
|
+
d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"
|
|
17
|
+
})
|
|
18
|
+
);
|
|
19
|
+
const metadata = {
|
|
20
|
+
requireAuth: true,
|
|
21
|
+
requireFeatures: ["entities.definitions.manage"],
|
|
22
|
+
pageTitle: "Custom Entities",
|
|
23
|
+
pageTitleKey: "entities.settings.title",
|
|
24
|
+
pageGroup: "Module Configs",
|
|
25
|
+
pageGroupKey: "settings.sections.moduleConfigs",
|
|
26
|
+
pageOrder: 5,
|
|
27
|
+
icon: settingsIcon,
|
|
28
|
+
pageContext: "settings",
|
|
29
|
+
breadcrumb: [
|
|
30
|
+
{ label: "Custom Entities", labelKey: "entities.settings.title" }
|
|
31
|
+
]
|
|
32
|
+
};
|
|
33
|
+
export {
|
|
34
|
+
metadata
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=page.meta.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/entities/backend/config/settings/page.meta.ts"],
|
|
4
|
+
"sourcesContent": ["import React from 'react'\n\nconst settingsIcon = React.createElement(\n 'svg',\n {\n width: 16,\n height: 16,\n viewBox: '0 0 24 24',\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 2,\n strokeLinecap: 'round',\n strokeLinejoin: 'round',\n },\n React.createElement('circle', { cx: 12, cy: 12, r: 3 }),\n React.createElement('path', {\n d: 'M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z'\n })\n)\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['entities.definitions.manage'],\n pageTitle: 'Custom Entities',\n pageTitleKey: 'entities.settings.title',\n pageGroup: 'Module Configs',\n pageGroupKey: 'settings.sections.moduleConfigs',\n pageOrder: 5,\n icon: settingsIcon,\n pageContext: 'settings' as const,\n breadcrumb: [\n { label: 'Custom Entities', labelKey: 'entities.settings.title' },\n ],\n} as const\n"],
|
|
5
|
+
"mappings": "AAAA,OAAO,WAAW;AAElB,MAAM,eAAe,MAAM;AAAA,EACzB;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,eAAe;AAAA,IACf,gBAAgB;AAAA,EAClB;AAAA,EACA,MAAM,cAAc,UAAU,EAAE,IAAI,IAAI,IAAI,IAAI,GAAG,EAAE,CAAC;AAAA,EACtD,MAAM,cAAc,QAAQ;AAAA,IAC1B,GAAG;AAAA,EACL,CAAC;AACH;AAEO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,6BAA6B;AAAA,EAC/C,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,MAAM;AAAA,EACN,aAAa;AAAA,EACb,YAAY;AAAA,IACV,EAAE,OAAO,mBAAmB,UAAU,0BAA0B;AAAA,EAClE;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -10,6 +10,7 @@ import { useRouter } from "next/navigation";
|
|
|
10
10
|
import { pushWithFlash } from "@open-mercato/ui/backend/utils/flash";
|
|
11
11
|
import { readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
|
|
12
12
|
import { LoadingMessage } from "@open-mercato/ui/backend/detail";
|
|
13
|
+
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
13
14
|
const schema = upsertCustomEntitySchema;
|
|
14
15
|
import { Page, PageBody } from "@open-mercato/ui/backend/Page";
|
|
15
16
|
function CreateEntityPage() {
|
|
@@ -19,17 +20,25 @@ function CreateEntityPage() {
|
|
|
19
20
|
React.useEffect(() => {
|
|
20
21
|
let active = true;
|
|
21
22
|
(async () => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
try {
|
|
24
|
+
const data = await readApiResultOrThrow(
|
|
25
|
+
"/api/entities/entity-settings",
|
|
26
|
+
void 0,
|
|
27
|
+
{
|
|
28
|
+
errorMessage: t("entities.userEntities.errors.settingsLoadFailed", "Could not load default restriction policy; using default"),
|
|
29
|
+
fallback: { newEntitiesRestrictedByDefault: false }
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
if (active) setDefaultRestricted(data?.newEntitiesRestrictedByDefault === true);
|
|
33
|
+
} catch {
|
|
34
|
+
flash(t("entities.userEntities.errors.settingsLoadFailed", "Could not load default restriction policy; using default"), "warning");
|
|
35
|
+
if (active) setDefaultRestricted(false);
|
|
36
|
+
}
|
|
28
37
|
})();
|
|
29
38
|
return () => {
|
|
30
39
|
active = false;
|
|
31
40
|
};
|
|
32
|
-
}, []);
|
|
41
|
+
}, [t]);
|
|
33
42
|
const fields = React.useMemo(() => [
|
|
34
43
|
{
|
|
35
44
|
id: "entityId",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/entities/backend/entities/user/create/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { CrudForm, type CrudField } from '@open-mercato/ui/backend/CrudForm'\nimport { createCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { upsertCustomEntitySchema } from '@open-mercato/core/modules/entities/data/validators'\nimport { useRouter } from 'next/navigation'\nimport { pushWithFlash } from '@open-mercato/ui/backend/utils/flash'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { LoadingMessage } from '@open-mercato/ui/backend/detail'\n\nconst schema = upsertCustomEntitySchema\n\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\n\nexport default function CreateEntityPage() {\n const t = useT()\n const router = useRouter()\n // Pre-fill the restriction toggle from the tenant default-restricted policy so\n // the form reflects the workspace posture; null while loading.\n const [defaultRestricted, setDefaultRestricted] = React.useState<boolean | null>(null)\n React.useEffect(() => {\n let active = true\n ;(async () => {\n const data = await readApiResultOrThrow<{ newEntitiesRestrictedByDefault?: boolean }>(\n
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { CrudForm, type CrudField } from '@open-mercato/ui/backend/CrudForm'\nimport { createCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { upsertCustomEntitySchema } from '@open-mercato/core/modules/entities/data/validators'\nimport { useRouter } from 'next/navigation'\nimport { pushWithFlash } from '@open-mercato/ui/backend/utils/flash'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\n\nconst schema = upsertCustomEntitySchema\n\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\n\nexport default function CreateEntityPage() {\n const t = useT()\n const router = useRouter()\n // Pre-fill the restriction toggle from the tenant default-restricted policy so\n // the form reflects the workspace posture; null while loading.\n const [defaultRestricted, setDefaultRestricted] = React.useState<boolean | null>(null)\n React.useEffect(() => {\n let active = true\n ;(async () => {\n try {\n const data = await readApiResultOrThrow<{ newEntitiesRestrictedByDefault?: boolean }>(\n '/api/entities/entity-settings',\n undefined,\n {\n errorMessage: t('entities.userEntities.errors.settingsLoadFailed', 'Could not load default restriction policy; using default'),\n fallback: { newEntitiesRestrictedByDefault: false },\n },\n )\n if (active) setDefaultRestricted(data?.newEntitiesRestrictedByDefault === true)\n } catch {\n flash(t('entities.userEntities.errors.settingsLoadFailed', 'Could not load default restriction policy; using default'), 'warning')\n if (active) setDefaultRestricted(false)\n }\n })()\n return () => { active = false }\n }, [t])\n const fields = React.useMemo<CrudField[]>(() => ([\n {\n id: 'entityId',\n label: t('entities.userEntities.form.entityId.label', 'Entity ID'),\n type: 'text',\n required: true,\n placeholder: t('entities.userEntities.form.entityId.placeholder', 'module_name:entity_id'),\n },\n { id: 'label', label: t('entities.userEntities.form.label.label', 'Label'), type: 'text', required: true },\n { id: 'description', label: t('entities.userEntities.form.description.label', 'Description'), type: 'textarea' },\n {\n id: 'defaultEditor',\n label: t('entities.userEntities.form.defaultEditor.label', 'Default Editor (multiline)'),\n type: 'select',\n options: [\n { value: '', label: t('entities.userEntities.form.defaultEditor.options.default', 'Default (Markdown)') },\n { value: 'markdown', label: t('entities.userEntities.form.defaultEditor.options.markdown', 'Markdown (UIW)') },\n { value: 'simpleMarkdown', label: t('entities.userEntities.form.defaultEditor.options.simpleMarkdown', 'Simple Markdown') },\n { value: 'htmlRichText', label: t('entities.userEntities.form.defaultEditor.options.htmlRichText', 'HTML Rich Text') },\n { value: 'plain', label: t('entities.userEntities.form.defaultEditor.options.plain', 'Plain textarea') },\n ],\n } as unknown as CrudField,\n { id: 'showInSidebar', label: t('entities.userEntities.form.showInSidebar.label', 'Show in sidebar'), type: 'checkbox' } as CrudField,\n {\n id: 'accessRestricted',\n label: t('entities.userEntities.form.accessRestricted.label', 'Restrict record access'),\n type: 'checkbox',\n description: t('entities.userEntities.form.accessRestricted.help', 'Require an explicit per-entity permission to view or manage this entity\u2019s records. Leave off to allow anyone with the general records permission.'),\n } as CrudField,\n ]), [t])\n\n if (defaultRestricted === null) {\n return (\n <Page>\n <PageBody>\n <LoadingMessage label={t('entities.userEntities.form.loading', 'Loading\u2026')} />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm\n title={t('entities.userEntities.form.title', 'Create Entity')}\n backHref=\"/backend/entities/user\"\n schema={schema}\n fields={fields}\n initialValues={{ entityId: 'user:your_entity', label: '', showInSidebar: false, accessRestricted: defaultRestricted }}\n submitLabel={t('entities.userEntities.form.submit', 'Create')}\n cancelHref=\"/backend/entities/user\"\n onSubmit={async (vals) => {\n const entityId = await submitCreateEntity({\n values: vals as Record<string, unknown>,\n messages: {\n entityIdRequired: t('entities.userEntities.errors.entityIdRequired', 'Entity ID is required'),\n entityIdExists: t('entities.userEntities.errors.entityIdExists', 'Entity ID already exists'),\n loadFailed: t('entities.userEntities.errors.loadFailed', 'Failed to load entities'),\n },\n })\n try {\n window.dispatchEvent(new Event('om:refresh-sidebar'))\n } catch {}\n const successMessage = t('entities.userEntities.flash.created', 'Entity created')\n pushWithFlash(router, `/backend/entities/user/${encodeURIComponent(entityId)}`, successMessage, 'success')\n }}\n />\n </PageBody>\n </Page>\n )\n}\n\ntype EntityListEntry = {\n entityId?: string\n source?: string\n}\n\ntype FetchCustomEntities = (errorMessage?: string) => Promise<EntityListEntry[]>\n\nasync function defaultFetchCustomEntities(errorMessage?: string): Promise<EntityListEntry[]> {\n const data = await readApiResultOrThrow<{ items?: EntityListEntry[] }>(\n '/api/entities/entities',\n undefined,\n { errorMessage: errorMessage ?? 'Failed to load entities', fallback: { items: [] } },\n )\n return Array.isArray(data?.items) ? data.items! : []\n}\n\ntype CreateEntityRequest = (payload: Record<string, unknown>) => Promise<void>\n\nasync function defaultCreateEntityRequest(payload: Record<string, unknown>) {\n await createCrud('entities/entities', payload)\n}\n\nexport async function submitCreateEntity(options: {\n values: Record<string, unknown>\n fetchEntities?: FetchCustomEntities\n createEntity?: CreateEntityRequest\n messages?: {\n entityIdRequired?: string\n entityIdExists?: string\n loadFailed?: string\n }\n}): Promise<string> {\n const {\n values,\n fetchEntities = defaultFetchCustomEntities,\n createEntity = defaultCreateEntityRequest,\n messages,\n } = options\n const rawEntityId = typeof values.entityId === 'string' ? values.entityId.trim() : ''\n if (!rawEntityId) {\n const message = messages?.entityIdRequired ?? 'Entity ID is required'\n throw createCrudFormError(message, { entityId: message })\n }\n\n const existing = await fetchEntities(messages?.loadFailed)\n const exists = existing.some(\n (entry) => entry?.entityId === rawEntityId && (entry?.source === 'custom' || entry?.source === undefined),\n )\n if (exists) {\n const message = messages?.entityIdExists ?? 'Entity ID already exists'\n throw createCrudFormError(message, { entityId: message })\n }\n\n const payload: Record<string, unknown> = {\n ...values,\n entityId: rawEntityId,\n labelField: 'name',\n defaultEditor:\n typeof (values as Record<string, unknown>).defaultEditor === 'string' &&\n (values as Record<string, unknown>).defaultEditor\n ? (values as Record<string, unknown>).defaultEditor\n : undefined,\n }\n\n await createEntity(payload)\n return rawEntityId\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Button } from "@open-mercato/ui/primitives/button";
|
|
5
|
+
import { Checkbox } from "@open-mercato/ui/primitives/checkbox";
|
|
6
|
+
import { Label } from "@open-mercato/ui/primitives/label";
|
|
7
|
+
import { Spinner } from "@open-mercato/ui/primitives/spinner";
|
|
8
|
+
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
9
|
+
import { apiCallOrThrow, readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
|
|
10
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
11
|
+
import { ErrorMessage } from "@open-mercato/ui/backend/detail";
|
|
12
|
+
import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
|
|
13
|
+
import { buildOptimisticLockHeader, extractOptimisticLockConflict } from "@open-mercato/ui/backend/utils/optimisticLock";
|
|
14
|
+
import { withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
|
|
15
|
+
function EntitySettingsManager() {
|
|
16
|
+
const t = useT();
|
|
17
|
+
const [loading, setLoading] = React.useState(true);
|
|
18
|
+
const [loadError, setLoadError] = React.useState(false);
|
|
19
|
+
const [saving, setSaving] = React.useState(false);
|
|
20
|
+
const [restricted, setRestricted] = React.useState(false);
|
|
21
|
+
const [updatedAt, setUpdatedAt] = React.useState(null);
|
|
22
|
+
const { runMutation } = useGuardedMutation({
|
|
23
|
+
contextId: "custom-entities-settings"
|
|
24
|
+
});
|
|
25
|
+
const loadSettings = React.useCallback(async () => {
|
|
26
|
+
setLoading(true);
|
|
27
|
+
setLoadError(false);
|
|
28
|
+
try {
|
|
29
|
+
const data = await readApiResultOrThrow(
|
|
30
|
+
"/api/entities/entity-settings",
|
|
31
|
+
void 0,
|
|
32
|
+
{
|
|
33
|
+
errorMessage: t("entities.settings.errors.loadFailed", "Failed to load settings")
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
setRestricted(data.newEntitiesRestrictedByDefault === true);
|
|
37
|
+
setUpdatedAt(data.updatedAt ?? null);
|
|
38
|
+
} catch {
|
|
39
|
+
setLoadError(true);
|
|
40
|
+
} finally {
|
|
41
|
+
setLoading(false);
|
|
42
|
+
}
|
|
43
|
+
}, [t]);
|
|
44
|
+
React.useEffect(() => {
|
|
45
|
+
void loadSettings();
|
|
46
|
+
}, [loadSettings]);
|
|
47
|
+
const handleSubmit = async (e) => {
|
|
48
|
+
e.preventDefault();
|
|
49
|
+
setSaving(true);
|
|
50
|
+
try {
|
|
51
|
+
const headers = buildOptimisticLockHeader(updatedAt);
|
|
52
|
+
await runMutation({
|
|
53
|
+
operation: () => withScopedApiRequestHeaders(headers, async () => {
|
|
54
|
+
const res = await apiCallOrThrow(
|
|
55
|
+
"/api/entities/entity-settings",
|
|
56
|
+
{
|
|
57
|
+
method: "PUT",
|
|
58
|
+
headers: { "content-type": "application/json" },
|
|
59
|
+
body: JSON.stringify({
|
|
60
|
+
newEntitiesRestrictedByDefault: restricted,
|
|
61
|
+
expectedUpdatedAt: updatedAt ?? void 0
|
|
62
|
+
})
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
errorMessage: t("entities.settings.errors.saveFailed", "Failed to save settings")
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
if (res.ok && res.result) {
|
|
69
|
+
setUpdatedAt(res.result.updatedAt);
|
|
70
|
+
flash(t("entities.settings.flash.saved", "Settings saved"), "success");
|
|
71
|
+
}
|
|
72
|
+
return res;
|
|
73
|
+
}),
|
|
74
|
+
context: {},
|
|
75
|
+
mutationPayload: {
|
|
76
|
+
newEntitiesRestrictedByDefault: restricted
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
} catch (err) {
|
|
80
|
+
const conflict = extractOptimisticLockConflict(err);
|
|
81
|
+
if (conflict) {
|
|
82
|
+
setLoading(true);
|
|
83
|
+
await loadSettings();
|
|
84
|
+
} else {
|
|
85
|
+
flash(t("entities.settings.errors.saveFailed", "Failed to save settings"), "error");
|
|
86
|
+
}
|
|
87
|
+
} finally {
|
|
88
|
+
setSaving(false);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
if (loading) {
|
|
92
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
93
|
+
/* @__PURE__ */ jsx(Spinner, { className: "h-4 w-4" }),
|
|
94
|
+
/* @__PURE__ */ jsx("span", { children: t("entities.userEntities.form.loading", "Loading\u2026") })
|
|
95
|
+
] });
|
|
96
|
+
}
|
|
97
|
+
if (loadError) {
|
|
98
|
+
return /* @__PURE__ */ jsx(
|
|
99
|
+
ErrorMessage,
|
|
100
|
+
{
|
|
101
|
+
label: t("entities.settings.errors.loadFailed", "Failed to load settings"),
|
|
102
|
+
action: /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => void loadSettings(), children: t("common.retry", "Retry") })
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return /* @__PURE__ */ jsxs("form", { onSubmit: (e) => void handleSubmit(e), className: "space-y-6 max-w-2xl", children: [
|
|
107
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
108
|
+
/* @__PURE__ */ jsx("h3", { className: "text-base font-semibold", children: t("entities.settings.title", "Custom Entities") }),
|
|
109
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("entities.settings.description", "Manage global custom entity configurations.") })
|
|
110
|
+
] }),
|
|
111
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-4 space-y-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
112
|
+
/* @__PURE__ */ jsx(
|
|
113
|
+
Checkbox,
|
|
114
|
+
{
|
|
115
|
+
id: "newEntitiesRestrictedByDefault",
|
|
116
|
+
checked: restricted,
|
|
117
|
+
onCheckedChange: (checked) => setRestricted(checked === true),
|
|
118
|
+
disabled: saving,
|
|
119
|
+
className: "mt-1"
|
|
120
|
+
}
|
|
121
|
+
),
|
|
122
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
123
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "newEntitiesRestrictedByDefault", className: "font-medium cursor-pointer", children: t("entities.settings.newEntitiesRestrictedByDefault.label", "Restrict record access by default") }),
|
|
124
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("entities.settings.newEntitiesRestrictedByDefault.description", "Require explicit per-entity permissions for records of newly created custom entities by default.") })
|
|
125
|
+
] })
|
|
126
|
+
] }) }),
|
|
127
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-end gap-3 border-t pt-4", children: /* @__PURE__ */ jsxs(Button, { type: "submit", disabled: saving, children: [
|
|
128
|
+
saving ? /* @__PURE__ */ jsx(Spinner, { className: "mr-2 h-4 w-4" }) : null,
|
|
129
|
+
t("common.save", "Save")
|
|
130
|
+
] }) })
|
|
131
|
+
] });
|
|
132
|
+
}
|
|
133
|
+
export {
|
|
134
|
+
EntitySettingsManager
|
|
135
|
+
};
|
|
136
|
+
//# sourceMappingURL=EntitySettingsManager.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/entities/components/EntitySettingsManager.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall, apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { buildOptimisticLockHeader, extractOptimisticLockConflict } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\n\nexport function EntitySettingsManager(): React.ReactElement {\n const t = useT()\n const [loading, setLoading] = React.useState(true)\n const [loadError, setLoadError] = React.useState(false)\n const [saving, setSaving] = React.useState(false)\n const [restricted, setRestricted] = React.useState(false)\n const [updatedAt, setUpdatedAt] = React.useState<string | null>(null)\n\n const { runMutation } = useGuardedMutation({\n contextId: 'custom-entities-settings',\n })\n\n const loadSettings = React.useCallback(async () => {\n setLoading(true)\n setLoadError(false)\n try {\n const data = await readApiResultOrThrow<{ newEntitiesRestrictedByDefault?: boolean; updatedAt?: string | null }>(\n '/api/entities/entity-settings',\n undefined,\n {\n errorMessage: t('entities.settings.errors.loadFailed', 'Failed to load settings'),\n }\n )\n setRestricted(data.newEntitiesRestrictedByDefault === true)\n setUpdatedAt(data.updatedAt ?? null)\n } catch {\n setLoadError(true)\n } finally {\n setLoading(false)\n }\n }, [t])\n\n React.useEffect(() => {\n void loadSettings()\n }, [loadSettings])\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault()\n setSaving(true)\n try {\n const headers = buildOptimisticLockHeader(updatedAt)\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(headers, async () => {\n const res = await apiCallOrThrow<{ ok: boolean; newEntitiesRestrictedByDefault: boolean; updatedAt: string | null }>(\n '/api/entities/entity-settings',\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n newEntitiesRestrictedByDefault: restricted,\n expectedUpdatedAt: updatedAt ?? undefined,\n }),\n },\n {\n errorMessage: t('entities.settings.errors.saveFailed', 'Failed to save settings'),\n }\n )\n\n if (res.ok && res.result) {\n setUpdatedAt(res.result.updatedAt)\n flash(t('entities.settings.flash.saved', 'Settings saved'), 'success')\n }\n return res\n }),\n context: {},\n mutationPayload: {\n newEntitiesRestrictedByDefault: restricted,\n },\n })\n } catch (err) {\n const conflict = extractOptimisticLockConflict(err)\n if (conflict) {\n setLoading(true)\n await loadSettings()\n } else {\n flash(t('entities.settings.errors.saveFailed', 'Failed to save settings'), 'error')\n }\n } finally {\n setSaving(false)\n }\n }\n\n if (loading) {\n return (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner className=\"h-4 w-4\" />\n <span>{t('entities.userEntities.form.loading', 'Loading\u2026')}</span>\n </div>\n )\n }\n\n if (loadError) {\n return (\n <ErrorMessage\n label={t('entities.settings.errors.loadFailed', 'Failed to load settings')}\n action={(\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void loadSettings()}>\n {t('common.retry', 'Retry')}\n </Button>\n )}\n />\n )\n }\n\n return (\n <form onSubmit={(e) => void handleSubmit(e)} className=\"space-y-6 max-w-2xl\">\n <div className=\"space-y-1\">\n <h3 className=\"text-base font-semibold\">\n {t('entities.settings.title', 'Custom Entities')}\n </h3>\n <p className=\"text-sm text-muted-foreground\">\n {t('entities.settings.description', 'Manage global custom entity configurations.')}\n </p>\n </div>\n\n <div className=\"rounded-lg border bg-card p-4 space-y-4\">\n <div className=\"flex items-start gap-3\">\n <Checkbox\n id=\"newEntitiesRestrictedByDefault\"\n checked={restricted}\n onCheckedChange={(checked) => setRestricted(checked === true)}\n disabled={saving}\n className=\"mt-1\"\n />\n <div className=\"space-y-1\">\n <Label htmlFor=\"newEntitiesRestrictedByDefault\" className=\"font-medium cursor-pointer\">\n {t('entities.settings.newEntitiesRestrictedByDefault.label', 'Restrict record access by default')}\n </Label>\n <p className=\"text-sm text-muted-foreground\">\n {t('entities.settings.newEntitiesRestrictedByDefault.description', 'Require explicit per-entity permissions for records of newly created custom entities by default.')}\n </p>\n </div>\n </div>\n </div>\n\n <div className=\"flex items-center justify-end gap-3 border-t pt-4\">\n <Button type=\"submit\" disabled={saving}>\n {saving ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {t('common.save', 'Save')}\n </Button>\n </div>\n </form>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -10,11 +10,19 @@ import { parseInboundEmail } from "../../lib/emailParser.js";
|
|
|
10
10
|
import { checkRateLimit } from "../../lib/rateLimiter.js";
|
|
11
11
|
import { emitInboxOpsEvent } from "../../events.js";
|
|
12
12
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
13
|
+
import {
|
|
14
|
+
readBoundedRequestBody,
|
|
15
|
+
resolveWebhookBodyLimitBytes,
|
|
16
|
+
WebhookBodyTooLargeError
|
|
17
|
+
} from "@open-mercato/shared/lib/webhooks";
|
|
13
18
|
const logger = createLogger("inbox_ops").child({ component: "webhook" });
|
|
14
19
|
const metadata = {
|
|
15
20
|
POST: { requireAuth: false }
|
|
16
21
|
};
|
|
17
|
-
const MAX_PAYLOAD_SIZE =
|
|
22
|
+
const MAX_PAYLOAD_SIZE = resolveWebhookBodyLimitBytes(
|
|
23
|
+
process.env.INBOX_OPS_WEBHOOK_MAX_BODY_BYTES ?? process.env.OM_WEBHOOK_MAX_BODY_BYTES,
|
|
24
|
+
2 * 1024 * 1024
|
|
25
|
+
);
|
|
18
26
|
const REPLAY_WINDOW_MS = 5 * 60 * 1e3;
|
|
19
27
|
function isTimestampFresh(timestamp) {
|
|
20
28
|
if (!timestamp) return false;
|
|
@@ -140,19 +148,15 @@ async function POST(req) {
|
|
|
140
148
|
logger.error("Neither INBOX_OPS_WEBHOOK_SECRET nor RESEND_WEBHOOK_SIGNING_SECRET is configured");
|
|
141
149
|
return NextResponse.json({ error: "Service unavailable" }, { status: 503 });
|
|
142
150
|
}
|
|
143
|
-
const contentLength = parseInt(req.headers.get("content-length") || "0", 10);
|
|
144
|
-
if (contentLength > MAX_PAYLOAD_SIZE) {
|
|
145
|
-
return NextResponse.json({ error: "Payload too large" }, { status: 413 });
|
|
146
|
-
}
|
|
147
151
|
let rawBody;
|
|
148
152
|
try {
|
|
149
|
-
rawBody = await req
|
|
150
|
-
} catch {
|
|
153
|
+
rawBody = await readBoundedRequestBody(req, { maxBytes: MAX_PAYLOAD_SIZE });
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if (error instanceof WebhookBodyTooLargeError) {
|
|
156
|
+
return NextResponse.json({ error: "Payload too large" }, { status: 413 });
|
|
157
|
+
}
|
|
151
158
|
return NextResponse.json({ error: "Bad request" }, { status: 400 });
|
|
152
159
|
}
|
|
153
|
-
if (rawBody.length > MAX_PAYLOAD_SIZE) {
|
|
154
|
-
return NextResponse.json({ error: "Payload too large" }, { status: 413 });
|
|
155
|
-
}
|
|
156
160
|
const verified = await verifyAndParse(req, rawBody);
|
|
157
161
|
if (!verified.ok) {
|
|
158
162
|
return verified.response;
|