@porulle/core 0.11.0 → 0.14.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/dist/auth/actor.d.ts +10 -0
- package/dist/auth/actor.d.ts.map +1 -0
- package/dist/auth/actor.js +91 -0
- package/dist/auth/auth-schema-guard.d.ts +2444 -0
- package/dist/auth/auth-schema-guard.d.ts.map +1 -0
- package/dist/auth/auth-schema-guard.js +113 -0
- package/dist/auth/auth-schema.d.ts +228 -0
- package/dist/auth/auth-schema.d.ts.map +1 -1
- package/dist/auth/auth-schema.js +24 -2
- package/dist/auth/middleware.d.ts +1 -0
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +16 -92
- package/dist/auth/org.d.ts +8 -2
- package/dist/auth/org.d.ts.map +1 -1
- package/dist/auth/org.js +12 -4
- package/dist/auth/organization-guard.d.ts +5 -0
- package/dist/auth/organization-guard.d.ts.map +1 -0
- package/dist/auth/organization-guard.js +124 -0
- package/dist/auth/permissions.d.ts +10 -0
- package/dist/auth/permissions.d.ts.map +1 -1
- package/dist/auth/permissions.js +31 -7
- package/dist/auth/role-authority.d.ts +44 -0
- package/dist/auth/role-authority.d.ts.map +1 -0
- package/dist/auth/role-authority.js +85 -0
- package/dist/auth/setup.d.ts.map +1 -1
- package/dist/auth/setup.js +19 -1
- package/dist/auth/strict-org-resolution.d.ts.map +1 -1
- package/dist/auth/strict-org-resolution.js +9 -1
- package/dist/auth/types.d.ts +1 -1
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +2 -0
- package/dist/config/types.d.ts +32 -4
- package/dist/config/types.d.ts.map +1 -1
- package/dist/hooks/checkout.d.ts.map +1 -1
- package/dist/hooks/checkout.js +10 -9
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/interfaces/rest/audit-middleware.d.ts.map +1 -1
- package/dist/interfaces/rest/audit-middleware.js +2 -0
- package/dist/interfaces/rest/customer-portal.d.ts.map +1 -1
- package/dist/interfaces/rest/customer-portal.js +9 -8
- package/dist/interfaces/rest/route-coverage.d.ts +14 -0
- package/dist/interfaces/rest/route-coverage.d.ts.map +1 -0
- package/dist/interfaces/rest/route-coverage.js +81 -0
- package/dist/interfaces/rest/router.d.ts +7 -2
- package/dist/interfaces/rest/router.d.ts.map +1 -1
- package/dist/interfaces/rest/router.js +8 -4
- package/dist/interfaces/rest/routes/admin/compensation-failures.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/admin/compensation-failures.js +7 -5
- package/dist/interfaces/rest/routes/admin/custom-field-proposals.js +2 -2
- package/dist/interfaces/rest/routes/admin/permissions.d.ts +1 -1
- package/dist/interfaces/rest/routes/admin/permissions.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/admin/permissions.js +2 -0
- package/dist/interfaces/rest/routes/admin/staff.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/admin/staff.js +126 -66
- package/dist/interfaces/rest/routes/admin-jobs.js +3 -3
- package/dist/interfaces/rest/routes/audit.js +3 -3
- package/dist/interfaces/rest/routes/carts.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/carts.js +13 -10
- package/dist/interfaces/rest/routes/catalog.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/catalog.js +10 -2
- package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/checkout.js +30 -35
- package/dist/interfaces/rest/routes/documents.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/documents.js +10 -5
- package/dist/interfaces/rest/routes/inventory.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/inventory.js +2 -0
- package/dist/interfaces/rest/routes/media.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/media.js +7 -3
- package/dist/interfaces/rest/routes/orders.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/orders.js +26 -8
- package/dist/interfaces/rest/routes/pricing.js +2 -2
- package/dist/interfaces/rest/routes/promotions.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/promotions.js +13 -10
- package/dist/interfaces/rest/routes/search.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/search.js +2 -1
- package/dist/interfaces/rest/schemas/carts.d.ts +7 -0
- package/dist/interfaces/rest/schemas/carts.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/customer-portal.d.ts +35 -0
- package/dist/interfaces/rest/schemas/customer-portal.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/orders.d.ts +204 -0
- package/dist/interfaces/rest/schemas/orders.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/responses.d.ts +71 -0
- package/dist/interfaces/rest/schemas/responses.d.ts.map +1 -1
- package/dist/interfaces/rest/utils.d.ts +15 -0
- package/dist/interfaces/rest/utils.d.ts.map +1 -1
- package/dist/interfaces/rest/utils.js +45 -2
- package/dist/kernel/compensation/executor.js +2 -2
- package/dist/kernel/database/plugin-types.d.ts +11 -0
- package/dist/kernel/database/plugin-types.d.ts.map +1 -1
- package/dist/kernel/database/scoped-db.d.ts +12 -0
- package/dist/kernel/database/scoped-db.d.ts.map +1 -1
- package/dist/kernel/database/scoped-db.js +12 -0
- package/dist/kernel/database/tx-context.d.ts +8 -0
- package/dist/kernel/database/tx-context.d.ts.map +1 -1
- package/dist/kernel/database/tx-context.js +13 -0
- package/dist/kernel/hooks/create-context.d.ts +2 -0
- package/dist/kernel/hooks/create-context.d.ts.map +1 -1
- package/dist/kernel/hooks/create-context.js +1 -0
- package/dist/kernel/hooks/types.d.ts +2 -0
- package/dist/kernel/hooks/types.d.ts.map +1 -1
- package/dist/kernel/plugin/manifest.d.ts.map +1 -1
- package/dist/kernel/plugin/manifest.js +9 -5
- package/dist/modules/analytics/service.js +2 -2
- package/dist/modules/audit/service.js +3 -3
- package/dist/modules/cart/repository/index.d.ts +13 -7
- package/dist/modules/cart/repository/index.d.ts.map +1 -1
- package/dist/modules/cart/repository/index.js +16 -10
- package/dist/modules/cart/schema.d.ts +17 -0
- package/dist/modules/cart/schema.d.ts.map +1 -1
- package/dist/modules/cart/schema.js +4 -0
- package/dist/modules/cart/service.d.ts +22 -9
- package/dist/modules/cart/service.d.ts.map +1 -1
- package/dist/modules/cart/service.js +57 -33
- package/dist/modules/catalog/brand-service.js +7 -7
- package/dist/modules/catalog/category-service.js +7 -7
- package/dist/modules/catalog/entity-service.d.ts +14 -7
- package/dist/modules/catalog/entity-service.d.ts.map +1 -1
- package/dist/modules/catalog/entity-service.js +109 -31
- package/dist/modules/catalog/read-policy.d.ts +9 -0
- package/dist/modules/catalog/read-policy.d.ts.map +1 -0
- package/dist/modules/catalog/read-policy.js +8 -0
- package/dist/modules/catalog/repository/index.d.ts +3 -2
- package/dist/modules/catalog/repository/index.d.ts.map +1 -1
- package/dist/modules/catalog/repository/index.js +14 -4
- package/dist/modules/catalog/service.d.ts +15 -13
- package/dist/modules/catalog/service.d.ts.map +1 -1
- package/dist/modules/catalog/service.js +25 -18
- package/dist/modules/customers/module.d.ts.map +1 -1
- package/dist/modules/customers/module.js +1 -0
- package/dist/modules/customers/service.d.ts +2 -0
- package/dist/modules/customers/service.d.ts.map +1 -1
- package/dist/modules/customers/service.js +21 -20
- package/dist/modules/documents/module.d.ts.map +1 -1
- package/dist/modules/documents/module.js +1 -0
- package/dist/modules/documents/service.d.ts +18 -4
- package/dist/modules/documents/service.d.ts.map +1 -1
- package/dist/modules/documents/service.js +54 -16
- package/dist/modules/fulfillment/module.d.ts.map +1 -1
- package/dist/modules/fulfillment/module.js +1 -0
- package/dist/modules/fulfillment/service.d.ts +2 -0
- package/dist/modules/fulfillment/service.d.ts.map +1 -1
- package/dist/modules/fulfillment/service.js +15 -6
- package/dist/modules/inventory/service.d.ts.map +1 -1
- package/dist/modules/inventory/service.js +15 -14
- package/dist/modules/media/service.js +6 -6
- package/dist/modules/orders/guest-access.d.ts +27 -0
- package/dist/modules/orders/guest-access.d.ts.map +1 -0
- package/dist/modules/orders/guest-access.js +46 -0
- package/dist/modules/orders/module.d.ts.map +1 -1
- package/dist/modules/orders/module.js +1 -0
- package/dist/modules/orders/quote.d.ts.map +1 -1
- package/dist/modules/orders/quote.js +1 -0
- package/dist/modules/orders/repository/index.d.ts +1 -1
- package/dist/modules/orders/repository/index.d.ts.map +1 -1
- package/dist/modules/orders/repository/index.js +2 -2
- package/dist/modules/orders/schema.d.ts +17 -0
- package/dist/modules/orders/schema.d.ts.map +1 -1
- package/dist/modules/orders/schema.js +5 -3
- package/dist/modules/orders/service.d.ts +16 -7
- package/dist/modules/orders/service.d.ts.map +1 -1
- package/dist/modules/orders/service.js +184 -55
- package/dist/modules/pricing/module.d.ts.map +1 -1
- package/dist/modules/pricing/module.js +1 -0
- package/dist/modules/pricing/service.d.ts +2 -0
- package/dist/modules/pricing/service.d.ts.map +1 -1
- package/dist/modules/pricing/service.js +12 -11
- package/dist/modules/promotions/module.d.ts.map +1 -1
- package/dist/modules/promotions/module.js +1 -0
- package/dist/modules/promotions/service.d.ts +3 -0
- package/dist/modules/promotions/service.d.ts.map +1 -1
- package/dist/modules/promotions/service.js +19 -11
- package/dist/modules/search/adapter.d.ts +11 -0
- package/dist/modules/search/adapter.d.ts.map +1 -1
- package/dist/modules/search/module.d.ts.map +1 -1
- package/dist/modules/search/module.js +4 -1
- package/dist/modules/search/service.d.ts +1 -0
- package/dist/modules/search/service.d.ts.map +1 -1
- package/dist/modules/search/service.js +57 -6
- package/dist/modules/settings/module.d.ts.map +1 -1
- package/dist/modules/settings/module.js +1 -0
- package/dist/modules/settings/service.d.ts +3 -0
- package/dist/modules/settings/service.d.ts.map +1 -1
- package/dist/modules/settings/service.js +6 -4
- package/dist/modules/shipping/service.js +9 -9
- package/dist/modules/tax/module.d.ts.map +1 -1
- package/dist/modules/tax/module.js +1 -0
- package/dist/modules/tax/service.d.ts +3 -0
- package/dist/modules/tax/service.d.ts.map +1 -1
- package/dist/modules/tax/service.js +11 -9
- package/dist/modules/webhooks/hook.js +2 -2
- package/dist/modules/webhooks/module.d.ts.map +1 -1
- package/dist/modules/webhooks/module.js +1 -0
- package/dist/modules/webhooks/service.d.ts +2 -0
- package/dist/modules/webhooks/service.d.ts.map +1 -1
- package/dist/modules/webhooks/service.js +4 -4
- package/dist/runtime/server.d.ts.map +1 -1
- package/dist/runtime/server.js +28 -2
- package/dist/test-utils/create-pglite-adapter.d.ts.map +1 -1
- package/dist/test-utils/create-pglite-adapter.js +35 -13
- package/dist/test-utils/create-test-config.d.ts.map +1 -1
- package/dist/test-utils/create-test-config.js +4 -0
- package/dist/test-utils/rest-api-test-utils.d.ts.map +1 -1
- package/dist/test-utils/rest-api-test-utils.js +2 -0
- package/dist/test-utils/test-actors.js +2 -2
- package/dist/utils/id.d.ts +2 -0
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +11 -0
- package/package.json +6 -5
- package/src/auth/actor.ts +110 -0
- package/src/auth/auth-schema-guard.ts +143 -0
- package/src/auth/auth-schema.ts +50 -19
- package/src/auth/middleware.ts +25 -101
- package/src/auth/org.ts +15 -3
- package/src/auth/organization-guard.ts +158 -0
- package/src/auth/permissions.ts +31 -8
- package/src/auth/role-authority.ts +107 -0
- package/src/auth/setup.ts +19 -1
- package/src/auth/strict-org-resolution.ts +7 -1
- package/src/auth/types.ts +1 -1
- package/src/config/defaults.ts +2 -0
- package/src/config/types.ts +31 -4
- package/src/hooks/checkout.ts +19 -12
- package/src/index.ts +21 -6
- package/src/interfaces/rest/audit-middleware.ts +2 -0
- package/src/interfaces/rest/customer-portal.ts +31 -24
- package/src/interfaces/rest/route-coverage.ts +103 -0
- package/src/interfaces/rest/router.ts +16 -10
- package/src/interfaces/rest/routes/admin/compensation-failures.ts +8 -5
- package/src/interfaces/rest/routes/admin/custom-field-proposals.ts +2 -2
- package/src/interfaces/rest/routes/admin/permissions.ts +3 -1
- package/src/interfaces/rest/routes/admin/staff.ts +161 -72
- package/src/interfaces/rest/routes/admin-jobs.ts +3 -3
- package/src/interfaces/rest/routes/audit.ts +3 -3
- package/src/interfaces/rest/routes/carts.ts +17 -7
- package/src/interfaces/rest/routes/catalog.ts +11 -0
- package/src/interfaces/rest/routes/checkout.ts +41 -38
- package/src/interfaces/rest/routes/documents.ts +15 -1
- package/src/interfaces/rest/routes/inventory.ts +2 -0
- package/src/interfaces/rest/routes/media.ts +8 -3
- package/src/interfaces/rest/routes/orders.ts +27 -8
- package/src/interfaces/rest/routes/pricing.ts +2 -2
- package/src/interfaces/rest/routes/promotions.ts +12 -9
- package/src/interfaces/rest/routes/search.ts +3 -1
- package/src/interfaces/rest/utils.ts +61 -2
- package/src/kernel/compensation/executor.ts +2 -2
- package/src/kernel/database/plugin-types.ts +11 -0
- package/src/kernel/database/scoped-db.ts +12 -0
- package/src/kernel/database/tx-context.ts +25 -0
- package/src/kernel/hooks/create-context.ts +3 -0
- package/src/kernel/hooks/types.ts +2 -0
- package/src/kernel/plugin/manifest.ts +9 -5
- package/src/modules/analytics/service.ts +2 -2
- package/src/modules/audit/service.ts +3 -3
- package/src/modules/cart/repository/index.ts +22 -8
- package/src/modules/cart/schema.ts +4 -0
- package/src/modules/cart/service.ts +62 -27
- package/src/modules/catalog/brand-service.ts +7 -7
- package/src/modules/catalog/category-service.ts +7 -7
- package/src/modules/catalog/entity-service.ts +117 -35
- package/src/modules/catalog/read-policy.ts +16 -0
- package/src/modules/catalog/repository/index.ts +19 -3
- package/src/modules/catalog/service.ts +46 -31
- package/src/modules/customers/module.ts +2 -0
- package/src/modules/customers/service.ts +23 -22
- package/src/modules/documents/module.ts +2 -0
- package/src/modules/documents/service.ts +74 -11
- package/src/modules/fulfillment/module.ts +2 -0
- package/src/modules/fulfillment/service.ts +17 -6
- package/src/modules/inventory/service.ts +15 -24
- package/src/modules/media/service.ts +6 -6
- package/src/modules/orders/guest-access.ts +55 -0
- package/src/modules/orders/module.ts +1 -0
- package/src/modules/orders/quote.ts +1 -0
- package/src/modules/orders/repository/index.ts +2 -0
- package/src/modules/orders/schema.ts +5 -3
- package/src/modules/orders/service.ts +302 -52
- package/src/modules/pricing/module.ts +2 -0
- package/src/modules/pricing/service.ts +14 -16
- package/src/modules/promotions/module.ts +2 -0
- package/src/modules/promotions/service.ts +25 -16
- package/src/modules/search/adapter.ts +9 -0
- package/src/modules/search/module.ts +5 -1
- package/src/modules/search/service.ts +55 -6
- package/src/modules/settings/module.ts +2 -0
- package/src/modules/settings/service.ts +8 -4
- package/src/modules/shipping/service.ts +9 -9
- package/src/modules/tax/module.ts +1 -0
- package/src/modules/tax/service.ts +13 -9
- package/src/modules/webhooks/hook.ts +2 -2
- package/src/modules/webhooks/module.ts +2 -0
- package/src/modules/webhooks/service.ts +6 -4
- package/src/runtime/server.ts +41 -2
- package/src/test-utils/create-pglite-adapter.ts +34 -12
- package/src/test-utils/create-test-config.ts +10 -6
- package/src/test-utils/rest-api-test-utils.ts +2 -0
- package/src/test-utils/test-actors.ts +2 -2
- package/src/utils/id.ts +20 -0
|
@@ -2,14 +2,14 @@ import { OpenAPIHono } from "@hono/zod-openapi";
|
|
|
2
2
|
import type { Kernel } from "../../../../runtime/kernel.js";
|
|
3
3
|
import type { Actor } from "../../../../auth/types.js";
|
|
4
4
|
import { assertPermission } from "../../../../auth/permissions.js";
|
|
5
|
-
import {
|
|
5
|
+
import { resolveOrgIdForCommerce } from "../../../../auth/org.js";
|
|
6
6
|
import { CommerceValidationError } from "../../../../kernel/errors.js";
|
|
7
7
|
import type { CompensationFailure } from "../../../../kernel/compensation/schema.js";
|
|
8
8
|
import {
|
|
9
9
|
listCompensationFailuresRoute,
|
|
10
10
|
resolveCompensationFailureRoute,
|
|
11
11
|
} from "../../schemas/compensation-failures.js";
|
|
12
|
-
import { type AppEnv, mapErrorToResponse, mapErrorToStatus } from "../../utils.js";
|
|
12
|
+
import { type AppEnv, mapErrorToResponse, mapErrorToStatus, requirePerm } from "../../utils.js";
|
|
13
13
|
|
|
14
14
|
function toIso(d: Date | null | undefined): string | null {
|
|
15
15
|
if (d == null) return null;
|
|
@@ -67,11 +67,14 @@ function parseResolvedFilter(
|
|
|
67
67
|
export function compensationFailureAdminRoutes(kernel: Kernel) {
|
|
68
68
|
const router = new OpenAPIHono<AppEnv>();
|
|
69
69
|
|
|
70
|
+
router.use("/compensation-failures", requirePerm("compensation:admin"));
|
|
71
|
+
router.use("/compensation-failures/*", requirePerm("compensation:admin"));
|
|
72
|
+
|
|
70
73
|
// @ts-expect-error -- openapi handler union return type
|
|
71
74
|
router.openapi(listCompensationFailuresRoute, async (c) => {
|
|
72
75
|
const actor = c.get("actor");
|
|
73
76
|
assertPermission(actor, "compensation:admin");
|
|
74
|
-
const orgId =
|
|
77
|
+
const orgId = resolveOrgIdForCommerce(actor, kernel.config);
|
|
75
78
|
const resolvedParsed = parseResolvedFilter(c.req.query("resolved"));
|
|
76
79
|
if (!resolvedParsed.ok) {
|
|
77
80
|
return c.json(
|
|
@@ -116,7 +119,7 @@ export function compensationFailureAdminRoutes(kernel: Kernel) {
|
|
|
116
119
|
router.openapi(resolveCompensationFailureRoute, async (c) => {
|
|
117
120
|
const actor = c.get("actor");
|
|
118
121
|
assertPermission(actor, "compensation:admin");
|
|
119
|
-
const orgId =
|
|
122
|
+
const orgId = resolveOrgIdForCommerce(actor, kernel.config);
|
|
120
123
|
const id = c.req.param("id");
|
|
121
124
|
const body = c.req.valid("json");
|
|
122
125
|
|
|
@@ -147,7 +150,7 @@ export function compensationFailureAdminRoutes(kernel: Kernel) {
|
|
|
147
150
|
const resolved = await kernel.services.compensationFailures.markResolved({
|
|
148
151
|
id,
|
|
149
152
|
organizationId: orgId,
|
|
150
|
-
resolvedBy: (actor as Actor).userId,
|
|
153
|
+
resolvedBy: (actor as Actor).userId ?? "system",
|
|
151
154
|
...(body.notes !== undefined ? { notes: body.notes } : {}),
|
|
152
155
|
});
|
|
153
156
|
if (!resolved.ok) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
2
2
|
import type { Kernel } from "../../../../runtime/kernel.js";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveOrgIdForCommerce } from "../../../../auth/org.js";
|
|
4
4
|
import {
|
|
5
5
|
listCustomFieldProposalsRoute,
|
|
6
6
|
} from "../../schemas/admin-custom-field-proposals.js";
|
|
@@ -19,7 +19,7 @@ export function adminCustomFieldProposalRoutes(kernel: Kernel) {
|
|
|
19
19
|
const pagination = parsePagination(c.req.query());
|
|
20
20
|
const entityType = c.req.query("entityType");
|
|
21
21
|
const result = await kernel.services.catalog.repository.listProposedCustomFields(
|
|
22
|
-
|
|
22
|
+
resolveOrgIdForCommerce(c.get("actor"), kernel.config),
|
|
23
23
|
pagination,
|
|
24
24
|
entityType !== undefined ? { entityType } : undefined,
|
|
25
25
|
);
|
|
@@ -3,7 +3,7 @@ import type { Kernel } from "../../../../runtime/kernel.js";
|
|
|
3
3
|
import type { CommerceConfig } from "../../../../config/types.js";
|
|
4
4
|
import { assertPermission } from "../../../../auth/permissions.js";
|
|
5
5
|
import { listAdminPermissionsRoute } from "../../schemas/admin-permissions.js";
|
|
6
|
-
import type
|
|
6
|
+
import { type AppEnv, requirePerm } from "../../utils.js";
|
|
7
7
|
|
|
8
8
|
function flattenCoreRolePermissions(config: CommerceConfig): string[] {
|
|
9
9
|
const roles = config.auth?.roles ?? {};
|
|
@@ -19,6 +19,8 @@ function flattenCoreRolePermissions(config: CommerceConfig): string[] {
|
|
|
19
19
|
export function adminPermissionsRoutes(kernel: Kernel) {
|
|
20
20
|
const router = new OpenAPIHono<AppEnv>();
|
|
21
21
|
|
|
22
|
+
router.use("/permissions", requirePerm("admin"));
|
|
23
|
+
|
|
22
24
|
router.openapi(listAdminPermissionsRoute, (c) => {
|
|
23
25
|
const actor = c.get("actor");
|
|
24
26
|
assertPermission(actor, "admin");
|
|
@@ -4,7 +4,7 @@ import type { Kernel } from "../../../../runtime/kernel.js";
|
|
|
4
4
|
import type { CommerceConfig } from "../../../../config/types.js";
|
|
5
5
|
import type { DrizzleDatabase } from "../../../../kernel/database/drizzle-db.js";
|
|
6
6
|
import { member, user, invitation } from "../../../../auth/auth-schema.js";
|
|
7
|
-
import {
|
|
7
|
+
import { resolveOrgIdForCommerce } from "../../../../auth/org.js";
|
|
8
8
|
import { makeId } from "../../../../utils/id.js";
|
|
9
9
|
import {
|
|
10
10
|
listStaffRoute,
|
|
@@ -15,6 +15,15 @@ import {
|
|
|
15
15
|
updateStaffRoleRoute,
|
|
16
16
|
revokeStaffRoute,
|
|
17
17
|
} from "../../schemas/admin-staff.js";
|
|
18
|
+
import type { Actor } from "../../../../auth/types.js";
|
|
19
|
+
import {
|
|
20
|
+
canGrantRole,
|
|
21
|
+
granterForRole,
|
|
22
|
+
isCompositeRole,
|
|
23
|
+
isOwnerRole,
|
|
24
|
+
roleRank,
|
|
25
|
+
validRoles,
|
|
26
|
+
} from "../../../../auth/role-authority.js";
|
|
18
27
|
import { type AppEnv, requirePerm } from "../../utils.js";
|
|
19
28
|
|
|
20
29
|
/**
|
|
@@ -34,39 +43,28 @@ export function adminStaffRoutes(kernel: Kernel) {
|
|
|
34
43
|
router.use("/staff/invitations", requirePerm("staff:manage"));
|
|
35
44
|
router.use("/staff/roles", requirePerm("staff:manage"));
|
|
36
45
|
|
|
37
|
-
function validRoles(): Set<string> {
|
|
38
|
-
return new Set([
|
|
39
|
-
...Object.keys(config.auth?.roles ?? {}),
|
|
40
|
-
"owner",
|
|
41
|
-
"admin",
|
|
42
|
-
"member",
|
|
43
|
-
]);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
46
|
function invalidRole(c: { json: (d: unknown, s: number) => unknown }, role: string) {
|
|
47
47
|
return c.json(
|
|
48
48
|
{
|
|
49
49
|
error: {
|
|
50
50
|
code: "VALIDATION_FAILED",
|
|
51
|
-
message: `Unknown role "${role}". Valid roles: ${[...validRoles()].join(", ")}.`,
|
|
51
|
+
message: `Unknown role "${role}". Valid roles: ${[...validRoles(config)].join(", ")}.`,
|
|
52
52
|
},
|
|
53
53
|
},
|
|
54
54
|
422,
|
|
55
55
|
);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
admin: 2,
|
|
61
|
-
};
|
|
62
|
-
const CUSTOM_ROLE_RANK = 1;
|
|
63
|
-
|
|
64
|
-
function roleRank(role: string): number {
|
|
65
|
-
return BUILTIN_ROLE_RANK[role] ?? CUSTOM_ROLE_RANK;
|
|
58
|
+
function rankOf(role: string | null | undefined): number {
|
|
59
|
+
return roleRank(config, role);
|
|
66
60
|
}
|
|
67
61
|
|
|
68
|
-
function
|
|
69
|
-
return
|
|
62
|
+
function actorCanGrantRole(actor: Actor | null, targetRole: string): boolean {
|
|
63
|
+
return canGrantRole(
|
|
64
|
+
config,
|
|
65
|
+
actor ? { role: actor.role, permissions: actor.permissions } : null,
|
|
66
|
+
targetRole,
|
|
67
|
+
);
|
|
70
68
|
}
|
|
71
69
|
|
|
72
70
|
function insufficientPrivilege(
|
|
@@ -84,16 +82,81 @@ export function adminStaffRoutes(kernel: Kernel) {
|
|
|
84
82
|
);
|
|
85
83
|
}
|
|
86
84
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Runs a membership mutation with the organization's member rows locked, so
|
|
87
|
+
* the last-owner check and the write it guards cannot be interleaved.
|
|
88
|
+
*
|
|
89
|
+
* Counting owners and then mutating as two statements let two owners revoke
|
|
90
|
+
* each other concurrently: both read `count = 2`, both proceed, and the
|
|
91
|
+
* organization is left with none. The lock makes the second transaction
|
|
92
|
+
* re-read after the first commits, so it sees the count it must respect.
|
|
93
|
+
*
|
|
94
|
+
* The whole member set is locked, not just the owner rows, because an owner
|
|
95
|
+
* is any member whose role string contains `owner` — a predicate SQL cannot
|
|
96
|
+
* express as cheaply as the JS filter below. Membership writes are rare
|
|
97
|
+
* administrative actions, so serialising them per organization is free.
|
|
98
|
+
* `ORDER BY id` fixes the lock order and keeps two concurrent revocations
|
|
99
|
+
* from deadlocking each other.
|
|
100
|
+
*/
|
|
101
|
+
async function withMembershipLock<T>(
|
|
102
|
+
orgId: string,
|
|
103
|
+
run: (tx: DrizzleDatabase, ownerIds: string[]) => Promise<T>,
|
|
104
|
+
): Promise<T> {
|
|
105
|
+
return kernel.database.transaction(async (raw) => {
|
|
106
|
+
const tx = raw as DrizzleDatabase;
|
|
107
|
+
const locked = await tx
|
|
108
|
+
.select({ id: member.id, role: member.role })
|
|
109
|
+
.from(member)
|
|
110
|
+
.where(eq(member.organizationId, orgId))
|
|
111
|
+
.orderBy(member.id)
|
|
112
|
+
.for("update");
|
|
113
|
+
const ownerIds = locked
|
|
114
|
+
.filter((row) => isOwnerRole(row.role))
|
|
115
|
+
.map((row) => row.id);
|
|
116
|
+
return run(tx, ownerIds);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Cancels the member's pending invitations that their role can no longer
|
|
122
|
+
* grant. Acceptance re-checks the inviter's authority too, so this is not
|
|
123
|
+
* what closes the takeover — it keeps the invitation list honest and stops a
|
|
124
|
+
* dead grant sitting there for the rest of its seven-day life.
|
|
125
|
+
*
|
|
126
|
+
* `newRole` is null when the membership is being revoked outright.
|
|
127
|
+
*/
|
|
128
|
+
async function cancelUngrantableInvitations(
|
|
129
|
+
orgId: string,
|
|
130
|
+
inviterUserId: string,
|
|
131
|
+
newRole: string | null,
|
|
132
|
+
): Promise<void> {
|
|
133
|
+
const pending = await db
|
|
134
|
+
.select({ id: invitation.id, role: invitation.role })
|
|
135
|
+
.from(invitation)
|
|
136
|
+
.where(
|
|
137
|
+
and(
|
|
138
|
+
eq(invitation.organizationId, orgId),
|
|
139
|
+
eq(invitation.inviterId, inviterUserId),
|
|
140
|
+
eq(invitation.status, "pending"),
|
|
141
|
+
),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const dead = pending.filter(
|
|
145
|
+
(row) =>
|
|
146
|
+
newRole === null ||
|
|
147
|
+
!canGrantRole(config, granterForRole(config, newRole), row.role ?? "member"),
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
for (const row of dead) {
|
|
151
|
+
await db
|
|
152
|
+
.update(invitation)
|
|
153
|
+
.set({ status: "canceled" })
|
|
154
|
+
.where(eq(invitation.id, row.id));
|
|
155
|
+
}
|
|
93
156
|
}
|
|
94
157
|
|
|
95
158
|
router.openapi(listStaffRoute, async (c) => {
|
|
96
|
-
const orgId =
|
|
159
|
+
const orgId = resolveOrgIdForCommerce(c.get("actor"), config);
|
|
97
160
|
const rows = await db
|
|
98
161
|
.select({
|
|
99
162
|
id: member.id,
|
|
@@ -113,10 +176,10 @@ export function adminStaffRoutes(kernel: Kernel) {
|
|
|
113
176
|
router.openapi(createStaffRoute, async (c) => {
|
|
114
177
|
const body = c.req.valid("json") as { userId: string; role: string };
|
|
115
178
|
const actor = c.get("actor");
|
|
116
|
-
const orgId =
|
|
179
|
+
const orgId = resolveOrgIdForCommerce(actor, config);
|
|
117
180
|
|
|
118
|
-
if (!validRoles().has(body.role)) return invalidRole(c, body.role);
|
|
119
|
-
if (!
|
|
181
|
+
if (isCompositeRole(body.role) || !validRoles(config).has(body.role)) return invalidRole(c, body.role);
|
|
182
|
+
if (!actorCanGrantRole(actor, body.role)) return insufficientPrivilege(c, body.role);
|
|
120
183
|
|
|
121
184
|
const users = await db.select().from(user).where(eq(user.id, body.userId));
|
|
122
185
|
if (users.length === 0) {
|
|
@@ -151,10 +214,10 @@ export function adminStaffRoutes(kernel: Kernel) {
|
|
|
151
214
|
router.openapi(inviteStaffRoute, async (c) => {
|
|
152
215
|
const body = c.req.valid("json") as { email: string; role: string };
|
|
153
216
|
const actor = c.get("actor");
|
|
154
|
-
const orgId =
|
|
155
|
-
|
|
156
|
-
if (!validRoles().has(body.role)) return invalidRole(c, body.role);
|
|
217
|
+
const orgId = resolveOrgIdForCommerce(actor, config);
|
|
157
218
|
|
|
219
|
+
if (isCompositeRole(body.role) || !validRoles(config).has(body.role)) return invalidRole(c, body.role);
|
|
220
|
+
if (!actorCanGrantRole(actor, body.role)) return insufficientPrivilege(c, body.role);
|
|
158
221
|
const rows = await db
|
|
159
222
|
.insert(invitation)
|
|
160
223
|
.values({
|
|
@@ -164,14 +227,14 @@ export function adminStaffRoutes(kernel: Kernel) {
|
|
|
164
227
|
role: body.role,
|
|
165
228
|
status: "pending",
|
|
166
229
|
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
167
|
-
inviterId: actor
|
|
230
|
+
inviterId: actor?.userId ?? "system",
|
|
168
231
|
})
|
|
169
232
|
.returning();
|
|
170
233
|
return c.json({ data: rows[0] }, 201);
|
|
171
234
|
});
|
|
172
235
|
|
|
173
236
|
router.openapi(listStaffInvitationsRoute, async (c) => {
|
|
174
|
-
const orgId =
|
|
237
|
+
const orgId = resolveOrgIdForCommerce(c.get("actor"), config);
|
|
175
238
|
const rows = await db
|
|
176
239
|
.select()
|
|
177
240
|
.from(invitation)
|
|
@@ -192,67 +255,93 @@ export function adminStaffRoutes(kernel: Kernel) {
|
|
|
192
255
|
router.openapi(updateStaffRoleRoute, async (c) => {
|
|
193
256
|
const body = c.req.valid("json") as { role: string };
|
|
194
257
|
const actor = c.get("actor");
|
|
195
|
-
const orgId =
|
|
258
|
+
const orgId = resolveOrgIdForCommerce(actor, config);
|
|
196
259
|
const id = c.req.param("id");
|
|
197
260
|
|
|
198
|
-
if (!validRoles().has(body.role)) return invalidRole(c, body.role);
|
|
199
|
-
if (!
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
261
|
+
if (isCompositeRole(body.role) || !validRoles(config).has(body.role)) return invalidRole(c, body.role);
|
|
262
|
+
if (!actorCanGrantRole(actor, body.role)) return insufficientPrivilege(c, body.role);
|
|
263
|
+
|
|
264
|
+
const outcome = await withMembershipLock(orgId, async (tx, ownerIds) => {
|
|
265
|
+
const rows = await tx
|
|
266
|
+
.select()
|
|
267
|
+
.from(member)
|
|
268
|
+
.where(and(eq(member.organizationId, orgId), eq(member.id, id)));
|
|
269
|
+
const target = rows[0];
|
|
270
|
+
if (!target) return { kind: "not-found" } as const;
|
|
271
|
+
|
|
272
|
+
// SEC-18/R-02: the actor must also outrank (or equal) the target's CURRENT
|
|
273
|
+
// role, else an admin could demote an owner they do not outrank.
|
|
274
|
+
if (rankOf(actor!.role) < rankOf(target.role)) {
|
|
275
|
+
return { kind: "outranked", role: target.role } as const;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (isOwnerRole(target.role) && !isOwnerRole(body.role) && ownerIds.length <= 1) {
|
|
279
|
+
return { kind: "last-owner" } as const;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const updated = await tx
|
|
283
|
+
.update(member)
|
|
284
|
+
.set({ role: body.role })
|
|
285
|
+
.where(eq(member.id, id))
|
|
286
|
+
.returning();
|
|
287
|
+
return { kind: "updated", target, row: updated[0] } as const;
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
if (outcome.kind === "not-found") {
|
|
207
291
|
return c.json({ error: { code: "NOT_FOUND", message: "Staff member not found." } }, 404);
|
|
208
292
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
// role, else an admin could demote an owner they do not outrank.
|
|
212
|
-
if (!canGrantRole(actor!.role, target.role)) return insufficientPrivilege(c, target.role);
|
|
213
|
-
|
|
214
|
-
if (target.role === "owner" && body.role !== "owner" && (await countOwners(orgId)) <= 1) {
|
|
293
|
+
if (outcome.kind === "outranked") return insufficientPrivilege(c, outcome.role);
|
|
294
|
+
if (outcome.kind === "last-owner") {
|
|
215
295
|
return c.json(
|
|
216
296
|
{ error: { code: "VALIDATION_FAILED", message: "Cannot demote the organization's last owner." } },
|
|
217
297
|
422,
|
|
218
298
|
);
|
|
219
299
|
}
|
|
220
300
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
.set({ role: body.role })
|
|
224
|
-
.where(eq(member.id, id))
|
|
225
|
-
.returning();
|
|
226
|
-
return c.json({ data: updated[0] });
|
|
301
|
+
await cancelUngrantableInvitations(orgId, outcome.target.userId, body.role);
|
|
302
|
+
return c.json({ data: outcome.row });
|
|
227
303
|
});
|
|
228
304
|
|
|
229
305
|
// @ts-expect-error -- openapi handler union return type
|
|
230
306
|
router.openapi(revokeStaffRoute, async (c) => {
|
|
231
307
|
const actor = c.get("actor");
|
|
232
|
-
const orgId =
|
|
308
|
+
const orgId = resolveOrgIdForCommerce(actor, config);
|
|
233
309
|
const id = c.req.param("id");
|
|
234
310
|
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
311
|
+
const outcome = await withMembershipLock(orgId, async (tx, ownerIds) => {
|
|
312
|
+
const rows = await tx
|
|
313
|
+
.select()
|
|
314
|
+
.from(member)
|
|
315
|
+
.where(and(eq(member.organizationId, orgId), eq(member.id, id)));
|
|
316
|
+
const target = rows[0];
|
|
317
|
+
if (!target) return { kind: "not-found" } as const;
|
|
318
|
+
|
|
319
|
+
// SEC-18/R-02: the actor must outrank (or equal) the target's current role
|
|
320
|
+
// to revoke it — an admin cannot revoke an owner.
|
|
321
|
+
if (rankOf(actor!.role) < rankOf(target.role)) {
|
|
322
|
+
return { kind: "outranked", role: target.role } as const;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (isOwnerRole(target.role) && ownerIds.length <= 1) {
|
|
326
|
+
return { kind: "last-owner" } as const;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
await tx.delete(member).where(eq(member.id, id));
|
|
330
|
+
return { kind: "deleted", target } as const;
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
if (outcome.kind === "not-found") {
|
|
241
334
|
return c.json({ error: { code: "NOT_FOUND", message: "Staff member not found." } }, 404);
|
|
242
335
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
// to revoke it — an admin cannot revoke an owner.
|
|
246
|
-
if (!canGrantRole(actor!.role, target.role)) return insufficientPrivilege(c, target.role);
|
|
247
|
-
|
|
248
|
-
if (target.role === "owner" && (await countOwners(orgId)) <= 1) {
|
|
336
|
+
if (outcome.kind === "outranked") return insufficientPrivilege(c, outcome.role);
|
|
337
|
+
if (outcome.kind === "last-owner") {
|
|
249
338
|
return c.json(
|
|
250
339
|
{ error: { code: "VALIDATION_FAILED", message: "Cannot revoke the organization's last owner." } },
|
|
251
340
|
422,
|
|
252
341
|
);
|
|
253
342
|
}
|
|
254
343
|
|
|
255
|
-
await
|
|
344
|
+
await cancelUngrantableInvitations(orgId, outcome.target.userId, null);
|
|
256
345
|
return c.json({ data: { deleted: true } });
|
|
257
346
|
});
|
|
258
347
|
|
|
@@ -5,7 +5,7 @@ import type { Kernel } from "../../../runtime/kernel.js";
|
|
|
5
5
|
import { commerceJobs } from "../../../kernel/jobs/schema.js";
|
|
6
6
|
import { listFailedJobsRoute, retryJobRoute } from "../schemas/admin-jobs.js";
|
|
7
7
|
import { type AppEnv, requirePerm } from "../utils.js";
|
|
8
|
-
import {
|
|
8
|
+
import { resolveOrgIdForCommerce } from "../../../auth/org.js";
|
|
9
9
|
|
|
10
10
|
type Db = PgDatabase<PgQueryResultHKT, Record<string, unknown>>;
|
|
11
11
|
|
|
@@ -17,7 +17,7 @@ export function adminJobRoutes(kernel: Kernel) {
|
|
|
17
17
|
|
|
18
18
|
router.openapi(listFailedJobsRoute, async (c) => {
|
|
19
19
|
const actor = c.get("actor");
|
|
20
|
-
const orgId =
|
|
20
|
+
const orgId = resolveOrgIdForCommerce(actor, kernel.config);
|
|
21
21
|
const limit = Math.min(Number(c.req.query("limit") ?? "50"), 100);
|
|
22
22
|
const conditions = [eq(commerceJobs.status, "failed")];
|
|
23
23
|
// Scope to org unless wildcard admin
|
|
@@ -37,7 +37,7 @@ export function adminJobRoutes(kernel: Kernel) {
|
|
|
37
37
|
// @ts-expect-error -- openapi handler union return type
|
|
38
38
|
router.openapi(retryJobRoute, async (c) => {
|
|
39
39
|
const actor = c.get("actor");
|
|
40
|
-
const orgId =
|
|
40
|
+
const orgId = resolveOrgIdForCommerce(actor, kernel.config);
|
|
41
41
|
const id = c.req.param("id");
|
|
42
42
|
const conditions = [eq(commerceJobs.id, id)];
|
|
43
43
|
// Scope to org unless wildcard admin
|
|
@@ -2,7 +2,7 @@ import { OpenAPIHono } from "@hono/zod-openapi";
|
|
|
2
2
|
import type { Kernel } from "../../../runtime/kernel.js";
|
|
3
3
|
import { listAuditRoute, listEntityAuditRoute } from "../schemas/audit.js";
|
|
4
4
|
import { type AppEnv, requirePerm } from "../utils.js";
|
|
5
|
-
import {
|
|
5
|
+
import { resolveOrgIdForCommerce } from "../../../auth/org.js";
|
|
6
6
|
|
|
7
7
|
export function auditRoutes(kernel: Kernel) {
|
|
8
8
|
const router = new OpenAPIHono<AppEnv>();
|
|
@@ -15,7 +15,7 @@ export function auditRoutes(kernel: Kernel) {
|
|
|
15
15
|
|
|
16
16
|
router.openapi(listAuditRoute, async (c) => {
|
|
17
17
|
const actor = c.get("actor");
|
|
18
|
-
const orgId =
|
|
18
|
+
const orgId = resolveOrgIdForCommerce(actor, kernel.config);
|
|
19
19
|
const entries = await kernel.services.audit.list({
|
|
20
20
|
organizationId: orgId,
|
|
21
21
|
entityType: c.req.query("entityType"),
|
|
@@ -37,7 +37,7 @@ export function auditRoutes(kernel: Kernel) {
|
|
|
37
37
|
|
|
38
38
|
router.openapi(listEntityAuditRoute, async (c) => {
|
|
39
39
|
const actor = c.get("actor");
|
|
40
|
-
const orgId =
|
|
40
|
+
const orgId = resolveOrgIdForCommerce(actor, kernel.config);
|
|
41
41
|
const entries = await kernel.services.audit.listForEntity({
|
|
42
42
|
organizationId: orgId,
|
|
43
43
|
entityType: c.req.param("entityType"),
|
|
@@ -2,16 +2,22 @@ import { OpenAPIHono } from "@hono/zod-openapi";
|
|
|
2
2
|
import type { Kernel } from "../../../runtime/kernel.js";
|
|
3
3
|
import type { CreateCartInput, AddCartItemInput } from "../../../modules/cart/schemas.js";
|
|
4
4
|
import { createCartRoute, addCartItemRoute, updateCartItemQuantityRoute, getCartRoute, removeCartItemRoute, listCartsRoute, recoverCartRoute } from "../schemas/carts.js";
|
|
5
|
-
import { type AppEnv, mapErrorToResponse, mapErrorToStatus, parsePagination } from "../utils.js";
|
|
5
|
+
import { type AppEnv, mapErrorToResponse, mapErrorToStatus, parsePagination, requireMethodPerm, requirePerm } from "../utils.js";
|
|
6
6
|
|
|
7
7
|
export function cartRoutes(kernel: Kernel) {
|
|
8
8
|
const router = new OpenAPIHono<AppEnv>();
|
|
9
9
|
|
|
10
|
+
router.use("/", requireMethodPerm(["GET"], "cart:manage"));
|
|
11
|
+
router.use("/:id/recover", requirePerm("cart:manage"));
|
|
12
|
+
|
|
10
13
|
// @ts-expect-error -- openapi() enforces strict response typing but our handler
|
|
11
14
|
// returns union responses (201 | 400 | 422). The route definition documents the
|
|
12
15
|
// contract; the handler returns dynamic status.
|
|
13
16
|
router.openapi(createCartRoute, async (c) => {
|
|
14
17
|
const actor = c.get("actor");
|
|
18
|
+
if (!actor) {
|
|
19
|
+
return c.json({ error: { code: "NOT_FOUND", message: "Cart not found." } }, 404);
|
|
20
|
+
}
|
|
15
21
|
const result = await kernel.services.cart.create(c.req.valid("json") as CreateCartInput, actor);
|
|
16
22
|
if (!result.ok)
|
|
17
23
|
return c.json(
|
|
@@ -74,17 +80,12 @@ export function cartRoutes(kernel: Kernel) {
|
|
|
74
80
|
// @ts-expect-error -- openapi handler union return type
|
|
75
81
|
router.openapi(getCartRoute, async (c) => {
|
|
76
82
|
const actor = c.get("actor");
|
|
77
|
-
// Guest carts are gated by the cart secret; clients pass it as either
|
|
78
|
-
// ?secret=... or X-Cart-Secret. Customer carts ignore the secret and
|
|
79
|
-
// require ownership instead.
|
|
80
|
-
const secretParam = c.req.query("secret");
|
|
81
83
|
const secretHeader = c.req.header("x-cart-secret") ?? c.req.header("X-Cart-Secret");
|
|
82
|
-
const secret = secretParam ?? secretHeader ?? undefined;
|
|
83
84
|
const result = await kernel.services.cart.getById(
|
|
84
85
|
c.req.param("id"),
|
|
85
86
|
actor,
|
|
86
87
|
undefined,
|
|
87
|
-
|
|
88
|
+
secretHeader ?? undefined,
|
|
88
89
|
);
|
|
89
90
|
if (!result.ok)
|
|
90
91
|
return c.json(
|
|
@@ -98,9 +99,12 @@ export function cartRoutes(kernel: Kernel) {
|
|
|
98
99
|
// returns union responses (201 | 400 | 422). The route definition documents the
|
|
99
100
|
// contract; the handler returns dynamic status.
|
|
100
101
|
router.openapi(addCartItemRoute, async (c) => {
|
|
102
|
+
const secret = c.req.header("x-cart-secret") ?? c.req.header("X-Cart-Secret") ?? undefined;
|
|
101
103
|
const result = await kernel.services.cart.addItem(
|
|
102
104
|
{ ...c.req.valid("json"), cartId: c.req.param("id") } as AddCartItemInput,
|
|
103
105
|
c.get("actor"),
|
|
106
|
+
undefined,
|
|
107
|
+
secret,
|
|
104
108
|
);
|
|
105
109
|
if (!result.ok)
|
|
106
110
|
return c.json(
|
|
@@ -115,6 +119,7 @@ export function cartRoutes(kernel: Kernel) {
|
|
|
115
119
|
// contract; the handler returns dynamic status.
|
|
116
120
|
router.openapi(updateCartItemQuantityRoute, async (c) => {
|
|
117
121
|
const body = c.req.valid("json");
|
|
122
|
+
const secret = c.req.header("x-cart-secret") ?? c.req.header("X-Cart-Secret") ?? undefined;
|
|
118
123
|
const result = await kernel.services.cart.updateQuantity(
|
|
119
124
|
{
|
|
120
125
|
cartId: c.req.param("id"),
|
|
@@ -122,6 +127,8 @@ export function cartRoutes(kernel: Kernel) {
|
|
|
122
127
|
quantity: body.quantity,
|
|
123
128
|
},
|
|
124
129
|
c.get("actor"),
|
|
130
|
+
undefined,
|
|
131
|
+
secret,
|
|
125
132
|
);
|
|
126
133
|
if (!result.ok)
|
|
127
134
|
return c.json(
|
|
@@ -133,10 +140,13 @@ export function cartRoutes(kernel: Kernel) {
|
|
|
133
140
|
|
|
134
141
|
// @ts-expect-error -- openapi handler union return type
|
|
135
142
|
router.openapi(removeCartItemRoute, async (c) => {
|
|
143
|
+
const secret = c.req.header("x-cart-secret") ?? c.req.header("X-Cart-Secret") ?? undefined;
|
|
136
144
|
const result = await kernel.services.cart.removeItem(
|
|
137
145
|
c.req.param("id"),
|
|
138
146
|
c.req.param("itemId"),
|
|
139
147
|
c.get("actor"),
|
|
148
|
+
undefined,
|
|
149
|
+
secret,
|
|
140
150
|
);
|
|
141
151
|
if (!result.ok)
|
|
142
152
|
return c.json(
|
|
@@ -64,8 +64,17 @@ import {
|
|
|
64
64
|
export function catalogRoutes(kernel: Kernel) {
|
|
65
65
|
const router = new OpenAPIHono<AppEnv>();
|
|
66
66
|
|
|
67
|
+
router.use("/entities/*", requirePerm("catalog:read"));
|
|
68
|
+
router.use("/categories", requirePerm("catalog:read"));
|
|
69
|
+
router.use("/brands", requirePerm("catalog:read"));
|
|
70
|
+
|
|
67
71
|
router.use("/entities/:id/custom-fields/:fieldName/approve", requirePerm("catalog:update"));
|
|
68
72
|
router.use("/entities/:id/custom-fields/:fieldName/reject", requirePerm("catalog:update"));
|
|
73
|
+
router.use("/categories/:categoryId", requirePerm("catalog:update"));
|
|
74
|
+
router.use("/categories/:categoryId/*", requirePerm("catalog:update"));
|
|
75
|
+
router.use("/brands/:brandId", requirePerm("catalog:update"));
|
|
76
|
+
router.use("/brands/:brandId/*", requirePerm("catalog:update"));
|
|
77
|
+
router.use("/options/:optionTypeId/values", requirePerm("catalog:update"));
|
|
69
78
|
|
|
70
79
|
// @ts-expect-error -- openapi handler union return type
|
|
71
80
|
router.openapi(listFieldOwnershipRoute, async (c) => {
|
|
@@ -159,6 +168,7 @@ export function catalogRoutes(kernel: Kernel) {
|
|
|
159
168
|
const full = await kernel.services.catalog.getById(
|
|
160
169
|
item.id,
|
|
161
170
|
includeOptions,
|
|
171
|
+
c.get("actor"),
|
|
162
172
|
);
|
|
163
173
|
return full.ok ? full.value : item;
|
|
164
174
|
}),
|
|
@@ -358,6 +368,7 @@ export function catalogRoutes(kernel: Kernel) {
|
|
|
358
368
|
const result = await kernel.services.catalog.getAttributes(
|
|
359
369
|
c.req.param("id"),
|
|
360
370
|
c.req.param("locale"),
|
|
371
|
+
c.get("actor"),
|
|
361
372
|
);
|
|
362
373
|
if (!result.ok)
|
|
363
374
|
return c.json(
|