Compare commits

...

4 commits

Author SHA1 Message Date
4b12982597 add changeset 2023-09-30 22:22:32 +02:00
292a5bdb0c emails-and-messages redis apl support 2023-09-30 22:03:02 +02:00
Lukasz Ostrowski
653b98df86
Release apps (#1041)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-09-28 16:02:35 +02:00
Krzysztof Wolski
ae6dbb125b
Remove cache update on product webhooks (#1036)
* Product feed - change max function exec time to 5 minutes

* Remove cache update on product webhooks

* Add webhooks migration

---------

Co-authored-by: Lukasz Ostrowski <lukasz.ostrowski@saleor.io>
2023-09-27 12:56:12 +02:00
33 changed files with 173 additions and 470 deletions

View file

@ -0,0 +1,8 @@
#changelog
---
"apps": minor
---
### Added
- `apps/emails-and-messages/.env.template`: Described the new environment variable and how it works
- `apps/emails-and-messages/src/saleor-app.ts`: Added case "redis" for switch(AplType), which takes advantage of the [RedisAPL PR](https://github.com/saleor/app-sdk/pull/287) I submitted

View file

@ -7,6 +7,12 @@ APL=
REST_APL_ENDPOINT=
REST_APL_TOKEN=
# To use Redis as an APL store, set APP_API_BASE_URL and REDIS_URL.
# URL is in format redis[s]://[[username][:password]@][host][:port][/db-number],
# so for example redis://alice:foobared@awesome.redis.server:6380
# For saleor-platform, thats: `redis://redis:6379/1`
REDIS_URL=
APP_LOG_LEVEL=info
# Local development variables. When developped locally with Saleor inside docker, these can be set to:
@ -16,3 +22,4 @@ APP_LOG_LEVEL=info
# https://docs.saleor.io/docs/3.x/developer/extending/apps/local-app-development
APP_IFRAME_BASE_URL=
APP_API_BASE_URL=

View file

@ -1,4 +1,4 @@
import { APL, FileAPL, SaleorCloudAPL, UpstashAPL } from "@saleor/app-sdk/APL";
import { APL, FileAPL, RedisAPL, SaleorCloudAPL, UpstashAPL } from "@saleor/app-sdk/APL";
import { SaleorApp } from "@saleor/app-sdk/saleor-app";
const aplType = process.env.APL ?? "file";
@ -6,6 +6,12 @@ const aplType = process.env.APL ?? "file";
export let apl: APL;
switch (aplType) {
case "redis": {
if (!process.env.REDIS_URL) throw new Error("Missing redis url");
if (!process.env.APP_API_BASE_URL)
throw new Error("Redis relies on APP_API_BASE_URL to store keys, please set env variable");
apl = new RedisAPL(new URL(process.env.REDIS_URL), process.env.APP_API_BASE_URL);
}
case "upstash":
apl = new UpstashAPL();

View file

@ -1,5 +1,7 @@
{
"extends": ["//"],
"extends": [
"//"
],
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
@ -21,7 +23,8 @@
"NEXT_PUBLIC_SENTRY_DSN",
"SENTRY_ENVIRONMENT",
"APP_IFRAME_BASE_URL",
"APP_API_BASE_URL"
"APP_API_BASE_URL",
"REDIS_URL"
]
}
}

View file

@ -1,5 +1,12 @@
# saleor-app-products-feed
## 1.12.2
### Patch Changes
- ae6dbb1: Removed webhooks on product changes used for feed cache due to changed max execution time.
- ae6dbb1: Changed Vercel's maximum execution time to be 5 minutes for feed generation. This should help with the previous limits of 60s, that was not enough for feed to be generated.
## 1.12.1
### Patch Changes

View file

@ -1,8 +0,0 @@
fragment ProductVariantWebhookPayload on ProductVariant {
channel
channelListings {
channel {
slug
}
}
}

View file

@ -1,8 +0,0 @@
fragment ProductWebhookPayload on Product {
channel
channelListings {
channel {
slug
}
}
}

View file

@ -1,9 +0,0 @@
subscription ProductCreated {
event {
... on ProductCreated {
product {
...ProductWebhookPayload
}
}
}
}

View file

@ -1,9 +0,0 @@
subscription ProductDeleted {
event {
... on ProductDeleted {
product {
...ProductWebhookPayload
}
}
}
}

View file

@ -1,9 +0,0 @@
subscription ProductUpdated {
event {
... on ProductUpdated {
product {
...ProductWebhookPayload
}
}
}
}

View file

@ -1,9 +0,0 @@
subscription ProductVariantCreated {
event {
... on ProductVariantCreated {
productVariant {
...ProductVariantWebhookPayload
}
}
}
}

View file

@ -1,10 +0,0 @@
subscription ProductVariantDeleted {
event {
... on ProductVariantDeleted {
productVariant {
...ProductVariantWebhookPayload
}
}
}
}

View file

@ -1,9 +0,0 @@
subscription ProductVariantUpdated {
event {
... on ProductVariantUpdated {
productVariant {
...ProductVariantWebhookPayload
}
}
}
}

View file

@ -6,7 +6,12 @@ const isSentryPropertiesInEnvironment =
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
transpilePackages: ["@saleor/apps-shared", "@saleor/apps-ui", "@saleor/react-hook-form-macaw"],
transpilePackages: [
"@saleor/apps-shared",
"@saleor/apps-ui",
"@saleor/react-hook-form-macaw",
"@saleor/webhook-utils",
],
};
const configWithSentry = withSentryConfig(
@ -22,7 +27,7 @@ const configWithSentry = withSentryConfig(
tunnelRoute: "/monitoring",
hideSourceMaps: true,
disableLogger: true,
}
},
);
module.exports = isSentryPropertiesInEnvironment ? configWithSentry : nextConfig;

