@unchainedshop/api 4.0.0-rc.5 → 4.0.0-rc.7
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/README.md +3 -1
- package/lib/errors.d.ts +2 -0
- package/lib/errors.d.ts.map +1 -1
- package/lib/errors.js +2 -0
- package/lib/errors.js.map +1 -1
- package/lib/loaders/index.d.ts +5 -0
- package/lib/loaders/index.d.ts.map +1 -1
- package/lib/loaders/index.js +2 -0
- package/lib/loaders/index.js.map +1 -1
- package/lib/loaders/productProxiesLoader.d.ts +10 -0
- package/lib/loaders/productProxiesLoader.d.ts.map +1 -0
- package/lib/loaders/productProxiesLoader.js +30 -0
- package/lib/loaders/productProxiesLoader.js.map +1 -0
- package/lib/mcp/tools/assortment/addAssortmentMediaUploadHandler.d.ts.map +1 -1
- package/lib/mcp/tools/assortment/addAssortmentMediaUploadHandler.js +2 -1
- package/lib/mcp/tools/assortment/addAssortmentMediaUploadHandler.js.map +1 -1
- package/lib/mcp/tools/assortment/assortmentMediaHandler.d.ts +32 -0
- package/lib/mcp/tools/assortment/assortmentMediaHandler.d.ts.map +1 -0
- package/lib/mcp/tools/assortment/assortmentMediaHandler.js +48 -0
- package/lib/mcp/tools/assortment/assortmentMediaHandler.js.map +1 -0
- package/lib/mcp/tools/assortment/index.d.ts.map +1 -1
- package/lib/mcp/tools/assortment/index.js +3 -1
- package/lib/mcp/tools/assortment/index.js.map +1 -1
- package/lib/mcp/tools/assortment/removeAssortmentMediaHandler.d.ts.map +1 -1
- package/lib/mcp/tools/assortment/removeAssortmentMediaHandler.js +3 -1
- package/lib/mcp/tools/assortment/removeAssortmentMediaHandler.js.map +1 -1
- package/lib/mcp/tools/assortment/reorderAssortmentMediaHandler.js +2 -2
- package/lib/mcp/tools/assortment/reorderAssortmentMediaHandler.js.map +1 -1
- package/lib/mcp/tools/assortment/translatedAssortmentMediaTextsHandler.d.ts.map +1 -1
- package/lib/mcp/tools/assortment/translatedAssortmentMediaTextsHandler.js +7 -1
- package/lib/mcp/tools/assortment/translatedAssortmentMediaTextsHandler.js.map +1 -1
- package/lib/mcp/tools/assortment/updateAssortmentMediaTextsHandler.d.ts.map +1 -1
- package/lib/mcp/tools/assortment/updateAssortmentMediaTextsHandler.js +4 -1
- package/lib/mcp/tools/assortment/updateAssortmentMediaTextsHandler.js.map +1 -1
- package/lib/mcp/tools/product/addProductAssignmentHandler.d.ts.map +1 -1
- package/lib/mcp/tools/product/addProductAssignmentHandler.js +1 -1
- package/lib/mcp/tools/product/addProductAssignmentHandler.js.map +1 -1
- package/lib/mcp/tools/product/addProductBundleItemHandler.d.ts +48 -0
- package/lib/mcp/tools/product/addProductBundleItemHandler.d.ts.map +1 -0
- package/lib/mcp/tools/product/addProductBundleItemHandler.js +52 -0
- package/lib/mcp/tools/product/addProductBundleItemHandler.js.map +1 -0
- package/lib/mcp/tools/product/addProductMediaUploadHandler.d.ts.map +1 -1
- package/lib/mcp/tools/product/addProductMediaUploadHandler.js +3 -2
- package/lib/mcp/tools/product/addProductMediaUploadHandler.js.map +1 -1
- package/lib/mcp/tools/product/catalogPriceHandler.d.ts +28 -0
- package/lib/mcp/tools/product/catalogPriceHandler.d.ts.map +1 -0
- package/lib/mcp/tools/product/catalogPriceHandler.js +57 -0
- package/lib/mcp/tools/product/catalogPriceHandler.js.map +1 -0
- package/lib/mcp/tools/product/index.d.ts.map +1 -1
- package/lib/mcp/tools/product/index.js +27 -3
- package/lib/mcp/tools/product/index.js.map +1 -1
- package/lib/mcp/tools/product/productAssignmentsHandler.d.ts +24 -0
- package/lib/mcp/tools/product/productAssignmentsHandler.d.ts.map +1 -0
- package/lib/mcp/tools/product/productAssignmentsHandler.js +79 -0
- package/lib/mcp/tools/product/productAssignmentsHandler.js.map +1 -0
- package/lib/mcp/tools/product/productBundleItemsHandler.d.ts +20 -0
- package/lib/mcp/tools/product/productBundleItemsHandler.d.ts.map +1 -0
- package/lib/mcp/tools/product/productBundleItemsHandler.js +45 -0
- package/lib/mcp/tools/product/productBundleItemsHandler.js.map +1 -0
- package/lib/mcp/tools/product/productMediaHandler.d.ts +32 -0
- package/lib/mcp/tools/product/productMediaHandler.d.ts.map +1 -0
- package/lib/mcp/tools/product/productMediaHandler.js +46 -0
- package/lib/mcp/tools/product/productMediaHandler.js.map +1 -0
- package/lib/mcp/tools/product/productSiblingsHandler.d.ts +36 -0
- package/lib/mcp/tools/product/productSiblingsHandler.d.ts.map +1 -0
- package/lib/mcp/tools/product/productSiblingsHandler.js +66 -0
- package/lib/mcp/tools/product/productSiblingsHandler.js.map +1 -0
- package/lib/mcp/tools/product/productVariationsHandler.d.ts +20 -0
- package/lib/mcp/tools/product/productVariationsHandler.d.ts.map +1 -0
- package/lib/mcp/tools/product/productVariationsHandler.js +47 -0
- package/lib/mcp/tools/product/productVariationsHandler.js.map +1 -0
- package/lib/mcp/tools/product/removeProductBundleItemHandler.d.ts +24 -0
- package/lib/mcp/tools/product/removeProductBundleItemHandler.d.ts.map +1 -0
- package/lib/mcp/tools/product/removeProductBundleItemHandler.js +46 -0
- package/lib/mcp/tools/product/removeProductBundleItemHandler.js.map +1 -0
- package/lib/mcp/tools/product/removeProductMediaHandler.js +1 -1
- package/lib/mcp/tools/product/removeProductMediaHandler.js.map +1 -1
- package/lib/mcp/tools/product/reorderProductMediaHandler.js +2 -2
- package/lib/mcp/tools/product/reorderProductMediaHandler.js.map +1 -1
- package/lib/mcp/tools/product/simulatedPriceHandler.d.ts +70 -0
- package/lib/mcp/tools/product/simulatedPriceHandler.d.ts.map +1 -0
- package/lib/mcp/tools/product/simulatedPriceHandler.js +74 -0
- package/lib/mcp/tools/product/simulatedPriceHandler.js.map +1 -0
- package/lib/mcp/tools/product/simulatedPriceRangeHandler.d.ts +74 -0
- package/lib/mcp/tools/product/simulatedPriceRangeHandler.d.ts.map +1 -0
- package/lib/mcp/tools/product/simulatedPriceRangeHandler.js +103 -0
- package/lib/mcp/tools/product/simulatedPriceRangeHandler.js.map +1 -0
- package/lib/mcp/tools/product/translatedProductMediaTextsHandler.d.ts.map +1 -1
- package/lib/mcp/tools/product/translatedProductMediaTextsHandler.js +7 -1
- package/lib/mcp/tools/product/translatedProductMediaTextsHandler.js.map +1 -1
- package/lib/mcp/tools/product/translatedProductVariationTextsHandler.d.ts.map +1 -1
- package/lib/mcp/tools/product/translatedProductVariationTextsHandler.js +7 -1
- package/lib/mcp/tools/product/translatedProductVariationTextsHandler.js.map +1 -1
- package/lib/mcp/tools/product/updateProductMediaTextsHandler.js +1 -1
- package/lib/mcp/tools/product/updateProductMediaTextsHandler.js.map +1 -1
- package/lib/mcp/tools/product/updateProductPlanHandler.d.ts +47 -26
- package/lib/mcp/tools/product/updateProductPlanHandler.d.ts.map +1 -1
- package/lib/mcp/tools/product/updateProductPlanHandler.js +23 -7
- package/lib/mcp/tools/product/updateProductPlanHandler.js.map +1 -1
- package/lib/mcp/tools/product/updateProductVariationTextsHandler.d.ts +73 -0
- package/lib/mcp/tools/product/updateProductVariationTextsHandler.d.ts.map +1 -0
- package/lib/mcp/tools/product/updateProductVariationTextsHandler.js +52 -0
- package/lib/mcp/tools/product/updateProductVariationTextsHandler.js.map +1 -0
- package/lib/mcp/tools/product/variationProductsHandler.d.ts +52 -0
- package/lib/mcp/tools/product/variationProductsHandler.d.ts.map +1 -0
- package/lib/mcp/tools/product/variationProductsHandler.js +54 -0
- package/lib/mcp/tools/product/variationProductsHandler.js.map +1 -0
- package/lib/mcp/tools/shopInfoHandler.d.ts.map +1 -1
- package/lib/mcp/tools/shopInfoHandler.js +4 -2
- package/lib/mcp/tools/shopInfoHandler.js.map +1 -1
- package/lib/mcp/utils/getNormalizedProductDetails.d.ts +1 -1
- package/lib/mcp/utils/getNormalizedProductDetails.d.ts.map +1 -1
- package/lib/mcp/utils/getNormalizedProductDetails.js +25 -0
- package/lib/mcp/utils/getNormalizedProductDetails.js.map +1 -1
- package/lib/resolvers/mutations/filters/createFilter.d.ts.map +1 -1
- package/lib/resolvers/mutations/filters/createFilter.js +14 -5
- package/lib/resolvers/mutations/filters/createFilter.js.map +1 -1
- package/lib/resolvers/mutations/products/createProductBundleItem.d.ts.map +1 -1
- package/lib/resolvers/mutations/products/createProductBundleItem.js +7 -2
- package/lib/resolvers/mutations/products/createProductBundleItem.js.map +1 -1
- package/lib/resolvers/type/index.d.ts +9 -0
- package/lib/resolvers/type/index.d.ts.map +1 -1
- package/lib/resolvers/type/index.js +2 -0
- package/lib/resolvers/type/index.js.map +1 -1
- package/lib/resolvers/type/product/product-bundle-types.d.ts +1 -0
- package/lib/resolvers/type/product/product-bundle-types.d.ts.map +1 -1
- package/lib/resolvers/type/product/product-configurable-or-bundle-product-types.d.ts +5 -0
- package/lib/resolvers/type/product/product-configurable-or-bundle-product-types.d.ts.map +1 -0
- package/lib/resolvers/type/product/product-configurable-or-bundle-product-types.js +9 -0
- package/lib/resolvers/type/product/product-configurable-or-bundle-product-types.js.map +1 -0
- package/lib/resolvers/type/product/product-configurable-types.d.ts +1 -0
- package/lib/resolvers/type/product/product-configurable-types.d.ts.map +1 -1
- package/lib/resolvers/type/product/product-plan-types.d.ts +1 -0
- package/lib/resolvers/type/product/product-plan-types.d.ts.map +1 -1
- package/lib/resolvers/type/product/product-simple-types.d.ts +1 -0
- package/lib/resolvers/type/product/product-simple-types.d.ts.map +1 -1
- package/lib/resolvers/type/product/product-tokenized-types.d.ts +1 -0
- package/lib/resolvers/type/product/product-tokenized-types.d.ts.map +1 -1
- package/lib/resolvers/type/product/product-types.d.ts +1 -0
- package/lib/resolvers/type/product/product-types.d.ts.map +1 -1
- package/lib/resolvers/type/product/product-types.js +3 -0
- package/lib/resolvers/type/product/product-types.js.map +1 -1
- package/lib/schema/types/product/bundle-product.d.ts.map +1 -1
- package/lib/schema/types/product/bundle-product.js +2 -1
- package/lib/schema/types/product/bundle-product.js.map +1 -1
- package/lib/schema/types/product/configurable-product.d.ts.map +1 -1
- package/lib/schema/types/product/configurable-product.js +1 -0
- package/lib/schema/types/product/configurable-product.js.map +1 -1
- package/lib/schema/types/product/plan-product.d.ts.map +1 -1
- package/lib/schema/types/product/plan-product.js +1 -0
- package/lib/schema/types/product/plan-product.js.map +1 -1
- package/lib/schema/types/product/product.d.ts.map +1 -1
- package/lib/schema/types/product/product.js +3 -0
- package/lib/schema/types/product/product.js.map +1 -1
- package/lib/schema/types/product/simple-product.d.ts.map +1 -1
- package/lib/schema/types/product/simple-product.js +1 -0
- package/lib/schema/types/product/simple-product.js.map +1 -1
- package/lib/schema/types/product/tokenized-product.d.ts.map +1 -1
- package/lib/schema/types/product/tokenized-product.js +1 -0
- package/lib/schema/types/product/tokenized-product.js.map +1 -1
- package/package.json +6 -6
- package/src/errors.ts +10 -0
- package/src/loaders/index.ts +2 -0
- package/src/loaders/productProxiesLoader.ts +37 -0
- package/src/mcp/tools/assortment/addAssortmentMediaUploadHandler.ts +2 -1
- package/src/mcp/tools/assortment/assortmentMediaHandler.ts +57 -0
- package/src/mcp/tools/assortment/index.ts +9 -1
- package/src/mcp/tools/assortment/removeAssortmentMediaHandler.ts +3 -1
- package/src/mcp/tools/assortment/reorderAssortmentMediaHandler.ts +2 -2
- package/src/mcp/tools/assortment/translatedAssortmentMediaTextsHandler.ts +8 -1
- package/src/mcp/tools/assortment/updateAssortmentMediaTextsHandler.ts +4 -1
- package/src/mcp/tools/product/addProductAssignmentHandler.ts +3 -1
- package/src/mcp/tools/product/addProductBundleItemHandler.ts +64 -0
- package/src/mcp/tools/product/addProductMediaUploadHandler.ts +3 -2
- package/src/mcp/tools/product/catalogPriceHandler.ts +61 -0
- package/src/mcp/tools/product/index.ts +104 -3
- package/src/mcp/tools/product/productAssignmentsHandler.ts +86 -0
- package/src/mcp/tools/product/productBundleItemsHandler.ts +50 -0
- package/src/mcp/tools/product/productMediaHandler.ts +53 -0
- package/src/mcp/tools/product/productSiblingsHandler.ts +82 -0
- package/src/mcp/tools/product/productVariationsHandler.ts +53 -0
- package/src/mcp/tools/product/removeProductBundleItemHandler.ts +55 -0
- package/src/mcp/tools/product/removeProductMediaHandler.ts +1 -1
- package/src/mcp/tools/product/reorderProductMediaHandler.ts +2 -2
- package/src/mcp/tools/product/simulatedPriceHandler.ts +82 -0
- package/src/mcp/tools/product/simulatedPriceRangeHandler.ts +126 -0
- package/src/mcp/tools/product/translatedProductMediaTextsHandler.ts +6 -2
- package/src/mcp/tools/product/translatedProductVariationTextsHandler.ts +6 -1
- package/src/mcp/tools/product/updateProductMediaTextsHandler.ts +1 -1
- package/src/mcp/tools/product/updateProductPlanHandler.ts +37 -7
- package/src/mcp/tools/product/updateProductVariationTextsHandler.ts +65 -0
- package/src/mcp/tools/product/variationProductsHandler.ts +61 -0
- package/src/mcp/tools/shopInfoHandler.ts +4 -2
- package/src/mcp/utils/getNormalizedProductDetails.ts +28 -0
- package/src/resolvers/mutations/filters/createFilter.ts +11 -6
- package/src/resolvers/mutations/products/createProductBundleItem.ts +13 -3
- package/src/resolvers/type/index.ts +2 -0
- package/src/resolvers/type/product/product-configurable-or-bundle-product-types.ts +10 -0
- package/src/resolvers/type/product/product-types.ts +4 -0
- package/src/schema/types/product/bundle-product.ts +2 -1
- package/src/schema/types/product/configurable-product.ts +1 -0
- package/src/schema/types/product/plan-product.ts +1 -0
- package/src/schema/types/product/product.ts +3 -0
- package/src/schema/types/product/simple-product.ts +1 -0
- package/src/schema/types/product/tokenized-product.ts +1 -0
package/src/errors.ts
CHANGED
|
@@ -230,6 +230,11 @@ export const CyclicAssortmentLinkNotSupportedError = createError(
|
|
|
230
230
|
'Cyclic assortment link detected, make sure child assortment is not assigned as a parent on the assortment graph',
|
|
231
231
|
);
|
|
232
232
|
|
|
233
|
+
export const CyclicProductBundlingNotSupportedError = createError(
|
|
234
|
+
'CyclicBundlingNotSupportedError',
|
|
235
|
+
'Cyclic bundling detected, make sure bundled product is not the same as the bundle product itself',
|
|
236
|
+
);
|
|
237
|
+
|
|
233
238
|
export const EmailAlreadyExistsError = createError(
|
|
234
239
|
'EmailAlreadyExists',
|
|
235
240
|
'Email already exists or is invalid',
|
|
@@ -319,3 +324,8 @@ export const ImpersonatingAdminUserError = createError(
|
|
|
319
324
|
'ImpersonatingAdminUserError',
|
|
320
325
|
'Can not impersonate a admin user account',
|
|
321
326
|
);
|
|
327
|
+
|
|
328
|
+
export const DuplicateFilterKeyError = createError(
|
|
329
|
+
'DuplicateFilterKeyError',
|
|
330
|
+
'Key already registered for another filter',
|
|
331
|
+
);
|
package/src/loaders/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import productLoaderBySKU from './productLoaderBySKU.js';
|
|
|
14
14
|
import productTextLoader from './productTextLoader.js';
|
|
15
15
|
import productMediaTextLoader from './productMediaTextLoader.js';
|
|
16
16
|
import productMediasLoader from './productMediasLoader.js';
|
|
17
|
+
import productProxiesLoader from './productProxiesLoader.js';
|
|
17
18
|
import fileLoader from './fileLoader.js';
|
|
18
19
|
import userLoader from './userLoader.js';
|
|
19
20
|
import countryLoader from './countryLoader.js';
|
|
@@ -42,6 +43,7 @@ const loaders = (unchainedAPI: UnchainedCore) => {
|
|
|
42
43
|
productTextLoader: productTextLoader(unchainedAPI),
|
|
43
44
|
productMediaTextLoader: productMediaTextLoader(unchainedAPI),
|
|
44
45
|
productMediasLoader: productMediasLoader(unchainedAPI),
|
|
46
|
+
productProxiesLoader: productProxiesLoader(unchainedAPI),
|
|
45
47
|
|
|
46
48
|
fileLoader: fileLoader(unchainedAPI),
|
|
47
49
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { UnchainedCore } from '@unchainedshop/core';
|
|
2
|
+
import { Product } from '@unchainedshop/core-products';
|
|
3
|
+
import DataLoader from 'dataloader';
|
|
4
|
+
|
|
5
|
+
export default (unchainedAPI: UnchainedCore) =>
|
|
6
|
+
new DataLoader<{ productId?: string }, Product[]>(async (queries) => {
|
|
7
|
+
const productIds = [...new Set(queries.map((q) => q.productId).filter(Boolean))];
|
|
8
|
+
|
|
9
|
+
const productProxies = await unchainedAPI.modules.products.findProducts({
|
|
10
|
+
includeDrafts: true,
|
|
11
|
+
productSelector: {
|
|
12
|
+
$or: [
|
|
13
|
+
{
|
|
14
|
+
'bundleItems.productId': { $in: productIds },
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
'proxy.assignments.productId': { $in: productIds },
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const productProxyMap = {};
|
|
24
|
+
for (const productProxy of productProxies) {
|
|
25
|
+
const bundleItemIds = productProxy.bundleItems?.map((item) => item.productId) || [];
|
|
26
|
+
const proxyAssignmentIds =
|
|
27
|
+
productProxy.proxy?.assignments?.map((assignment) => assignment.productId) || [];
|
|
28
|
+
|
|
29
|
+
for (const productId of [...bundleItemIds, ...proxyAssignmentIds]) {
|
|
30
|
+
if (!productProxyMap[productId]) {
|
|
31
|
+
productProxyMap[productId] = [];
|
|
32
|
+
}
|
|
33
|
+
productProxyMap[productId].push(productProxy);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return queries.map((q) => productProxyMap[q.productId] || []);
|
|
37
|
+
});
|
|
@@ -2,6 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { Context } from '../../../context.js';
|
|
3
3
|
import { log } from '@unchainedshop/logger';
|
|
4
4
|
import { FileNotFoundError, FileUploadExpiredError } from '../../../errors.js';
|
|
5
|
+
import normalizeMediaUrl from '../../utils/normalizeMediaUrl.js';
|
|
5
6
|
|
|
6
7
|
export const AddAssortmentMediaUploadSchema = {
|
|
7
8
|
mediaName: z
|
|
@@ -61,7 +62,7 @@ export async function addAssortmentMediaUploadHandler(
|
|
|
61
62
|
{
|
|
62
63
|
type: 'text' as const,
|
|
63
64
|
text: JSON.stringify({
|
|
64
|
-
file: linked,
|
|
65
|
+
file: await normalizeMediaUrl([{ ...linked, mediaId: linked._id }], context),
|
|
65
66
|
}),
|
|
66
67
|
},
|
|
67
68
|
],
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Context } from '../../../context.js';
|
|
2
|
+
import { log } from '@unchainedshop/logger';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import normalizeMediaUrl from '../../utils/normalizeMediaUrl.js';
|
|
5
|
+
|
|
6
|
+
export const AssortmentMediaSchema = {
|
|
7
|
+
assortmentId: z.string().min(1).describe('ID of the Assortment to retrieve media for'),
|
|
8
|
+
limit: z.number().int().min(1).max(100).default(10).describe('Number of media items to return'),
|
|
9
|
+
offset: z.number().int().min(0).default(0).describe('Pagination offset'),
|
|
10
|
+
tags: z.array(z.string().min(1)).optional().describe('Filter media by lowercase tags'),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const AssortmentMediaZodSchema = z
|
|
14
|
+
.object(AssortmentMediaSchema)
|
|
15
|
+
.describe(
|
|
16
|
+
'Retrieve media assets associated with a assortment, optionally filtered by tags and paginated.',
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export type assortmentMediaParams = z.infer<typeof AssortmentMediaZodSchema>;
|
|
20
|
+
|
|
21
|
+
export async function assortmentMediaHandler(context: Context, params: assortmentMediaParams) {
|
|
22
|
+
const { assortmentId, limit, offset, tags } = params;
|
|
23
|
+
const { modules, loaders, userId } = context;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
log('handler assortmentMediaHandler', { userId, params });
|
|
27
|
+
let media = [];
|
|
28
|
+
if (offset || tags) {
|
|
29
|
+
media = await modules.assortments.media.findAssortmentMedias({
|
|
30
|
+
assortmentId,
|
|
31
|
+
...params,
|
|
32
|
+
});
|
|
33
|
+
} else {
|
|
34
|
+
media = (await loaders.assortmentMediasLoader.load({ assortmentId })).slice(
|
|
35
|
+
offset,
|
|
36
|
+
offset + limit,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
content: [
|
|
41
|
+
{
|
|
42
|
+
type: 'text' as const,
|
|
43
|
+
text: JSON.stringify({ media: await normalizeMediaUrl(media, context) }),
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return {
|
|
49
|
+
content: [
|
|
50
|
+
{
|
|
51
|
+
type: 'text' as const,
|
|
52
|
+
text: `Error retrieving assortment media: ${(error as Error).message}`,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -73,6 +73,7 @@ import {
|
|
|
73
73
|
UpdateAssortmentTextsSchema,
|
|
74
74
|
updateAssortmentTextsHandler,
|
|
75
75
|
} from './updateAssortmentTextsHandler.js';
|
|
76
|
+
import { assortmentMediaHandler, AssortmentMediaSchema } from './assortmentMediaHandler.js';
|
|
76
77
|
|
|
77
78
|
export const registerAssortmentTools = (server: McpServer, context: Context) => {
|
|
78
79
|
server.tool(
|
|
@@ -202,7 +203,7 @@ export const registerAssortmentTools = (server: McpServer, context: Context) =>
|
|
|
202
203
|
);
|
|
203
204
|
|
|
204
205
|
server.tool(
|
|
205
|
-
'
|
|
206
|
+
'assortment_reorderMedias',
|
|
206
207
|
'Reorder assortment media assets with new sorting keys; first item becomes primary media.',
|
|
207
208
|
ReorderAssortmentMediaSchema,
|
|
208
209
|
async (params) => reorderAssortmentMediaHandler(context, params),
|
|
@@ -270,4 +271,11 @@ export const registerAssortmentTools = (server: McpServer, context: Context) =>
|
|
|
270
271
|
ReorderAssortmentFiltersSchema,
|
|
271
272
|
async (params) => reorderAssortmentFiltersHandler(context, params),
|
|
272
273
|
);
|
|
274
|
+
|
|
275
|
+
server.tool(
|
|
276
|
+
'assortment_getMedias',
|
|
277
|
+
'Retrieve media assets associated with a assortment, optionally filtered by tags and paginated.',
|
|
278
|
+
AssortmentMediaSchema,
|
|
279
|
+
async (params) => assortmentMediaHandler(context, params),
|
|
280
|
+
);
|
|
273
281
|
};
|
|
@@ -30,7 +30,7 @@ export async function reorderAssortmentMediaHandler(
|
|
|
30
30
|
try {
|
|
31
31
|
log('handler reorderAssortmentMediaHandler', { userId, params });
|
|
32
32
|
|
|
33
|
-
await modules.assortments.media.updateManualOrder({
|
|
33
|
+
const media = await modules.assortments.media.updateManualOrder({
|
|
34
34
|
sortKeys,
|
|
35
35
|
} as any);
|
|
36
36
|
|
|
@@ -38,7 +38,7 @@ export async function reorderAssortmentMediaHandler(
|
|
|
38
38
|
content: [
|
|
39
39
|
{
|
|
40
40
|
type: 'text' as const,
|
|
41
|
-
text: JSON.stringify({
|
|
41
|
+
text: JSON.stringify({ media }),
|
|
42
42
|
},
|
|
43
43
|
],
|
|
44
44
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { Context } from '../../../context.js';
|
|
3
3
|
import { log } from '@unchainedshop/logger';
|
|
4
|
+
import { AssortmentMediaNotFoundError } from '../../../errors.js';
|
|
4
5
|
|
|
5
6
|
export const TranslatedAssortmentMediaTextsSchema = {
|
|
6
7
|
assortmentMediaId: z.string().min(1).describe('ID of the assortment media to retrieve texts for'),
|
|
@@ -22,6 +23,12 @@ export async function translatedAssortmentMediaTextsHandler(
|
|
|
22
23
|
try {
|
|
23
24
|
log('handler translatedAssortmentMediaTextsHandler', { userId, params });
|
|
24
25
|
|
|
26
|
+
const assortmentMedia = await modules.assortments.media.findAssortmentMedia({
|
|
27
|
+
assortmentMediaId,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (!assortmentMedia) throw new AssortmentMediaNotFoundError({ assortmentMediaId });
|
|
31
|
+
|
|
25
32
|
const texts = await modules.assortments.media.texts.findMediaTexts({
|
|
26
33
|
assortmentMediaId,
|
|
27
34
|
});
|
|
@@ -30,7 +37,7 @@ export async function translatedAssortmentMediaTextsHandler(
|
|
|
30
37
|
content: [
|
|
31
38
|
{
|
|
32
39
|
type: 'text' as const,
|
|
33
|
-
text: JSON.stringify({ texts }),
|
|
40
|
+
text: JSON.stringify({ texts, assortmentId: assortmentMedia.assortmentId }),
|
|
34
41
|
},
|
|
35
42
|
],
|
|
36
43
|
};
|
|
@@ -47,7 +47,10 @@ export async function updateAssortmentMediaTextsHandler(
|
|
|
47
47
|
content: [
|
|
48
48
|
{
|
|
49
49
|
type: 'text' as const,
|
|
50
|
-
text: JSON.stringify({
|
|
50
|
+
text: JSON.stringify({
|
|
51
|
+
texts: assortmentMediaTexts,
|
|
52
|
+
assortmentId: assortmentMedia?.assortmentId,
|
|
53
|
+
}),
|
|
51
54
|
},
|
|
52
55
|
],
|
|
53
56
|
};
|
|
@@ -51,7 +51,9 @@ export const AssignProductVariationSchema = {
|
|
|
51
51
|
vectors: z
|
|
52
52
|
.array(ProductAssignmentVectorSchema)
|
|
53
53
|
.min(1)
|
|
54
|
-
.describe(
|
|
54
|
+
.describe(
|
|
55
|
+
'Provide one option for **each** variation of the parent product (e.g., Color and Size). if it is not complete request the user to add all options or offer to create it. Do not call this tool unless all variation options have been selected. Otherwise, this will fail.',
|
|
56
|
+
),
|
|
55
57
|
};
|
|
56
58
|
|
|
57
59
|
export const AddProductAssignmentZodSchema = z.object(AssignProductVariationSchema);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Context } from '../../../context.js';
|
|
3
|
+
import { ProductNotFoundError, ProductWrongTypeError } from '../../../errors.js';
|
|
4
|
+
import { log } from '@unchainedshop/logger';
|
|
5
|
+
import { getNormalizedProductDetails } from '../../utils/getNormalizedProductDetails.js';
|
|
6
|
+
import { ProductTypes } from '@unchainedshop/core-products';
|
|
7
|
+
|
|
8
|
+
const AddProductBundleItemInputSchema = z.object({
|
|
9
|
+
productId: z.string().min(1),
|
|
10
|
+
quantity: z.number().int().min(1),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export const AddProductBundleItemSchema = {
|
|
14
|
+
productId: z.string().min(1).describe('ID of the BUNDLE_PRODUCT to which the item will be added'),
|
|
15
|
+
item: AddProductBundleItemInputSchema,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const AddProductBundleItemZodSchema = z.object(AddProductBundleItemSchema);
|
|
19
|
+
|
|
20
|
+
export type AddProductBundleItemParams = z.infer<typeof AddProductBundleItemZodSchema>;
|
|
21
|
+
export const addProductBundleItemHandler = async (
|
|
22
|
+
context: Context,
|
|
23
|
+
params: AddProductBundleItemParams,
|
|
24
|
+
) => {
|
|
25
|
+
const { modules, userId } = context;
|
|
26
|
+
const { productId, item } = params;
|
|
27
|
+
|
|
28
|
+
log('handler addProductBundleItemHandler', { userId, params });
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const product = await modules.products.findProduct({ productId });
|
|
32
|
+
if (!product) throw new ProductNotFoundError({ productId });
|
|
33
|
+
|
|
34
|
+
if (product.type !== ProductTypes.BundleProduct)
|
|
35
|
+
throw new ProductWrongTypeError({
|
|
36
|
+
productId,
|
|
37
|
+
received: product.type,
|
|
38
|
+
required: ProductTypes.BundleProduct,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (!(await modules.products.productExists({ productId: item.productId })))
|
|
42
|
+
throw new ProductNotFoundError({ productId: item.productId });
|
|
43
|
+
|
|
44
|
+
await modules.products.bundleItems.addBundleItem(productId, item as any);
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
content: [
|
|
48
|
+
{
|
|
49
|
+
type: 'text' as const,
|
|
50
|
+
text: JSON.stringify({ product: await getNormalizedProductDetails(productId, context) }),
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return {
|
|
56
|
+
content: [
|
|
57
|
+
{
|
|
58
|
+
type: 'text' as const,
|
|
59
|
+
text: `Error adding bundle item: ${(error as Error).message}`,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
};
|
|
@@ -2,6 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { Context } from '../../../context.js';
|
|
3
3
|
import { log } from '@unchainedshop/logger';
|
|
4
4
|
import { FileNotFoundError, FileUploadExpiredError } from '../../../errors.js';
|
|
5
|
+
import normalizeMediaUrl from '../../utils/normalizeMediaUrl.js';
|
|
5
6
|
|
|
6
7
|
export const AddProductMediaUploadSchema = {
|
|
7
8
|
mediaName: z
|
|
@@ -24,7 +25,7 @@ export async function addProductMediaUploadHandler(
|
|
|
24
25
|
const { modules, services, userId } = context;
|
|
25
26
|
|
|
26
27
|
try {
|
|
27
|
-
log('handler
|
|
28
|
+
log('handler addProductMediaUploadHandler', { userId, params });
|
|
28
29
|
const {
|
|
29
30
|
_id: fileId,
|
|
30
31
|
putURL,
|
|
@@ -61,7 +62,7 @@ export async function addProductMediaUploadHandler(
|
|
|
61
62
|
{
|
|
62
63
|
type: 'text' as const,
|
|
63
64
|
text: JSON.stringify({
|
|
64
|
-
file: linked,
|
|
65
|
+
file: await normalizeMediaUrl([{ ...linked, mediaId: linked._id }], context),
|
|
65
66
|
}),
|
|
66
67
|
},
|
|
67
68
|
],
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Context } from '../../../context.js';
|
|
2
|
+
import { ProductNotFoundError } from '../../../errors.js';
|
|
3
|
+
import { getNormalizedProductDetails } from '../../utils/getNormalizedProductDetails.js';
|
|
4
|
+
import { log } from '@unchainedshop/logger';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
export const CatalogPriceSchema = {
|
|
8
|
+
productId: z.string().min(1).describe('ID of the product to retrieve catalog price for'),
|
|
9
|
+
quantity: z
|
|
10
|
+
.number()
|
|
11
|
+
.int()
|
|
12
|
+
.positive()
|
|
13
|
+
.default(1)
|
|
14
|
+
.describe('Quantity to calculate catalog price for (default is 1)'),
|
|
15
|
+
currencyCode: z
|
|
16
|
+
.string()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe('Optional currency code (e.g., "USD") for price calculation'),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const CatalogPriceZodSchema = z.object(CatalogPriceSchema);
|
|
22
|
+
export type CatalogPriceParams = z.infer<typeof CatalogPriceZodSchema>;
|
|
23
|
+
|
|
24
|
+
export async function catalogPriceHandler(context: Context, params: CatalogPriceParams) {
|
|
25
|
+
const { productId, quantity, currencyCode: forcedCurrencyCode } = params;
|
|
26
|
+
const { modules, countryCode, currencyCode: defaultCurrencyCode, userId } = context;
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
log('handler catalogPriceHandler', { userId, params });
|
|
30
|
+
const product = await getNormalizedProductDetails(productId, context);
|
|
31
|
+
if (!product) throw new ProductNotFoundError({ productId });
|
|
32
|
+
const currencyCode = forcedCurrencyCode || defaultCurrencyCode;
|
|
33
|
+
const catalogPrice = await modules.products.prices.price(product, {
|
|
34
|
+
countryCode,
|
|
35
|
+
currencyCode,
|
|
36
|
+
quantity,
|
|
37
|
+
});
|
|
38
|
+
const leveledCatalogPrice = await modules.products.prices.catalogPricesLeveled(product, {
|
|
39
|
+
currencyCode,
|
|
40
|
+
countryCode,
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
content: [
|
|
44
|
+
{
|
|
45
|
+
type: 'text' as const,
|
|
46
|
+
text: JSON.stringify({ product, price: catalogPrice, leveledCatalogPrice }),
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: 'text' as const,
|
|
55
|
+
|
|
56
|
+
text: `Error retrieving catalog price: ${(error as Error).message}`,
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -71,6 +71,27 @@ import {
|
|
|
71
71
|
translatedProductVariationTextsHandler,
|
|
72
72
|
TranslatedProductVariationTextsSchema,
|
|
73
73
|
} from './translatedProductVariationTextsHandler.js';
|
|
74
|
+
import {
|
|
75
|
+
updateProductVariationTextsHandler,
|
|
76
|
+
UpdateProductVariationTextsSchema,
|
|
77
|
+
} from './updateProductVariationTextsHandler.js';
|
|
78
|
+
import { productSiblingsHandler, ProductSiblingsSchema } from './productSiblingsHandler.js';
|
|
79
|
+
import { productMediaHandler, ProductMediaSchema } from './productMediaHandler.js';
|
|
80
|
+
import { variationProductsHandler, VariationProductsSchema } from './variationProductsHandler.js';
|
|
81
|
+
import { productVariationsHandler, ProductVariationsSchema } from './productVariationsHandler.js';
|
|
82
|
+
import {
|
|
83
|
+
addProductBundleItemHandler,
|
|
84
|
+
AddProductBundleItemSchema,
|
|
85
|
+
} from './addProductBundleItemHandler.js';
|
|
86
|
+
import {
|
|
87
|
+
removeProductBundleItemHandler,
|
|
88
|
+
RemoveProductBundleItemSchema,
|
|
89
|
+
} from './removeProductBundleItemHandler.js';
|
|
90
|
+
import { productAssignmentsHandler, ProductAssignmentsSchema } from './productAssignmentsHandler.js';
|
|
91
|
+
import { productBundleItemsHandler, ProductBundleItemsSchema } from './productBundleItemsHandler.js';
|
|
92
|
+
import { simulatedPriceRangeHandler, SimulatedPriceRangeSchema } from './simulatedPriceRangeHandler.js';
|
|
93
|
+
import { simulatedPriceHandler, SimulatedPriceSchema } from './simulatedPriceHandler.js';
|
|
94
|
+
import { catalogPriceHandler, CatalogPriceSchema } from './catalogPriceHandler.js';
|
|
74
95
|
|
|
75
96
|
export const registerProductTools = (server: McpServer, context: Context) => {
|
|
76
97
|
server.tool(
|
|
@@ -161,7 +182,7 @@ export const registerProductTools = (server: McpServer, context: Context) => {
|
|
|
161
182
|
);
|
|
162
183
|
server.tool(
|
|
163
184
|
'product_updatePlan',
|
|
164
|
-
'Update the plan
|
|
185
|
+
'Update the subscription plan configuration for a product of type PLAN. This includes usage calculation type (LICENSED or METERED), billing interval settings, and optional trial period configuration. This operation is only valid for products with type PLAN_PRODUCT.',
|
|
165
186
|
UpdateProductPlanSchema,
|
|
166
187
|
async (params) => updateProductPlanHandler(context, params),
|
|
167
188
|
);
|
|
@@ -182,7 +203,7 @@ export const registerProductTools = (server: McpServer, context: Context) => {
|
|
|
182
203
|
|
|
183
204
|
server.tool(
|
|
184
205
|
'product_assignVariation',
|
|
185
|
-
'Assign a product variant to a configurable product with a unique combination of
|
|
206
|
+
'Assign a product variant to a configurable product with a unique combination of option vectors. assigning with incomplete vector will fail. make sure all variation options are selected before calling this tool.',
|
|
186
207
|
AssignProductVariationSchema,
|
|
187
208
|
async (params) => addProductAssignmentHandler(context, params),
|
|
188
209
|
);
|
|
@@ -230,7 +251,7 @@ export const registerProductTools = (server: McpServer, context: Context) => {
|
|
|
230
251
|
);
|
|
231
252
|
|
|
232
253
|
server.tool(
|
|
233
|
-
'
|
|
254
|
+
'product_reorderMedias',
|
|
234
255
|
'Reorder media assets of a product by providing new sort keys.',
|
|
235
256
|
ReorderProductMediaSchema,
|
|
236
257
|
async (params) => reorderProductMediaHandler(context, params),
|
|
@@ -269,4 +290,84 @@ export const registerProductTools = (server: McpServer, context: Context) => {
|
|
|
269
290
|
TranslatedProductVariationTextsSchema,
|
|
270
291
|
async (params) => translatedProductVariationTextsHandler(context, params),
|
|
271
292
|
);
|
|
293
|
+
server.tool(
|
|
294
|
+
'productVariation_updateTexts',
|
|
295
|
+
'Update product variation texts with the specified locales for product variations that match the provided variation ID and production option value.',
|
|
296
|
+
UpdateProductVariationTextsSchema,
|
|
297
|
+
async (params) => updateProductVariationTextsHandler(context, params),
|
|
298
|
+
);
|
|
299
|
+
server.tool(
|
|
300
|
+
'product_siblings',
|
|
301
|
+
'Retrieve sibling products of a given product, optionally filtered by assortment ID, with support for pagination and inclusion of inactive products.',
|
|
302
|
+
ProductSiblingsSchema,
|
|
303
|
+
async (params) => productSiblingsHandler(context, params),
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
server.tool(
|
|
307
|
+
'product_getMedias',
|
|
308
|
+
'Retrieve media assets associated with a product, optionally filtered by tags and paginated.',
|
|
309
|
+
ProductMediaSchema,
|
|
310
|
+
async (params) => productMediaHandler(context, params),
|
|
311
|
+
);
|
|
312
|
+
server.tool(
|
|
313
|
+
'product_variationProducts',
|
|
314
|
+
'Retrieve all variant products of a configurable product that exactly match the provided combination of variation key-value pairs (e.g., Color: Red, Size: M). You must provide at least one vector. Optionally include inactive variants in the result.',
|
|
315
|
+
VariationProductsSchema,
|
|
316
|
+
async (params) => variationProductsHandler(context, params),
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
server.tool(
|
|
320
|
+
'product_variations',
|
|
321
|
+
'Retrieve all defined variation attributes (e.g., Color, Size) and their possible values for a configurable product.',
|
|
322
|
+
ProductVariationsSchema,
|
|
323
|
+
async (params) => productVariationsHandler(context, params),
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
server.tool(
|
|
327
|
+
'product_addBundleItem',
|
|
328
|
+
'Adds a product as a bundled item to another product. Only works if the target product is of type BUNDLE_PRODUCT. You must provide the ID of the bundle and the ID and quantity of the item to include.',
|
|
329
|
+
AddProductBundleItemSchema,
|
|
330
|
+
async (params) => addProductBundleItemHandler(context, params),
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
server.tool(
|
|
334
|
+
'product_removeBundleItem',
|
|
335
|
+
'Removes a bundled product from a BUNDLE_PRODUCT using its 0-based index in the bundle list. The product must be of type BUNDLE_PRODUCT.',
|
|
336
|
+
RemoveProductBundleItemSchema,
|
|
337
|
+
async (params) => removeProductBundleItemHandler(context, params),
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
server.tool(
|
|
341
|
+
'product_assignments',
|
|
342
|
+
'Retrieve the full assignment matrix of a configurable product, including all assigned variants by their variation vectors. Optionally include inactive variants.',
|
|
343
|
+
ProductAssignmentsSchema,
|
|
344
|
+
async (params) => productAssignmentsHandler(context, params),
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
server.tool(
|
|
348
|
+
'product_bundleItems',
|
|
349
|
+
'Retrieve all bundle items from a product of type BUNDLE. Each item includes the configuration and quantity.',
|
|
350
|
+
ProductBundleItemsSchema,
|
|
351
|
+
async (params) => productBundleItemsHandler(context, params),
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
server.tool(
|
|
355
|
+
'product_simulatedPriceRange',
|
|
356
|
+
'Simulate the price range for a CONFIGURABLE_PRODUCT and its variant combinations based on quantity, variation selections, and pricing rules. Only applicable to configurable products.',
|
|
357
|
+
SimulatedPriceRangeSchema,
|
|
358
|
+
async (params) => simulatedPriceRangeHandler(context, params),
|
|
359
|
+
);
|
|
360
|
+
server.tool(
|
|
361
|
+
'product_simulatedPrice',
|
|
362
|
+
'Simulate the price for a product given a complete configuration vector and quantity. Useful for previewing dynamic pricing rules.',
|
|
363
|
+
SimulatedPriceSchema,
|
|
364
|
+
async (params) => simulatedPriceHandler(context, params),
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
server.tool(
|
|
368
|
+
'product_catalogPrice',
|
|
369
|
+
'Retrieve the catalog price for any product type, optionally for a specific quantity and currency. when ask for product price without specifying what, always use this tool',
|
|
370
|
+
CatalogPriceSchema,
|
|
371
|
+
async (params) => catalogPriceHandler(context, params),
|
|
372
|
+
);
|
|
272
373
|
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Context } from '../../../context.js';
|
|
2
|
+
import { ProductNotFoundError, ProductWrongTypeError } from '../../../errors.js';
|
|
3
|
+
import { ProductTypes } from '@unchainedshop/core-products';
|
|
4
|
+
import { log } from '@unchainedshop/logger';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import normalizeMediaUrl from '../../utils/normalizeMediaUrl.js';
|
|
7
|
+
import { getNormalizedProductDetails } from '../../utils/getNormalizedProductDetails.js';
|
|
8
|
+
|
|
9
|
+
export const ProductAssignmentsSchema = {
|
|
10
|
+
productId: z
|
|
11
|
+
.string()
|
|
12
|
+
.min(1)
|
|
13
|
+
.describe('ID of the CONFIGURABLE_PRODUCT whose assignments should be fetched'),
|
|
14
|
+
includeInactive: z
|
|
15
|
+
.boolean()
|
|
16
|
+
.optional()
|
|
17
|
+
.default(false)
|
|
18
|
+
.describe('Whether to include inactive assigned products'),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const ProductAssignmentsZodSchema = z.object(ProductAssignmentsSchema);
|
|
22
|
+
export type ProductAssignmentsParams = z.infer<typeof ProductAssignmentsZodSchema>;
|
|
23
|
+
|
|
24
|
+
export const productAssignmentsHandler = async (context: Context, params: ProductAssignmentsParams) => {
|
|
25
|
+
const { modules, userId, loaders, locale } = context;
|
|
26
|
+
const { productId, includeInactive } = params;
|
|
27
|
+
|
|
28
|
+
log('handler productAssignmentsHandler', { userId, params });
|
|
29
|
+
try {
|
|
30
|
+
const product = await getNormalizedProductDetails(productId, context);
|
|
31
|
+
if (!product) throw new ProductNotFoundError({ productId });
|
|
32
|
+
|
|
33
|
+
if (product.type !== ProductTypes.ConfigurableProduct) {
|
|
34
|
+
throw new ProductWrongTypeError({
|
|
35
|
+
productId,
|
|
36
|
+
received: product.type,
|
|
37
|
+
required: ProductTypes.ConfigurableProduct,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const assignments = await modules.products.proxyAssignments(product, { includeInactive });
|
|
42
|
+
const normalizedAssignments = await Promise.all(
|
|
43
|
+
assignments.map(async ({ assignment, ...rest }) => {
|
|
44
|
+
const product = await loaders.productLoader.load({
|
|
45
|
+
productId: assignment.productId,
|
|
46
|
+
});
|
|
47
|
+
const productMedias = await modules.products.media.findProductMedias({
|
|
48
|
+
productId: assignment.productId,
|
|
49
|
+
});
|
|
50
|
+
const media = await normalizeMediaUrl(productMedias, context);
|
|
51
|
+
const texts = await loaders.productTextLoader.load({
|
|
52
|
+
productId: assignment.productId,
|
|
53
|
+
locale,
|
|
54
|
+
});
|
|
55
|
+
return {
|
|
56
|
+
assignment: {
|
|
57
|
+
...assignment,
|
|
58
|
+
product: {
|
|
59
|
+
...product,
|
|
60
|
+
media,
|
|
61
|
+
texts,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
...rest,
|
|
65
|
+
};
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
return {
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: 'text' as const,
|
|
72
|
+
text: JSON.stringify({ product, assignments: normalizedAssignments }),
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
} catch (error) {
|
|
77
|
+
return {
|
|
78
|
+
content: [
|
|
79
|
+
{
|
|
80
|
+
type: 'text' as const,
|
|
81
|
+
text: `Error fetching assignments: ${(error as Error).message}`,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
};
|