medusa-product-helper 0.0.16 → 0.0.19
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/.medusa/server/src/admin/index.js +59 -117
- package/.medusa/server/src/admin/index.mjs +59 -117
- package/.medusa/server/src/api/store/product-helper/products/route.js +137 -7
- package/.medusa/server/src/api/store/product-helper/products/validators.js +11 -1
- package/.medusa/server/src/providers/filter-providers/availability-provider.js +53 -67
- package/.medusa/server/src/providers/filter-providers/base-filter-provider.js +1 -19
- package/.medusa/server/src/providers/filter-providers/base-product-provider.js +37 -100
- package/.medusa/server/src/providers/filter-providers/category-provider.js +15 -34
- package/.medusa/server/src/providers/filter-providers/collection-provider.js +15 -32
- package/.medusa/server/src/providers/filter-providers/index.js +13 -49
- package/.medusa/server/src/providers/filter-providers/metadata-provider.js +43 -58
- package/.medusa/server/src/providers/filter-providers/price-range-provider.js +66 -79
- package/.medusa/server/src/providers/filter-providers/promotion-provider.js +106 -169
- package/.medusa/server/src/providers/filter-providers/promotion-window-provider.js +53 -93
- package/.medusa/server/src/providers/filter-providers/rating-provider.js +47 -70
- package/.medusa/server/src/services/dynamic-filter-service.js +822 -736
- package/.medusa/server/src/services/filter-provider-loader.js +91 -139
- package/.medusa/server/src/services/filter-provider-registry.js +8 -107
- package/.medusa/server/src/services/product-filter-service.js +183 -172
- package/.medusa/server/src/shared/product-metadata/utils.js +66 -116
- package/.medusa/server/src/utils/query-builders/product-filters.js +89 -111
- package/.medusa/server/src/utils/query-parser.js +24 -76
- package/.medusa/server/src/workflows/add-to-wishlist.js +12 -26
- package/.medusa/server/src/workflows/get-wishlist.js +53 -51
- package/.medusa/server/src/workflows/remove-from-wishlist.js +3 -8
- package/package.json +1 -1
|
@@ -6,45 +6,35 @@ const react = require("react");
|
|
|
6
6
|
const reactQuery = require("@tanstack/react-query");
|
|
7
7
|
const METADATA_FIELD_TYPES = ["number", "text", "file", "bool"];
|
|
8
8
|
const VALID_FIELD_TYPES = new Set(METADATA_FIELD_TYPES);
|
|
9
|
+
const BOOLEAN_TRUES = /* @__PURE__ */ new Set(["true", "1", "yes", "y", "on"]);
|
|
10
|
+
const BOOLEAN_FALSES = /* @__PURE__ */ new Set(["false", "0", "no", "n", "off"]);
|
|
9
11
|
function normalizeMetadataDescriptors(input) {
|
|
10
|
-
if (!Array.isArray(input))
|
|
11
|
-
return [];
|
|
12
|
-
}
|
|
12
|
+
if (!Array.isArray(input)) return [];
|
|
13
13
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
const label = getNormalizedLabel(item.label);
|
|
28
|
-
const filterable = typeof item.filterable === "boolean" ? item.filterable : Boolean(item.filterable);
|
|
29
|
-
normalized.push({
|
|
14
|
+
return input.filter(
|
|
15
|
+
(item) => item && typeof item === "object"
|
|
16
|
+
).map((item) => {
|
|
17
|
+
const key = normalizeKey(item.key);
|
|
18
|
+
const type = normalizeType(item.type);
|
|
19
|
+
const label = normalizeLabel(item.label);
|
|
20
|
+
const filterable = !!item.filterable;
|
|
21
|
+
return { key, type, label, filterable };
|
|
22
|
+
}).filter(({ key, type }) => key && type && !seenKeys.has(key)).map(({ key, type, label, filterable }) => {
|
|
23
|
+
seenKeys.add(key);
|
|
24
|
+
return {
|
|
30
25
|
key,
|
|
31
26
|
type,
|
|
32
|
-
...label
|
|
33
|
-
...filterable
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
return normalized;
|
|
27
|
+
...label && { label },
|
|
28
|
+
...filterable && { filterable: true }
|
|
29
|
+
};
|
|
30
|
+
});
|
|
38
31
|
}
|
|
39
32
|
function buildInitialFormState(descriptors, metadata) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
},
|
|
46
|
-
{}
|
|
47
|
-
);
|
|
33
|
+
const base = metadata && typeof metadata === "object" ? metadata : {};
|
|
34
|
+
return descriptors.reduce((acc, descriptor) => {
|
|
35
|
+
acc[descriptor.key] = normalizeFormValue(descriptor, base[descriptor.key]);
|
|
36
|
+
return acc;
|
|
37
|
+
}, {});
|
|
48
38
|
}
|
|
49
39
|
function buildMetadataPayload({
|
|
50
40
|
descriptors,
|
|
@@ -53,13 +43,12 @@ function buildMetadataPayload({
|
|
|
53
43
|
}) {
|
|
54
44
|
const base = originalMetadata && typeof originalMetadata === "object" ? { ...originalMetadata } : {};
|
|
55
45
|
descriptors.forEach((descriptor) => {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
if (typeof coerced === "undefined") {
|
|
46
|
+
const coerced = coerceMetadataValue(descriptor, values[descriptor.key]);
|
|
47
|
+
if (coerced === void 0) {
|
|
59
48
|
delete base[descriptor.key];
|
|
60
|
-
|
|
49
|
+
} else {
|
|
50
|
+
base[descriptor.key] = coerced;
|
|
61
51
|
}
|
|
62
|
-
base[descriptor.key] = coerced;
|
|
63
52
|
});
|
|
64
53
|
return base;
|
|
65
54
|
}
|
|
@@ -70,38 +59,27 @@ function hasMetadataChanges({
|
|
|
70
59
|
}) {
|
|
71
60
|
const next = buildMetadataPayload({ descriptors, values, originalMetadata });
|
|
72
61
|
const prev = originalMetadata && typeof originalMetadata === "object" ? originalMetadata : {};
|
|
73
|
-
return descriptors.some((
|
|
74
|
-
const prevValue = prev[descriptor.key];
|
|
75
|
-
const nextValue = next[descriptor.key];
|
|
76
|
-
return !isDeepEqual(prevValue, nextValue);
|
|
77
|
-
});
|
|
62
|
+
return descriptors.some(({ key }) => !isDeepEqual(prev[key], next[key]));
|
|
78
63
|
}
|
|
79
64
|
function validateValueForDescriptor(descriptor, value) {
|
|
80
65
|
if (descriptor.type === "number") {
|
|
81
|
-
if (value
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const numericValue = typeof value === "number" ? value : Number(String(value).trim());
|
|
85
|
-
if (Number.isNaN(numericValue)) {
|
|
86
|
-
return "Enter a valid number";
|
|
87
|
-
}
|
|
66
|
+
if (value == null || value === "") return void 0;
|
|
67
|
+
const num = typeof value === "number" ? value : Number(String(value).trim());
|
|
68
|
+
return isNaN(num) ? "Enter a valid number" : void 0;
|
|
88
69
|
}
|
|
89
70
|
if (descriptor.type === "file") {
|
|
90
|
-
if (!value)
|
|
91
|
-
return void 0;
|
|
92
|
-
}
|
|
71
|
+
if (!value) return void 0;
|
|
93
72
|
try {
|
|
94
73
|
new URL(String(value).trim());
|
|
95
|
-
|
|
74
|
+
return void 0;
|
|
75
|
+
} catch {
|
|
96
76
|
return "Enter a valid URL";
|
|
97
77
|
}
|
|
98
78
|
}
|
|
99
79
|
return void 0;
|
|
100
80
|
}
|
|
101
81
|
function normalizeFormValue(descriptor, currentValue) {
|
|
102
|
-
if (descriptor.type === "bool")
|
|
103
|
-
return Boolean(currentValue);
|
|
104
|
-
}
|
|
82
|
+
if (descriptor.type === "bool") return Boolean(currentValue);
|
|
105
83
|
if ((descriptor.type === "number" || descriptor.type === "text") && typeof currentValue === "number") {
|
|
106
84
|
return currentValue.toString();
|
|
107
85
|
}
|
|
@@ -111,78 +89,42 @@ function normalizeFormValue(descriptor, currentValue) {
|
|
|
111
89
|
return "";
|
|
112
90
|
}
|
|
113
91
|
function coerceMetadataValue(descriptor, value) {
|
|
114
|
-
if (value
|
|
115
|
-
return void 0;
|
|
116
|
-
}
|
|
92
|
+
if (value == null || value === "") return void 0;
|
|
117
93
|
if (descriptor.type === "bool") {
|
|
118
|
-
if (typeof value === "boolean")
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (typeof value === "string") {
|
|
125
|
-
const normalized = value.trim().toLowerCase();
|
|
126
|
-
if (!normalized) {
|
|
127
|
-
return void 0;
|
|
128
|
-
}
|
|
129
|
-
if (["true", "1", "yes", "y", "on"].includes(normalized)) {
|
|
130
|
-
return true;
|
|
131
|
-
}
|
|
132
|
-
if (["false", "0", "no", "n", "off"].includes(normalized)) {
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
94
|
+
if (typeof value === "boolean") return value;
|
|
95
|
+
if (typeof value === "number") return value !== 0;
|
|
96
|
+
const normalized = String(value).trim().toLowerCase();
|
|
97
|
+
if (!normalized) return void 0;
|
|
98
|
+
if (BOOLEAN_TRUES.has(normalized)) return true;
|
|
99
|
+
if (BOOLEAN_FALSES.has(normalized)) return false;
|
|
136
100
|
return Boolean(value);
|
|
137
101
|
}
|
|
138
102
|
if (descriptor.type === "number") {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
const parsed = Number(String(value).trim());
|
|
143
|
-
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
103
|
+
const num = typeof value === "number" ? value : Number(String(value).trim());
|
|
104
|
+
return isNaN(num) ? void 0 : num;
|
|
144
105
|
}
|
|
145
106
|
return String(value).trim();
|
|
146
107
|
}
|
|
147
|
-
function
|
|
148
|
-
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
const trimmed = value.trim();
|
|
152
|
-
return trimmed.length ? trimmed : null;
|
|
108
|
+
function normalizeKey(value) {
|
|
109
|
+
return typeof value === "string" ? value.trim() || void 0 : void 0;
|
|
153
110
|
}
|
|
154
|
-
function
|
|
155
|
-
if (typeof value !== "string")
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
111
|
+
function normalizeType(value) {
|
|
112
|
+
if (typeof value !== "string") return void 0;
|
|
158
113
|
const type = value.trim().toLowerCase();
|
|
159
|
-
return VALID_FIELD_TYPES.has(type) ? type :
|
|
114
|
+
return VALID_FIELD_TYPES.has(type) ? type : void 0;
|
|
160
115
|
}
|
|
161
|
-
function
|
|
162
|
-
|
|
163
|
-
return void 0;
|
|
164
|
-
}
|
|
165
|
-
const trimmed = value.trim();
|
|
166
|
-
return trimmed.length ? trimmed : void 0;
|
|
116
|
+
function normalizeLabel(value) {
|
|
117
|
+
return typeof value === "string" ? value.trim() || void 0 : void 0;
|
|
167
118
|
}
|
|
168
119
|
function isDeepEqual(a, b) {
|
|
169
|
-
if (a === b)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
return aKeys.every(
|
|
179
|
-
(key) => isDeepEqual(
|
|
180
|
-
a[key],
|
|
181
|
-
b[key]
|
|
182
|
-
)
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
return false;
|
|
120
|
+
if (a === b) return true;
|
|
121
|
+
if (!a || !b || typeof a !== "object" || typeof b !== "object") return false;
|
|
122
|
+
const aKeys = Object.keys(a);
|
|
123
|
+
const bKeys = Object.keys(b);
|
|
124
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
125
|
+
return aKeys.every(
|
|
126
|
+
(key) => isDeepEqual(a[key], b[key])
|
|
127
|
+
);
|
|
186
128
|
}
|
|
187
129
|
const CONFIG_ENDPOINT = "/admin/product-metadata-config";
|
|
188
130
|
const QUERY_KEY = ["medusa-product-helper", "metadata-config"];
|
|
@@ -5,45 +5,35 @@ import { useState, useEffect, useMemo, useRef } from "react";
|
|
|
5
5
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
6
6
|
const METADATA_FIELD_TYPES = ["number", "text", "file", "bool"];
|
|
7
7
|
const VALID_FIELD_TYPES = new Set(METADATA_FIELD_TYPES);
|
|
8
|
+
const BOOLEAN_TRUES = /* @__PURE__ */ new Set(["true", "1", "yes", "y", "on"]);
|
|
9
|
+
const BOOLEAN_FALSES = /* @__PURE__ */ new Set(["false", "0", "no", "n", "off"]);
|
|
8
10
|
function normalizeMetadataDescriptors(input) {
|
|
9
|
-
if (!Array.isArray(input))
|
|
10
|
-
return [];
|
|
11
|
-
}
|
|
11
|
+
if (!Array.isArray(input)) return [];
|
|
12
12
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
continue;
|
|
25
|
-
}
|
|
26
|
-
const label = getNormalizedLabel(item.label);
|
|
27
|
-
const filterable = typeof item.filterable === "boolean" ? item.filterable : Boolean(item.filterable);
|
|
28
|
-
normalized.push({
|
|
13
|
+
return input.filter(
|
|
14
|
+
(item) => item && typeof item === "object"
|
|
15
|
+
).map((item) => {
|
|
16
|
+
const key = normalizeKey(item.key);
|
|
17
|
+
const type = normalizeType(item.type);
|
|
18
|
+
const label = normalizeLabel(item.label);
|
|
19
|
+
const filterable = !!item.filterable;
|
|
20
|
+
return { key, type, label, filterable };
|
|
21
|
+
}).filter(({ key, type }) => key && type && !seenKeys.has(key)).map(({ key, type, label, filterable }) => {
|
|
22
|
+
seenKeys.add(key);
|
|
23
|
+
return {
|
|
29
24
|
key,
|
|
30
25
|
type,
|
|
31
|
-
...label
|
|
32
|
-
...filterable
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
return normalized;
|
|
26
|
+
...label && { label },
|
|
27
|
+
...filterable && { filterable: true }
|
|
28
|
+
};
|
|
29
|
+
});
|
|
37
30
|
}
|
|
38
31
|
function buildInitialFormState(descriptors, metadata) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
},
|
|
45
|
-
{}
|
|
46
|
-
);
|
|
32
|
+
const base = metadata && typeof metadata === "object" ? metadata : {};
|
|
33
|
+
return descriptors.reduce((acc, descriptor) => {
|
|
34
|
+
acc[descriptor.key] = normalizeFormValue(descriptor, base[descriptor.key]);
|
|
35
|
+
return acc;
|
|
36
|
+
}, {});
|
|
47
37
|
}
|
|
48
38
|
function buildMetadataPayload({
|
|
49
39
|
descriptors,
|
|
@@ -52,13 +42,12 @@ function buildMetadataPayload({
|
|
|
52
42
|
}) {
|
|
53
43
|
const base = originalMetadata && typeof originalMetadata === "object" ? { ...originalMetadata } : {};
|
|
54
44
|
descriptors.forEach((descriptor) => {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
if (typeof coerced === "undefined") {
|
|
45
|
+
const coerced = coerceMetadataValue(descriptor, values[descriptor.key]);
|
|
46
|
+
if (coerced === void 0) {
|
|
58
47
|
delete base[descriptor.key];
|
|
59
|
-
|
|
48
|
+
} else {
|
|
49
|
+
base[descriptor.key] = coerced;
|
|
60
50
|
}
|
|
61
|
-
base[descriptor.key] = coerced;
|
|
62
51
|
});
|
|
63
52
|
return base;
|
|
64
53
|
}
|
|
@@ -69,38 +58,27 @@ function hasMetadataChanges({
|
|
|
69
58
|
}) {
|
|
70
59
|
const next = buildMetadataPayload({ descriptors, values, originalMetadata });
|
|
71
60
|
const prev = originalMetadata && typeof originalMetadata === "object" ? originalMetadata : {};
|
|
72
|
-
return descriptors.some((
|
|
73
|
-
const prevValue = prev[descriptor.key];
|
|
74
|
-
const nextValue = next[descriptor.key];
|
|
75
|
-
return !isDeepEqual(prevValue, nextValue);
|
|
76
|
-
});
|
|
61
|
+
return descriptors.some(({ key }) => !isDeepEqual(prev[key], next[key]));
|
|
77
62
|
}
|
|
78
63
|
function validateValueForDescriptor(descriptor, value) {
|
|
79
64
|
if (descriptor.type === "number") {
|
|
80
|
-
if (value
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const numericValue = typeof value === "number" ? value : Number(String(value).trim());
|
|
84
|
-
if (Number.isNaN(numericValue)) {
|
|
85
|
-
return "Enter a valid number";
|
|
86
|
-
}
|
|
65
|
+
if (value == null || value === "") return void 0;
|
|
66
|
+
const num = typeof value === "number" ? value : Number(String(value).trim());
|
|
67
|
+
return isNaN(num) ? "Enter a valid number" : void 0;
|
|
87
68
|
}
|
|
88
69
|
if (descriptor.type === "file") {
|
|
89
|
-
if (!value)
|
|
90
|
-
return void 0;
|
|
91
|
-
}
|
|
70
|
+
if (!value) return void 0;
|
|
92
71
|
try {
|
|
93
72
|
new URL(String(value).trim());
|
|
94
|
-
|
|
73
|
+
return void 0;
|
|
74
|
+
} catch {
|
|
95
75
|
return "Enter a valid URL";
|
|
96
76
|
}
|
|
97
77
|
}
|
|
98
78
|
return void 0;
|
|
99
79
|
}
|
|
100
80
|
function normalizeFormValue(descriptor, currentValue) {
|
|
101
|
-
if (descriptor.type === "bool")
|
|
102
|
-
return Boolean(currentValue);
|
|
103
|
-
}
|
|
81
|
+
if (descriptor.type === "bool") return Boolean(currentValue);
|
|
104
82
|
if ((descriptor.type === "number" || descriptor.type === "text") && typeof currentValue === "number") {
|
|
105
83
|
return currentValue.toString();
|
|
106
84
|
}
|
|
@@ -110,78 +88,42 @@ function normalizeFormValue(descriptor, currentValue) {
|
|
|
110
88
|
return "";
|
|
111
89
|
}
|
|
112
90
|
function coerceMetadataValue(descriptor, value) {
|
|
113
|
-
if (value
|
|
114
|
-
return void 0;
|
|
115
|
-
}
|
|
91
|
+
if (value == null || value === "") return void 0;
|
|
116
92
|
if (descriptor.type === "bool") {
|
|
117
|
-
if (typeof value === "boolean")
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (typeof value === "string") {
|
|
124
|
-
const normalized = value.trim().toLowerCase();
|
|
125
|
-
if (!normalized) {
|
|
126
|
-
return void 0;
|
|
127
|
-
}
|
|
128
|
-
if (["true", "1", "yes", "y", "on"].includes(normalized)) {
|
|
129
|
-
return true;
|
|
130
|
-
}
|
|
131
|
-
if (["false", "0", "no", "n", "off"].includes(normalized)) {
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
93
|
+
if (typeof value === "boolean") return value;
|
|
94
|
+
if (typeof value === "number") return value !== 0;
|
|
95
|
+
const normalized = String(value).trim().toLowerCase();
|
|
96
|
+
if (!normalized) return void 0;
|
|
97
|
+
if (BOOLEAN_TRUES.has(normalized)) return true;
|
|
98
|
+
if (BOOLEAN_FALSES.has(normalized)) return false;
|
|
135
99
|
return Boolean(value);
|
|
136
100
|
}
|
|
137
101
|
if (descriptor.type === "number") {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
const parsed = Number(String(value).trim());
|
|
142
|
-
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
102
|
+
const num = typeof value === "number" ? value : Number(String(value).trim());
|
|
103
|
+
return isNaN(num) ? void 0 : num;
|
|
143
104
|
}
|
|
144
105
|
return String(value).trim();
|
|
145
106
|
}
|
|
146
|
-
function
|
|
147
|
-
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
const trimmed = value.trim();
|
|
151
|
-
return trimmed.length ? trimmed : null;
|
|
107
|
+
function normalizeKey(value) {
|
|
108
|
+
return typeof value === "string" ? value.trim() || void 0 : void 0;
|
|
152
109
|
}
|
|
153
|
-
function
|
|
154
|
-
if (typeof value !== "string")
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
110
|
+
function normalizeType(value) {
|
|
111
|
+
if (typeof value !== "string") return void 0;
|
|
157
112
|
const type = value.trim().toLowerCase();
|
|
158
|
-
return VALID_FIELD_TYPES.has(type) ? type :
|
|
113
|
+
return VALID_FIELD_TYPES.has(type) ? type : void 0;
|
|
159
114
|
}
|
|
160
|
-
function
|
|
161
|
-
|
|
162
|
-
return void 0;
|
|
163
|
-
}
|
|
164
|
-
const trimmed = value.trim();
|
|
165
|
-
return trimmed.length ? trimmed : void 0;
|
|
115
|
+
function normalizeLabel(value) {
|
|
116
|
+
return typeof value === "string" ? value.trim() || void 0 : void 0;
|
|
166
117
|
}
|
|
167
118
|
function isDeepEqual(a, b) {
|
|
168
|
-
if (a === b)
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
return aKeys.every(
|
|
178
|
-
(key) => isDeepEqual(
|
|
179
|
-
a[key],
|
|
180
|
-
b[key]
|
|
181
|
-
)
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
return false;
|
|
119
|
+
if (a === b) return true;
|
|
120
|
+
if (!a || !b || typeof a !== "object" || typeof b !== "object") return false;
|
|
121
|
+
const aKeys = Object.keys(a);
|
|
122
|
+
const bKeys = Object.keys(b);
|
|
123
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
124
|
+
return aKeys.every(
|
|
125
|
+
(key) => isDeepEqual(a[key], b[key])
|
|
126
|
+
);
|
|
185
127
|
}
|
|
186
128
|
const CONFIG_ENDPOINT = "/admin/product-metadata-config";
|
|
187
129
|
const QUERY_KEY = ["medusa-product-helper", "metadata-config"];
|
|
@@ -35,6 +35,90 @@ const GET = async (req, res) => {
|
|
|
35
35
|
// Validate query parameters using zod schema
|
|
36
36
|
// This ensures type safety and catches invalid inputs early
|
|
37
37
|
const validatedQuery = validators_1.StoreProductHelperFilterQuerySchema.parse(normalizedQuery);
|
|
38
|
+
// Extract region_id (required field)
|
|
39
|
+
const regionId = validatedQuery.region_id;
|
|
40
|
+
if (!regionId) {
|
|
41
|
+
res.status(400).json({
|
|
42
|
+
message: "region_id is required",
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// Fetch region to get currency_code
|
|
47
|
+
const query = req.scope.resolve(utils_1.ContainerRegistrationKeys.QUERY);
|
|
48
|
+
const regionResult = await query.graph({
|
|
49
|
+
entity: "region",
|
|
50
|
+
fields: ["id", "currency_code"],
|
|
51
|
+
filters: { id: regionId },
|
|
52
|
+
});
|
|
53
|
+
console.log(`[ProductHelperRoute] Region fetch result:`, {
|
|
54
|
+
hasData: !!regionResult.data,
|
|
55
|
+
dataLength: Array.isArray(regionResult.data) ? regionResult.data.length : 0,
|
|
56
|
+
regionId
|
|
57
|
+
});
|
|
58
|
+
const region = Array.isArray(regionResult.data) ? regionResult.data[0] : null;
|
|
59
|
+
if (!region) {
|
|
60
|
+
console.error(`[ProductHelperRoute] Region not found: ${regionId}`);
|
|
61
|
+
res.status(404).json({
|
|
62
|
+
message: `Region with id ${regionId} not found`,
|
|
63
|
+
});
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
// Extract currency_code and validate it
|
|
67
|
+
const currencyCode = region.currency_code;
|
|
68
|
+
console.log(`[ProductHelperRoute] Region currency_code:`, {
|
|
69
|
+
currencyCode,
|
|
70
|
+
type: typeof currencyCode,
|
|
71
|
+
trimmed: typeof currencyCode === "string" ? currencyCode.trim() : null
|
|
72
|
+
});
|
|
73
|
+
if (!currencyCode || typeof currencyCode !== "string" || currencyCode.trim().length === 0) {
|
|
74
|
+
console.error(`[ProductHelperRoute] Invalid currency_code for region ${regionId}:`, currencyCode);
|
|
75
|
+
res.status(400).json({
|
|
76
|
+
message: `Region with id ${regionId} has no valid currency_code`,
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const trimmedCurrencyCode = currencyCode.trim();
|
|
81
|
+
// Log req.pricingContext if it exists
|
|
82
|
+
if (req.pricingContext) {
|
|
83
|
+
console.log(`[ProductHelperRoute] req.pricingContext exists:`, {
|
|
84
|
+
hasCurrencyCode: !!req.pricingContext.currency_code,
|
|
85
|
+
currencyCode: req.pricingContext.currency_code,
|
|
86
|
+
keys: Object.keys(req.pricingContext)
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
console.log(`[ProductHelperRoute] req.pricingContext does not exist`);
|
|
91
|
+
}
|
|
92
|
+
// Build pricing context with region_id and currency_code
|
|
93
|
+
// Don't trust req.pricingContext - always build fresh with currency_code from region
|
|
94
|
+
// Only preserve other properties from req.pricingContext if currency_code is valid
|
|
95
|
+
const pricingContext = {
|
|
96
|
+
region_id: regionId,
|
|
97
|
+
currency_code: trimmedCurrencyCode, // Always set currency_code from region first
|
|
98
|
+
};
|
|
99
|
+
// Only spread req.pricingContext if it has a valid currency_code
|
|
100
|
+
// Otherwise, we might inherit an invalid or missing currency_code
|
|
101
|
+
if (req.pricingContext) {
|
|
102
|
+
const existingCurrencyCode = req.pricingContext.currency_code;
|
|
103
|
+
if (typeof existingCurrencyCode === "string" && existingCurrencyCode.trim().length > 0) {
|
|
104
|
+
// Preserve other properties from req.pricingContext, but use our currency_code
|
|
105
|
+
const existingContext = req.pricingContext;
|
|
106
|
+
Object.keys(existingContext).forEach(key => {
|
|
107
|
+
if (key !== "currency_code" && key !== "region_id") {
|
|
108
|
+
pricingContext[key] = existingContext[key];
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
console.log(`[ProductHelperRoute] Preserved properties from req.pricingContext (excluding currency_code and region_id)`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
console.warn(`[ProductHelperRoute] req.pricingContext exists but has invalid currency_code, ignoring it`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
console.log(`[ProductHelperRoute] Final pricing context:`, {
|
|
118
|
+
region_id: pricingContext.region_id,
|
|
119
|
+
currency_code: pricingContext.currency_code,
|
|
120
|
+
keys: Object.keys(pricingContext)
|
|
121
|
+
});
|
|
38
122
|
// Resolve plugin options
|
|
39
123
|
const configModule = req.scope.resolve(utils_1.ContainerRegistrationKeys.CONFIG_MODULE);
|
|
40
124
|
const options = (0, product_helper_options_1.resolveProductHelperOptions)(configModule);
|
|
@@ -44,16 +128,62 @@ const GET = async (req, res) => {
|
|
|
44
128
|
const { products, count, metadata } = await filterService.applyFilterPlanWithMedusaContext({
|
|
45
129
|
queryConfig: req.queryConfig,
|
|
46
130
|
filterableFields: req.filterableFields || {},
|
|
47
|
-
pricingContext
|
|
48
|
-
query: validatedQuery,
|
|
131
|
+
pricingContext,
|
|
132
|
+
query: { ...validatedQuery, currency_code: currencyCode },
|
|
49
133
|
}, options);
|
|
50
|
-
//
|
|
51
|
-
|
|
134
|
+
// Calculate min_price and max_price if price_range=true
|
|
135
|
+
// Only consider prices in the selected region's currency
|
|
136
|
+
let minPrice = null;
|
|
137
|
+
let maxPrice = null;
|
|
138
|
+
if (validatedQuery.price_range === true) {
|
|
139
|
+
for (const product of products) {
|
|
140
|
+
if (!product.variants || !Array.isArray(product.variants)) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
for (const variant of product.variants) {
|
|
144
|
+
// Prioritize calculated_price if available (includes price list overrides)
|
|
145
|
+
let priceToUse;
|
|
146
|
+
if (variant.calculated_price?.calculated_amount !== undefined &&
|
|
147
|
+
variant.calculated_price?.currency_code === currencyCode) {
|
|
148
|
+
priceToUse = variant.calculated_price.calculated_amount;
|
|
149
|
+
}
|
|
150
|
+
else if (variant.prices && Array.isArray(variant.prices)) {
|
|
151
|
+
// Find price matching the selected currency
|
|
152
|
+
const matchingPrice = variant.prices.find((p) => p.currency_code === currencyCode);
|
|
153
|
+
if (matchingPrice?.amount !== undefined) {
|
|
154
|
+
priceToUse = matchingPrice.amount;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (priceToUse !== undefined && typeof priceToUse === "number") {
|
|
158
|
+
if (minPrice === null || priceToUse < minPrice) {
|
|
159
|
+
minPrice = priceToUse;
|
|
160
|
+
}
|
|
161
|
+
if (maxPrice === null || priceToUse > maxPrice) {
|
|
162
|
+
maxPrice = priceToUse;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Build response object
|
|
169
|
+
const response = {
|
|
52
170
|
products: products,
|
|
53
171
|
count: metadata?.count || count || products.length,
|
|
54
172
|
offset: metadata?.skip || 0,
|
|
55
|
-
limit
|
|
56
|
-
|
|
173
|
+
// Ensure limit is never 0 - use metadata take, validated query limit, or default to 20
|
|
174
|
+
limit: (metadata?.take && metadata.take > 0)
|
|
175
|
+
? metadata.take
|
|
176
|
+
: (validatedQuery.limit && validatedQuery.limit > 0)
|
|
177
|
+
? validatedQuery.limit
|
|
178
|
+
: (products.length > 0 ? products.length : 20),
|
|
179
|
+
};
|
|
180
|
+
// Add price range fields if price_range=true
|
|
181
|
+
if (validatedQuery.price_range === true) {
|
|
182
|
+
response.min_price = minPrice;
|
|
183
|
+
response.max_price = maxPrice;
|
|
184
|
+
}
|
|
185
|
+
// Return response matching Medusa's format
|
|
186
|
+
res.json(response);
|
|
57
187
|
}
|
|
58
188
|
catch (error) {
|
|
59
189
|
// Handle validation errors
|
|
@@ -73,4 +203,4 @@ const GET = async (req, res) => {
|
|
|
73
203
|
}
|
|
74
204
|
};
|
|
75
205
|
exports.GET = GET;
|
|
76
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
206
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL3N0b3JlL3Byb2R1Y3QtaGVscGVyL3Byb2R1Y3RzL3JvdXRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUVBLHFEQUFxRTtBQUVyRSxzRkFBdUY7QUFDdkYsd0ZBQWtGO0FBQ2xGLGlFQUFxRTtBQUNyRSw2Q0FFcUI7QUFnQnJCOzs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBbUJHO0FBQ0ksTUFBTSxHQUFHLEdBQUcsS0FBSyxFQUN0QixHQUF1QixFQUN2QixHQUF1RCxFQUN2RCxFQUFFO0lBQ0YsSUFBSSxDQUFDO1FBQ0gseUVBQXlFO1FBQ3pFLDhEQUE4RDtRQUM5RCxNQUFNLFFBQVEsR0FBRyxHQUFHLENBQUMsS0FBSyxJQUFJLEVBQUUsQ0FBQTtRQUNoQyxNQUFNLGVBQWUsR0FBRyxJQUFBLG1DQUFvQixFQUMxQyxRQUFtQyxDQUNwQyxDQUFBO1FBRUQsNkNBQTZDO1FBQzdDLDREQUE0RDtRQUM1RCxNQUFNLGNBQWMsR0FBRyxnREFBbUMsQ0FBQyxLQUFLLENBQzlELGVBQWUsQ0FDaEIsQ0FBQTtRQUVELHFDQUFxQztRQUNyQyxNQUFNLFFBQVEsR0FBRyxjQUFjLENBQUMsU0FBUyxDQUFBO1FBQ3pDLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztZQUNkLEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDO2dCQUNuQixPQUFPLEVBQUUsdUJBQXVCO2FBQ2dCLENBQUMsQ0FBQTtZQUNuRCxPQUFNO1FBQ1IsQ0FBQztRQUVELG9DQUFvQztRQUNwQyxNQUFNLEtBQUssR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBUSxpQ0FBeUIsQ0FBQyxLQUFLLENBQUMsQ0FBQTtRQUN2RSxNQUFNLFlBQVksR0FBRyxNQUFNLEtBQUssQ0FBQyxLQUFLLENBQUM7WUFDckMsTUFBTSxFQUFFLFFBQVE7WUFDaEIs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
|