View file

@ -1,6 +1,6 @@
{
"name": "saleor-app-products-feed",
"version": "1.12.1",
"version": "1.12.2",
"scripts": {
"build": "pnpm generate && next build",
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
@ -19,6 +19,7 @@
"@saleor/apps-ui": "workspace:*",
"@saleor/macaw-ui": "0.8.0-pre.127",
"@saleor/react-hook-form-macaw": "workspace:*",
"@saleor/webhook-utils": "workspace:*",
"@sentry/nextjs": "7.67.0",
"@tanstack/react-query": "4.29.19",
"@trpc/client": "10.38.1",
@ -27,6 +28,7 @@
"@trpc/server": "10.38.1",
"@urql/exchange-auth": "^2.1.4",
"@vitejs/plugin-react": "4.0.4",
"dotenv": "^16.3.1",
"fast-xml-parser": "^4.0.15",
"graphql": "16.7.1",
"graphql-tag": "^2.12.6",

View file

@ -0,0 +1,7 @@
# Webhook migration scripts
Test migration with dry run, operation will not modify any data:
`npx tsx scripts/migrations/run-webhooks-migration-dry-run.ts`
To start the migration run command:
`npx tsx scripts/migrations/run-webhooks-migration.ts`

View file

@ -0,0 +1,20 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import { SaleorCloudAPL } from "@saleor/app-sdk/APL";
export const verifyRequiredEnvs = () => {
const requiredEnvs = ["SALEOR_CLOUD_TOKEN", "SALEOR_CLOUD_RESOURCE_URL"];
if (!requiredEnvs.every((env) => process.env[env])) {
throw new Error(`Missing envs: ${requiredEnvs.join(" | ")}`);
}
};
export const fetchCloudAplEnvs = () => {
const saleorAPL = new SaleorCloudAPL({
token: process.env.SALEOR_CLOUD_TOKEN!,
resourceUrl: process.env.SALEOR_CLOUD_RESOURCE_URL!,
});
return saleorAPL.getAll();
};

View file

@ -0,0 +1,30 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import * as dotenv from "dotenv";
import { fetchCloudAplEnvs, verifyRequiredEnvs } from "./migration-utils";
import { updateWebhooksScript } from "./update-webhooks";
dotenv.config();
const runMigration = async () => {
console.log("Starting webhooks migration (dry run)");
verifyRequiredEnvs();
console.log("Envs verified, fetching envs");
const allEnvs = await fetchCloudAplEnvs().catch((r) => {
console.error("Could not fetch instances from the APL");
console.error(r);
process.exit(1);
});
for (const env of allEnvs) {
await updateWebhooksScript({ authData: env, dryRun: true });
}
console.log("Migration dry run complete");
};
runMigration();

View file

@ -0,0 +1,30 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import * as dotenv from "dotenv";
import { fetchCloudAplEnvs, verifyRequiredEnvs } from "./migration-utils";
import { updateWebhooksScript } from "./update-webhooks";
dotenv.config();
const runMigration = async () => {
console.log("Starting running migration");
verifyRequiredEnvs();
console.log("Envs verified, fetching envs");
const allEnvs = await fetchCloudAplEnvs().catch((r) => {
console.error("Could not fetch instances from the APL");
console.error(r);
process.exit(1);
});
for (const env of allEnvs) {
await updateWebhooksScript({ authData: env, dryRun: false });
}
console.log("Migration complete");
};
runMigration();

View file

@ -0,0 +1,29 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import { createGraphQLClient } from "@saleor/apps-shared";
import { AuthData } from "@saleor/app-sdk/APL";
import { webhookMigrationRunner } from "@saleor/webhook-utils";
export const updateWebhooksScript = async ({
authData,
dryRun,
}: {
authData: AuthData;
dryRun: boolean;
}) => {
console.log("Working on env: ", authData.saleorApiUrl);
const client = createGraphQLClient({
saleorApiUrl: authData.saleorApiUrl,
token: authData.token,
});
await webhookMigrationRunner({
client,
dryRun,
getManifests: async ({ appDetails }) => {
// Products feed application has currently no webhooks, so we return empty array
return [];
},
});
};

View file

@ -2,7 +2,6 @@ import { router } from "../trpc/trpc-server";
import { protectedClientProcedure } from "../trpc/protected-client-procedure";
import { createLogger } from "@saleor/apps-shared";
import { updateCacheForConfigurations } from "../metadata-cache/update-cache-for-configurations";
import { AppConfigSchema, imageSizeInputSchema, titleTemplateInputSchema } from "./app-config";
import { z } from "zod";
import { createS3ClientFromConfiguration } from "../file-storage/s3/create-s3-client-from-configuration";
@ -106,17 +105,6 @@ export const appConfigurationRouter = router({
}) => {
const config = await getConfig();
/**
* TODO Check if this has to run, once its cached, it should be invalidated by webhooks only.
*
* But this operation isn't expensive and users will not continuously save this form
*/
await updateCacheForConfigurations({
client: apiClient,
channelsSlugs: [input.channelSlug],
saleorApiUrl: saleorApiUrl,
});
logger.debug({ channel: input.channelSlug }, "Updated cache for channel");
config.setChannelUrls(input.channelSlug, input.urls);

View file

@ -1,33 +0,0 @@
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
export class CacheConfigurator {
private metadataKeyPrefix = "cursor-cache-";
constructor(private metadataManager: SettingsManager, private saleorApiUrl: string) {}
private constructKey(channel: string) {
return this.metadataKeyPrefix + channel;
}
get({ channel }: { channel: string }): Promise<string[] | undefined> {
return this.metadataManager.get(this.constructKey(channel), this.saleorApiUrl).then((data) => {
if (!data) {
return undefined;
}
try {
return JSON.parse(data);
} catch (e) {
throw new Error("Invalid metadata value, can't be parsed");
}
});
}
set({ channel, value }: { channel: string; value: string[] }): Promise<void> {
return this.metadataManager.set({
key: this.constructKey(channel),
value: JSON.stringify(value),
domain: this.saleorApiUrl,
});
}
}

View file

@ -1,33 +0,0 @@
import { createLogger } from "@saleor/apps-shared";
import { CacheConfigurator } from "./cache-configurator";
import { createSettingsManager } from "../../lib/metadata-manager";
import { getCursors } from "../google-feed/fetch-product-data";
import { Client } from "urql";
interface UpdateCacheForConfigurationsArgs {
client: Client;
saleorApiUrl: string;
channelsSlugs: string[];
}
export const updateCacheForConfigurations = async ({
client,
channelsSlugs,
saleorApiUrl,
}: UpdateCacheForConfigurationsArgs) => {
const logger = createLogger({ saleorApiUrl: saleorApiUrl });
logger.debug("Updating the cursor cache");
const cache = new CacheConfigurator(createSettingsManager(client), saleorApiUrl);
const cacheUpdatePromises = channelsSlugs.map(async (channel) => {
const cursors = await getCursors({ client, channel });
await cache.set({ channel, value: cursors });
});
await Promise.all(cacheUpdatePromises);
logger.debug("Cursor cache updated");
};

View file

@ -1,41 +0,0 @@
import { GraphqlClientFactory } from "../../lib/create-graphql-client";
import { updateCacheForConfigurations } from "./update-cache-for-configurations";
import { AuthData } from "@saleor/app-sdk/APL";
import {
ProductVariantWebhookPayloadFragment,
ProductWebhookPayloadFragment,
} from "../../../generated/graphql";
import { NextApiResponse } from "next";
type ChannelFragment =
| Pick<ProductWebhookPayloadFragment, "channel" | "channelListings">
| Pick<ProductVariantWebhookPayloadFragment, "channel" | "channelListings">;
export const updateCacheOnWebhook = async ({
channels,
authData,
res,
}: {
authData: AuthData;
channels: ChannelFragment;
res: NextApiResponse;
}) => {
const client = GraphqlClientFactory.fromAuthData(authData);
const channelsSlugs = [
channels.channel,
...(channels.channelListings?.map((cl) => cl.channel.slug) ?? []),
].filter((c) => c) as string[];
if (channelsSlugs.length === 0) {
return res.status(200).end();
}
await updateCacheForConfigurations({
channelsSlugs,
client,
saleorApiUrl: authData.saleorApiUrl,
});
return res.status(200).end();
};

View file

@ -6,9 +6,6 @@ import { fetchProductData } from "../../../../../modules/google-feed/fetch-produ
import { GoogleFeedSettingsFetcher } from "../../../../../modules/google-feed/get-google-feed-settings";
import { generateGoogleXmlFeed } from "../../../../../modules/google-feed/generate-google-xml-feed";
import { fetchShopData } from "../../../../../modules/google-feed/fetch-shop-data";
import { CacheConfigurator } from "../../../../../modules/metadata-cache/cache-configurator";
import { createSettingsManager } from "../../../../../lib/metadata-manager";
import { GraphqlClientFactory } from "../../../../../lib/create-graphql-client";
import { uploadFile } from "../../../../../modules/file-storage/s3/upload-file";
import { createS3ClientFromConfiguration } from "../../../../../modules/file-storage/s3/create-s3-client-from-configuration";
import { getFileDetails } from "../../../../../modules/file-storage/s3/get-file-details";
@ -16,6 +13,10 @@ import { getDownloadUrl, getFileName } from "../../../../../modules/file-storage
import { RootConfig } from "../../../../../modules/app-configuration/app-config";
import { z, ZodError } from "zod";
export const config = {
maxDuration: 5 * 60, // 5 minutes
};
// By default we cache the feed for 5 minutes. This can be changed by setting the FEED_CACHE_MAX_AGE
const FEED_CACHE_MAX_AGE = process.env.FEED_CACHE_MAX_AGE
? parseInt(process.env.FEED_CACHE_MAX_AGE, 10)
@ -157,23 +158,10 @@ export const handler = async (req: NextApiRequest, res: NextApiResponse) => {
logger.debug("Generating a new feed");
const cacheClient = GraphqlClientFactory.fromAuthData(authData);
if (!cacheClient) {
logger.error("Can't create the gql client");
return res.status(500).end();
}
// get cached cursors
const cache = new CacheConfigurator(createSettingsManager(cacheClient), authData.saleorApiUrl);
const cursors = await cache.get({ channel });
// TODO: instead of separate variants, use group id https://support.google.com/merchants/answer/6324507?hl=en
let productVariants: GoogleFeedProductVariantFragment[] = [];
try {
productVariants = await fetchProductData({ client, channel, cursors, imageSize });
productVariants = await fetchProductData({ client, channel, imageSize });
} catch (error) {
logger.error(error);
return res.status(400).end();

View file

@ -2,11 +2,6 @@ import { createManifestHandler } from "@saleor/app-sdk/handlers/next";
import { AppManifest } from "@saleor/app-sdk/types";
import packageJson from "../../../package.json";
import { webhookProductCreated } from "./webhooks/product_created";
import { webhookProductDeleted } from "./webhooks/product_deleted";
import { webhookProductVariantCreated } from "./webhooks/product_variant_created";
import { webhookProductVariantDeleted } from "./webhooks/product_variant_deleted";
import { webhookProductVariantUpdated } from "./webhooks/product_variant_updated";
export default createManifestHandler({
async manifestFactory({ appBaseUrl }) {
@ -31,13 +26,7 @@ export default createManifestHandler({
supportUrl: "https://github.com/saleor/apps/discussions",
tokenTargetUrl: `${apiBaseURL}/api/register`,
version: packageJson.version,
webhooks: [
webhookProductCreated.getWebhookManifest(apiBaseURL),
webhookProductDeleted.getWebhookManifest(apiBaseURL),
webhookProductVariantCreated.getWebhookManifest(apiBaseURL),
webhookProductVariantDeleted.getWebhookManifest(apiBaseURL),
webhookProductVariantUpdated.getWebhookManifest(apiBaseURL),
],
webhooks: [],
};
return manifest;

View file

@ -1,41 +0,0 @@
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import { createLogger } from "@saleor/apps-shared";
import { saleorApp } from "../../../saleor-app";
import {
ProductCreatedDocument,
ProductWebhookPayloadFragment,
} from "../../../../generated/graphql";
import { updateCacheOnWebhook } from "../../../modules/metadata-cache/update-cache-on-webhook";
export const config = {
api: {
bodyParser: false,
},
};
export const webhookProductCreated = new SaleorAsyncWebhook<ProductWebhookPayloadFragment>({
webhookPath: "api/webhooks/product_created",
event: "PRODUCT_CREATED",
apl: saleorApp.apl,
query: ProductCreatedDocument,
// todo make it disabled by default, enable when app is configured
isActive: true,
});
const logger = createLogger({
service: "webhook-product_created",
});
export const handler: NextWebhookApiHandler<ProductWebhookPayloadFragment> = async (
req,
res,
context
) => {
await updateCacheOnWebhook({
authData: context.authData,
channels: context.payload,
res,
});
};
export default webhookProductCreated.createHandler(handler);

View file

@ -1,40 +0,0 @@
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import {
ProductDeletedDocument,
ProductWebhookPayloadFragment,
} from "../../../../generated/graphql";
import { saleorApp } from "../../../saleor-app";
import { createLogger } from "@saleor/apps-shared";
import { updateCacheOnWebhook } from "../../../modules/metadata-cache/update-cache-on-webhook";
export const config = {
api: {
bodyParser: false,
},
};
export const webhookProductDeleted = new SaleorAsyncWebhook<ProductWebhookPayloadFragment>({
webhookPath: "api/webhooks/product_deleted",
event: "PRODUCT_DELETED",
apl: saleorApp.apl,
query: ProductDeletedDocument,
isActive: true,
});
const logger = createLogger({
service: "webhook_product_deleted",
});
export const handler: NextWebhookApiHandler<ProductWebhookPayloadFragment> = async (
req,
res,
context
) => {
await updateCacheOnWebhook({
authData: context.authData,
channels: context.payload,
res,
});
};
export default webhookProductDeleted.createHandler(handler);

View file

@ -1,40 +0,0 @@
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import {
ProductUpdatedDocument,
ProductWebhookPayloadFragment,
} from "../../../../generated/graphql";
import { saleorApp } from "../../../saleor-app";
import { createLogger } from "@saleor/apps-shared";
import { updateCacheOnWebhook } from "../../../modules/metadata-cache/update-cache-on-webhook";
export const config = {
api: {
bodyParser: false,
},
};
export const webhookProductUpdated = new SaleorAsyncWebhook<ProductWebhookPayloadFragment>({
webhookPath: "api/webhooks/product_updated",
event: "PRODUCT_UPDATED",
apl: saleorApp.apl,
query: ProductUpdatedDocument,
isActive: true,
});
const logger = createLogger({
service: "webhookProductUpdatedWebhookHandler",
});
export const handler: NextWebhookApiHandler<ProductWebhookPayloadFragment> = async (
req,
res,
context
) => {
await updateCacheOnWebhook({
authData: context.authData,
channels: context.payload,
res,
});
};
export default webhookProductUpdated.createHandler(handler);

View file

@ -1,41 +0,0 @@
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import {
ProductVariantCreatedDocument,
ProductVariantWebhookPayloadFragment,
} from "../../../../generated/graphql";
import { saleorApp } from "../../../saleor-app";
import { createLogger } from "@saleor/apps-shared";
import { updateCacheOnWebhook } from "../../../modules/metadata-cache/update-cache-on-webhook";
export const config = {
api: {
bodyParser: false,
},
};
export const webhookProductVariantCreated =
new SaleorAsyncWebhook<ProductVariantWebhookPayloadFragment>({
webhookPath: "api/webhooks/product_variant_created",
event: "PRODUCT_VARIANT_CREATED",
apl: saleorApp.apl,
query: ProductVariantCreatedDocument,
isActive: true,
});
const logger = createLogger({
service: "PRODUCT_VARIANT_CREATED webhook",
});
export const handler: NextWebhookApiHandler<ProductVariantWebhookPayloadFragment> = async (
req,
res,
context
) => {
await updateCacheOnWebhook({
authData: context.authData,
channels: context.payload,
res,
});
};
export default webhookProductVariantCreated.createHandler(handler);

View file

@ -1,41 +0,0 @@
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import { createLogger } from "@saleor/apps-shared";
import {
ProductVariantDeletedDocument,
ProductVariantWebhookPayloadFragment,
} from "../../../../generated/graphql";
import { saleorApp } from "../../../saleor-app";
import { updateCacheOnWebhook } from "../../../modules/metadata-cache/update-cache-on-webhook";
export const config = {
api: {
bodyParser: false,
},
};
export const webhookProductVariantDeleted =
new SaleorAsyncWebhook<ProductVariantWebhookPayloadFragment>({
webhookPath: "api/webhooks/product_variant_deleted",
event: "PRODUCT_VARIANT_DELETED",
apl: saleorApp.apl,
query: ProductVariantDeletedDocument,
isActive: true,
});
const logger = createLogger({
service: "PRODUCT_VARIANT_DELETED",
});
export const handler: NextWebhookApiHandler<ProductVariantWebhookPayloadFragment> = async (
req,
res,
context
) => {
await updateCacheOnWebhook({
authData: context.authData,
channels: context.payload,
res,
});
};
export default webhookProductVariantDeleted.createHandler(handler);

View file

@ -1,41 +0,0 @@
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import { createLogger } from "@saleor/apps-shared";
import {
ProductVariantUpdatedDocument,
ProductVariantWebhookPayloadFragment,
} from "../../../../generated/graphql";
import { saleorApp } from "../../../saleor-app";
import { updateCacheOnWebhook } from "../../../modules/metadata-cache/update-cache-on-webhook";
export const config = {
api: {
bodyParser: false,
},
};
export const webhookProductVariantUpdated =
new SaleorAsyncWebhook<ProductVariantWebhookPayloadFragment>({
webhookPath: "api/webhooks/product_variant_updated",
event: "PRODUCT_VARIANT_UPDATED",
apl: saleorApp.apl,
query: ProductVariantUpdatedDocument,
isActive: true,
});
const logger = createLogger({
service: "webhookProductVariantUpdatedWebhookHandler",
});
export const handler: NextWebhookApiHandler<ProductVariantWebhookPayloadFragment> = async (
req,
res,
context
) => {
await updateCacheOnWebhook({
authData: context.authData,
channels: context.payload,
res,
});
};
export default webhookProductVariantUpdated.createHandler(handler);

View file

@ -923,6 +923,9 @@ importers:
'@saleor/react-hook-form-macaw':
specifier: workspace:*
version: link:../../packages/react-hook-form-macaw
'@saleor/webhook-utils':
specifier: workspace:*
version: link:../../packages/webhook-utils
'@sentry/nextjs':
specifier: 7.67.0
version: 7.67.0(next@13.4.8)(react@18.2.0)
@ -947,6 +950,9 @@ importers:
'@vitejs/plugin-react':
specifier: 4.0.4
version: 4.0.4(vite@4.4.8)
dotenv:
specifier: ^16.3.1
version: 16.3.1
fast-xml-parser:
specifier: ^4.0.15
version: 4.0.15