@porulle/core 0.40.1 → 0.41.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/permissions.d.ts.map +1 -1
- package/dist/auth/permissions.js +10 -8
- package/dist/hooks/checkout.d.ts.map +1 -1
- package/dist/hooks/checkout.js +0 -1
- package/dist/interfaces/rest/customer-portal.js +1 -1
- package/dist/interfaces/rest/schemas/catalog.d.ts +83 -5
- package/dist/interfaces/rest/schemas/catalog.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/customer-portal.d.ts +8 -5
- package/dist/interfaces/rest/schemas/customer-portal.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/orders.d.ts +8 -5
- package/dist/interfaces/rest/schemas/orders.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/responses.d.ts +33 -15
- package/dist/interfaces/rest/schemas/responses.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/responses.js +10 -4
- package/dist/interfaces/rest/schemas/search.d.ts +3 -0
- package/dist/interfaces/rest/schemas/search.d.ts.map +1 -1
- package/dist/modules/cart/service.d.ts.map +1 -1
- package/dist/modules/pricing/service.d.ts +0 -1
- package/dist/modules/pricing/service.d.ts.map +1 -1
- package/dist/runtime/kernel-register-hooks.d.ts.map +1 -1
- package/dist/runtime/kernel-register-hooks.js +0 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/auth/permissions.ts +10 -8
- package/src/hooks/checkout.ts +2 -9
- package/src/interfaces/rest/customer-portal.ts +1 -1
- package/src/interfaces/rest/schemas/responses.ts +10 -4
- package/src/modules/cart/service.ts +0 -1
- package/src/modules/pricing/service.ts +0 -1
- package/src/runtime/kernel-register-hooks.ts +0 -2
- package/dist/hooks/order-emails.d.ts +0 -16
- package/dist/hooks/order-emails.d.ts.map +0 -1
- package/dist/hooks/order-emails.js +0 -44
- package/src/hooks/order-emails.ts +0 -62
package/package.json
CHANGED
package/src/auth/permissions.ts
CHANGED
|
@@ -41,15 +41,17 @@ export function assertPermission(actor: Actor | null, required: string): void {
|
|
|
41
41
|
);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
// NOT given the unauthenticated predicate, and the omission is deliberate.
|
|
45
|
-
// `auth-permissions.test.ts` pins this refusal as 403 for a STAFF actor with a
|
|
46
|
-
// null userId — a shape `resolveActor` cannot produce — so whether that means
|
|
47
|
-
// "anonymous" or "a synthetic actor whose identity is broken" has to be decided
|
|
48
|
-
// before the status can be. It is carded; no route reaches here with an
|
|
49
|
-
// anonymous actor today.
|
|
50
44
|
export function assertOwnership(actor: Actor | null, resourceOwnerId: string | null): void {
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
// Written exactly as `assertPermission` above, and for the same two reasons: `actor === null`
|
|
46
|
+
// is redundant at runtime because the predicate already covers it, and load-bearing at compile
|
|
47
|
+
// time because a boolean predicate narrows nothing and the lines below read off `actor`.
|
|
48
|
+
//
|
|
49
|
+
// Hoisting the predicate is what makes the rest of this function readable: an `api_key` actor
|
|
50
|
+
// presented a credential and a `userId: ""` actor is a credential with a blank identity, so
|
|
51
|
+
// neither satisfies the predicate and both fall through to the 403 below — telling either to
|
|
52
|
+
// authenticate would be advice it cannot act on.
|
|
53
|
+
if (actor === null || isUnauthenticatedActor(actor)) {
|
|
54
|
+
throw new CommerceUnauthorizedError(AUTHENTICATION_REQUIRED_MESSAGE);
|
|
53
55
|
}
|
|
54
56
|
if (actor.permissions.includes("*:*")) return;
|
|
55
57
|
if (!actor.userId || !resourceOwnerId) {
|
package/src/hooks/checkout.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { CompensationFailuresRepository } from "../kernel/compensation/repo
|
|
|
4
4
|
import type { AfterHook, BeforeHook } from "../kernel/hooks/types.js";
|
|
5
5
|
import type { ShippingAddress } from "../modules/shipping/calculator.js";
|
|
6
6
|
import type { AppliedPromotion } from "../modules/promotions/service.js";
|
|
7
|
+
import type { PriceResolutionContext } from "../modules/pricing/service.js";
|
|
7
8
|
import { runCompensationChain } from "../kernel/compensation/executor.js";
|
|
8
9
|
import type { CompensationContext } from "../kernel/compensation/types.js";
|
|
9
10
|
import type { TxContext } from "../kernel/database/tx-context.js";
|
|
@@ -209,14 +210,7 @@ export const resolveCurrentPrices: BeforeHook<CheckoutData> = async ({
|
|
|
209
210
|
context,
|
|
210
211
|
}) => {
|
|
211
212
|
const pricing = context.services.pricing as {
|
|
212
|
-
resolve(params:
|
|
213
|
-
entityId: string;
|
|
214
|
-
variantId?: string;
|
|
215
|
-
currency: string;
|
|
216
|
-
quantity: number;
|
|
217
|
-
customerId?: string;
|
|
218
|
-
customerGroupIds?: string[];
|
|
219
|
-
}, actor?: unknown): Promise<
|
|
213
|
+
resolve(params: PriceResolutionContext, actor?: unknown): Promise<
|
|
220
214
|
| {
|
|
221
215
|
ok: true;
|
|
222
216
|
value: {
|
|
@@ -239,7 +233,6 @@ export const resolveCurrentPrices: BeforeHook<CheckoutData> = async ({
|
|
|
239
233
|
currency: data.currency,
|
|
240
234
|
quantity: item.quantity,
|
|
241
235
|
...(item.variantId !== undefined ? { variantId: item.variantId } : {}),
|
|
242
|
-
...(data.customerId !== undefined ? { customerId: data.customerId } : {}),
|
|
243
236
|
...(data.customerGroupIds !== undefined
|
|
244
237
|
? { customerGroupIds: data.customerGroupIds }
|
|
245
238
|
: {}),
|
|
@@ -125,7 +125,7 @@ export function createCustomerPortalRoutes(kernel: Kernel) {
|
|
|
125
125
|
const status = c.req.query("status");
|
|
126
126
|
// Resolve customer profile UUID from Better Auth userId
|
|
127
127
|
const customerResult = await kernel.services.customers.getByUserId(actor.userId, actor);
|
|
128
|
-
if (!customerResult.ok) return c.json({ data: [], meta: {
|
|
128
|
+
if (!customerResult.ok) return c.json({ data: [], meta: { pagination: { page: 1, limit: 20, total: 0, totalPages: 0 } } });
|
|
129
129
|
const result = await kernel.services.orders.listByCustomer(
|
|
130
130
|
customerResult.value.id,
|
|
131
131
|
{
|
|
@@ -56,6 +56,9 @@ export const CustomerAddressSchema = createSelectSchema(customerAddresses).opena
|
|
|
56
56
|
export const CatalogEntitySchema = createSelectSchema(sellableEntities, {
|
|
57
57
|
// Override jsonb → narrow to object (drizzle-zod maps jsonb to a wide union)
|
|
58
58
|
metadata: z.record(z.string(), z.unknown()).openapi({ example: { weight: 200, material: "cotton" } }),
|
|
59
|
+
createdAt: z.string().datetime(),
|
|
60
|
+
updatedAt: z.string().datetime(),
|
|
61
|
+
publishedAt: z.string().datetime().nullable(),
|
|
59
62
|
}).openapi("CatalogEntity");
|
|
60
63
|
|
|
61
64
|
// ─── Jobs ────────────────────────────────────────────────────────────────────
|
|
@@ -77,10 +80,13 @@ export function paginatedResponse<T extends z.ZodType>(schema: T, name: string)
|
|
|
77
80
|
return z.object({
|
|
78
81
|
data: z.array(schema),
|
|
79
82
|
meta: z.object({
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
pagination: z.object({
|
|
84
|
+
page: z.number(),
|
|
85
|
+
limit: z.number(),
|
|
86
|
+
total: z.number(),
|
|
87
|
+
totalPages: z.number(),
|
|
88
|
+
}),
|
|
89
|
+
}),
|
|
84
90
|
}).openapi(name);
|
|
85
91
|
}
|
|
86
92
|
|
|
@@ -3,7 +3,6 @@ import { HookRegistry, type HookHandler } from "../kernel/hooks/registry.js";
|
|
|
3
3
|
import { deliverWebhooks } from "../modules/webhooks/hook.js";
|
|
4
4
|
import { syncToSearchIndex } from "../modules/search/hooks.js";
|
|
5
5
|
import { auditHooks } from "../modules/audit/hooks.js";
|
|
6
|
-
import { sendOrderStatusEmail } from "../hooks/order-emails.js";
|
|
7
6
|
|
|
8
7
|
export function registerConfiguredKernelHooks(
|
|
9
8
|
config: CommerceConfig,
|
|
@@ -40,7 +39,6 @@ export function registerConfiguredKernelHooks(
|
|
|
40
39
|
|
|
41
40
|
hooks.append("orders.afterCreate", deliverWebhooks);
|
|
42
41
|
hooks.append("orders.afterStatusChange", deliverWebhooks);
|
|
43
|
-
hooks.append("orders.afterStatusChange", sendOrderStatusEmail as (...args: unknown[]) => unknown);
|
|
44
42
|
hooks.append("catalog.afterCreate", deliverWebhooks);
|
|
45
43
|
hooks.append("catalog.afterUpdate", deliverWebhooks);
|
|
46
44
|
hooks.append("catalog.afterDelete", deliverWebhooks);
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Order lifecycle email notifications.
|
|
3
|
-
*
|
|
4
|
-
* Sends emails on status changes: confirmed, fulfilled, cancelled, refunded.
|
|
5
|
-
* Registered as an orders.afterStatusChange hook.
|
|
6
|
-
*/
|
|
7
|
-
import type { AfterHook } from "../kernel/hooks/types.js";
|
|
8
|
-
interface StatusChangeResult {
|
|
9
|
-
orderId: string;
|
|
10
|
-
customerId?: string | null;
|
|
11
|
-
newStatus: string;
|
|
12
|
-
previousStatus: string;
|
|
13
|
-
}
|
|
14
|
-
export declare const sendOrderStatusEmail: AfterHook<StatusChangeResult>;
|
|
15
|
-
export {};
|
|
16
|
-
//# sourceMappingURL=order-emails.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"order-emails.d.ts","sourceRoot":"","sources":["../../src/hooks/order-emails.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D,UAAU,kBAAkB;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,oBAAoB,EAAE,SAAS,CAAC,kBAAkB,CA6C9D,CAAC"}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Order lifecycle email notifications.
|
|
3
|
-
*
|
|
4
|
-
* Sends emails on status changes: confirmed, fulfilled, cancelled, refunded.
|
|
5
|
-
* Registered as an orders.afterStatusChange hook.
|
|
6
|
-
*/
|
|
7
|
-
export const sendOrderStatusEmail = async ({ result, context, }) => {
|
|
8
|
-
const email = context.services.email;
|
|
9
|
-
if (!email?.send)
|
|
10
|
-
return;
|
|
11
|
-
// Only send for customer-facing status changes
|
|
12
|
-
const notifiableStatuses = ["confirmed", "processing", "fulfilled", "cancelled", "refunded"];
|
|
13
|
-
if (!notifiableStatuses.includes(result.newStatus))
|
|
14
|
-
return;
|
|
15
|
-
// Look up customer email
|
|
16
|
-
const customerId = result.customerId;
|
|
17
|
-
if (!customerId)
|
|
18
|
-
return;
|
|
19
|
-
const customers = context.services.customers;
|
|
20
|
-
if (!customers)
|
|
21
|
-
return;
|
|
22
|
-
try {
|
|
23
|
-
const customer = await customers.getByUserId(customerId, context.actor);
|
|
24
|
-
if (!customer.ok || !customer.value?.email)
|
|
25
|
-
return;
|
|
26
|
-
await email.send({
|
|
27
|
-
template: "order-status-change",
|
|
28
|
-
to: customer.value.email,
|
|
29
|
-
data: {
|
|
30
|
-
orderId: result.orderId,
|
|
31
|
-
newStatus: result.newStatus,
|
|
32
|
-
previousStatus: result.previousStatus,
|
|
33
|
-
},
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
catch (err) {
|
|
37
|
-
// Email failure must not break the order flow
|
|
38
|
-
context.logger.warn("Order status email failed", {
|
|
39
|
-
orderId: result.orderId,
|
|
40
|
-
newStatus: result.newStatus,
|
|
41
|
-
error: err instanceof Error ? err.message : String(err),
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
};
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Order lifecycle email notifications.
|
|
3
|
-
*
|
|
4
|
-
* Sends emails on status changes: confirmed, fulfilled, cancelled, refunded.
|
|
5
|
-
* Registered as an orders.afterStatusChange hook.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { AfterHook } from "../kernel/hooks/types.js";
|
|
9
|
-
|
|
10
|
-
interface StatusChangeResult {
|
|
11
|
-
orderId: string;
|
|
12
|
-
customerId?: string | null;
|
|
13
|
-
newStatus: string;
|
|
14
|
-
previousStatus: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const sendOrderStatusEmail: AfterHook<StatusChangeResult> = async ({
|
|
18
|
-
result,
|
|
19
|
-
context,
|
|
20
|
-
}) => {
|
|
21
|
-
const email = context.services.email as
|
|
22
|
-
| { send(input: { template: string; to: string; data?: Record<string, unknown> }): Promise<void> }
|
|
23
|
-
| undefined;
|
|
24
|
-
|
|
25
|
-
if (!email?.send) return;
|
|
26
|
-
|
|
27
|
-
// Only send for customer-facing status changes
|
|
28
|
-
const notifiableStatuses = ["confirmed", "processing", "fulfilled", "cancelled", "refunded"];
|
|
29
|
-
if (!notifiableStatuses.includes(result.newStatus)) return;
|
|
30
|
-
|
|
31
|
-
// Look up customer email
|
|
32
|
-
const customerId = result.customerId;
|
|
33
|
-
if (!customerId) return;
|
|
34
|
-
|
|
35
|
-
const customers = context.services.customers as
|
|
36
|
-
| { getByUserId(id: string, actor?: unknown): Promise<{ ok: boolean; value?: { email?: string | null } }> }
|
|
37
|
-
| undefined;
|
|
38
|
-
|
|
39
|
-
if (!customers) return;
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
const customer = await customers.getByUserId(customerId, context.actor);
|
|
43
|
-
if (!customer.ok || !customer.value?.email) return;
|
|
44
|
-
|
|
45
|
-
await email.send({
|
|
46
|
-
template: "order-status-change",
|
|
47
|
-
to: customer.value.email,
|
|
48
|
-
data: {
|
|
49
|
-
orderId: result.orderId,
|
|
50
|
-
newStatus: result.newStatus,
|
|
51
|
-
previousStatus: result.previousStatus,
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
} catch (err) {
|
|
55
|
-
// Email failure must not break the order flow
|
|
56
|
-
context.logger.warn("Order status email failed", {
|
|
57
|
-
orderId: result.orderId,
|
|
58
|
-
newStatus: result.newStatus,
|
|
59
|
-
error: err instanceof Error ? err.message : String(err),
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
};
|