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 { productVariantCreatedWebhook } from "./webhooks/product-variant-created";
|
||||
import { productVariantDeletedWebhook } from "./webhooks/product-variant-deleted";
|
||||
import { productUpdatedWebhook } from "./webhooks/product-updated";
|
||||
|
||||
export default createManifestHandler({
|
||||
async manifestFactory(context) {
|
||||
|
@ -19,6 +20,7 @@ export default createManifestHandler({
|
|||
productVariantCreatedWebhook.getWebhookManifest(context.appBaseUrl),
|
||||
productVariantUpdatedWebhook.getWebhookManifest(context.appBaseUrl),
|
||||
productVariantDeletedWebhook.getWebhookManifest(context.appBaseUrl),
|
||||
productUpdatedWebhook.getWebhookManifest(context.appBaseUrl),
|
||||
],
|
||||
extensions: [],
|
||||
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