Product data updates in CMS variant objects (#324)
This commit is contained in:
parent
84df6acf9c
commit
2f94183dc6
3 changed files with 165 additions and 0 deletions
36
apps/cms/graphql/fragments/WebhookProduct.graphql
Normal file
36
apps/cms/graphql/fragments/WebhookProduct.graphql
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
fragment WebhookProduct on Product {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
media {
|
||||||
|
url
|
||||||
|
}
|
||||||
|
channelListings {
|
||||||
|
id
|
||||||
|
channel {
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
isPublished
|
||||||
|
}
|
||||||
|
variants {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
sku
|
||||||
|
channelListings {
|
||||||
|
id
|
||||||
|
channel {
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
price {
|
||||||
|
amount
|
||||||
|
currency
|
||||||
|
}
|
||||||
|
}
|
||||||
|
metadata {
|
||||||
|
key
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import packageJson from "../../../package.json";
|
||||||
import { productVariantUpdatedWebhook } from "./webhooks/product-variant-updated";
|
import { productVariantUpdatedWebhook } from "./webhooks/product-variant-updated";
|
||||||
import { productVariantCreatedWebhook } from "./webhooks/product-variant-created";
|
import { productVariantCreatedWebhook } from "./webhooks/product-variant-created";
|
||||||
import { productVariantDeletedWebhook } from "./webhooks/product-variant-deleted";
|
import { productVariantDeletedWebhook } from "./webhooks/product-variant-deleted";
|
||||||
|
import { productUpdatedWebhook } from "./webhooks/product-updated";
|
||||||
|
|
||||||
export default createManifestHandler({
|
export default createManifestHandler({
|
||||||
async manifestFactory(context) {
|
async manifestFactory(context) {
|
||||||
|
@ -19,6 +20,7 @@ export default createManifestHandler({
|
||||||
productVariantCreatedWebhook.getWebhookManifest(context.appBaseUrl),
|
productVariantCreatedWebhook.getWebhookManifest(context.appBaseUrl),
|
||||||
productVariantUpdatedWebhook.getWebhookManifest(context.appBaseUrl),
|
productVariantUpdatedWebhook.getWebhookManifest(context.appBaseUrl),
|
||||||
productVariantDeletedWebhook.getWebhookManifest(context.appBaseUrl),
|
productVariantDeletedWebhook.getWebhookManifest(context.appBaseUrl),
|
||||||
|
productUpdatedWebhook.getWebhookManifest(context.appBaseUrl),
|
||||||
],
|
],
|
||||||
extensions: [],
|
extensions: [],
|
||||||
author: "Saleor Commerce",
|
author: "Saleor Commerce",
|
||||||
|
|
127
apps/cms/src/pages/api/webhooks/product-updated.ts
Normal file
127
apps/cms/src/pages/api/webhooks/product-updated.ts
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
|
||||||
|
import { gql } from "urql";
|
||||||
|
import {
|
||||||
|
ProductUpdatedWebhookPayloadFragment,
|
||||||
|
UntypedWebhookProductFragmentDoc,
|
||||||
|
} from "../../../../generated/graphql";
|
||||||
|
import { saleorApp } from "../../../../saleor-app";
|
||||||
|
import { getCmsKeysFromSaleorItem } from "../../../lib/cms/client/metadata";
|
||||||
|
import { getChannelsSlugsFromSaleorItem } from "../../../lib/cms/client/channels";
|
||||||
|
import {
|
||||||
|
createCmsOperations,
|
||||||
|
executeCmsOperations,
|
||||||
|
executeMetadataUpdate,
|
||||||
|
} from "../../../lib/cms/client";
|
||||||
|
import { logger as pinoLogger } from "../../../lib/logger";
|
||||||
|
import { createClient } from "../../../lib/graphql";
|
||||||
|
import { fetchProductVariantMetadata } from "../../../lib/metadata";
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
bodyParser: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProductUpdatedWebhookPayload = gql`
|
||||||
|
${UntypedWebhookProductFragmentDoc}
|
||||||
|
fragment ProductUpdatedWebhookPayload on ProductUpdated {
|
||||||
|
product {
|
||||||
|
...WebhookProduct
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ProductUpdatedSubscription = gql`
|
||||||
|
${ProductUpdatedWebhookPayload}
|
||||||
|
subscription ProductUpdated {
|
||||||
|
event {
|
||||||
|
...ProductUpdatedWebhookPayload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const productUpdatedWebhook = new SaleorAsyncWebhook<ProductUpdatedWebhookPayloadFragment>({
|
||||||
|
name: "Cms-hub product updated webhook",
|
||||||
|
webhookPath: "api/webhooks/product-updated",
|
||||||
|
event: "PRODUCT_UPDATED",
|
||||||
|
apl: saleorApp.apl,
|
||||||
|
query: ProductUpdatedSubscription,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const handler: NextWebhookApiHandler<ProductUpdatedWebhookPayloadFragment> = async (
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
context
|
||||||
|
) => {
|
||||||
|
const { product } = context.payload;
|
||||||
|
const { saleorApiUrl, token } = context.authData;
|
||||||
|
|
||||||
|
const logger = pinoLogger.child({
|
||||||
|
product,
|
||||||
|
});
|
||||||
|
logger.debug("Called webhook PRODUCT_UPDATED");
|
||||||
|
|
||||||
|
if (!product) {
|
||||||
|
return res.status(500).json({
|
||||||
|
errors: [
|
||||||
|
"No product product data payload provided. Cannot process product variants syncronisation in CMS providers.",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = createClient(saleorApiUrl, async () => ({
|
||||||
|
token: token,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const allCMSErrors: string[] = [];
|
||||||
|
|
||||||
|
product.variants?.forEach(async (variant) => {
|
||||||
|
const { variants: _, ...productFields } = product;
|
||||||
|
const productVariant = {
|
||||||
|
product: productFields,
|
||||||
|
...variant,
|
||||||
|
};
|
||||||
|
|
||||||
|
const productVariantChannels = getChannelsSlugsFromSaleorItem(productVariant);
|
||||||
|
const productMetadata = await fetchProductVariantMetadata(client, productVariant.id);
|
||||||
|
const productVariantCmsKeys = getCmsKeysFromSaleorItem({ metadata: productMetadata });
|
||||||
|
const cmsOperations = await createCmsOperations({
|
||||||
|
context,
|
||||||
|
client,
|
||||||
|
productVariantChannels: productVariantChannels,
|
||||||
|
productVariantCmsKeys: productVariantCmsKeys,
|
||||||
|
});
|
||||||
|
// Do not touch product variants which are not created or should be deleted.
|
||||||
|
// These operations should and will be performed by PRODUCT_VARIANT_CREATED and PRODUCT_VARIANT_DELETED webhooks.
|
||||||
|
// Otherwise we will end up with duplicated product variants in CMS providers! (or failed variant delete operations).
|
||||||
|
const cmsUpdateOperations = cmsOperations.filter(
|
||||||
|
(operation) => operation.operationType === "updateProduct"
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
cmsProviderInstanceProductVariantIdsToCreate,
|
||||||
|
cmsProviderInstanceProductVariantIdsToDelete,
|
||||||
|
cmsErrors,
|
||||||
|
} = await executeCmsOperations({
|
||||||
|
cmsOperations: cmsUpdateOperations,
|
||||||
|
productVariant,
|
||||||
|
});
|
||||||
|
|
||||||
|
allCMSErrors.push(...cmsErrors);
|
||||||
|
|
||||||
|
await executeMetadataUpdate({
|
||||||
|
context,
|
||||||
|
productVariant,
|
||||||
|
cmsProviderInstanceIdsToCreate: cmsProviderInstanceProductVariantIdsToCreate,
|
||||||
|
cmsProviderInstanceIdsToDelete: cmsProviderInstanceProductVariantIdsToDelete,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!allCMSErrors.length) {
|
||||||
|
return res.status(200).end();
|
||||||
|
} else {
|
||||||
|
return res.status(500).json({ errors: allCMSErrors });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default productUpdatedWebhook.createHandler(handler);
|
Loading…
Reference in a new issue