Search: Split webhook subscription query (#151)

* Split the search subscription query

* Add changeset
This commit is contained in:
Krzysztof Wolski 2023-02-15 12:32:33 +01:00 committed by GitHub
parent 648d99b4f5
commit c786483f8b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 376 additions and 179 deletions

View file

@ -0,0 +1,5 @@
---
"saleor-app-search": patch
---
Subscription queries for webhooks has been splitted to pass a new validation

View file

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

View file

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

View file

@ -1,41 +0,0 @@
subscription ProductEdited {
event {
__typename
... on ProductUpdated {
__typename
product {
...ProductWebhookPayload
}
}
... on ProductCreated {
__typename
product {
...ProductWebhookPayload
}
}
... on ProductDeleted {
__typename
product {
...ProductWebhookPayload
}
}
... on ProductVariantCreated {
__typename
productVariant {
...ProductVariantWebhookPayload
}
}
... on ProductVariantUpdated {
__typename
productVariant {
...ProductVariantWebhookPayload
}
}
... on ProductVariantDeleted {
__typename
productVariant {
...ProductVariantWebhookPayload
}
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,61 @@
import { AuthData } from "@saleor/app-sdk/APL";
import { createDebug } from "../debug";
import { createClient } from "../graphql";
import { createSettingsManager } from "../metadata";
interface GetAlgoliaConfigurationArgs {
authData: AuthData;
}
const debug = createDebug("getAlgoliaConfiguration");
export const getAlgoliaConfiguration = async ({ authData }: GetAlgoliaConfigurationArgs) => {
const client = createClient(authData.saleorApiUrl, async () =>
Promise.resolve({ token: authData.token })
);
const settings = createSettingsManager(client);
try {
const secretKey = await settings.get("secretKey", authData.domain);
if (!secretKey?.length) {
return {
errors: [
{
message:
"Missing secret key to the Algolia API. Please, configure the application first.",
},
],
};
}
const appId = await settings.get("appId", authData.domain);
if (!appId?.length) {
return {
errors: [
{
message: "Missing App ID to the Algolia API. Please, configure the application first.",
},
],
};
}
const indexNamePrefix = (await settings.get("indexNamePrefix", authData.domain)) || "";
debug("Configuration fetched");
return {
settings: {
appId,
secretKey,
indexNamePrefix,
},
};
} catch (error) {
debug("Unexpected error during fetching the configuration");
if (error instanceof Error) {
debug(error.message);
}
return {
errors: [{ message: "Couldn't fetch the settings from the API" }],
};
}
};

View file

@ -1,102 +0,0 @@
import { NextWebhookApiHandler } from "@saleor/app-sdk/handlers/next";
import { ProductEditedSubscription } from "../../../../../generated/graphql";
import { AlgoliaSearchProvider } from "../../../../lib/algolia/algoliaSearchProvider";
import { createDebug } from "../../../../lib/debug";
import { createClient } from "../../../../lib/graphql";
import { createSettingsManager } from "../../../../lib/metadata";
import { AlgoliaConfigurationFields } from "../../../../lib/algolia/types";
const debug = createDebug("Webhooks handler");
export const handler: NextWebhookApiHandler<ProductEditedSubscription["event"]> = async (
req,
res,
context
) => {
const { event, authData } = context;
debug(
`New event ${event} (${context.payload?.__typename}) from the ${authData.domain} domain has been received!`
);
const client = createClient(authData.saleorApiUrl, async () =>
Promise.resolve({ token: authData.token })
);
const settings = createSettingsManager(client);
const algoliaConfiguration: AlgoliaConfigurationFields = {
secretKey: (await settings.get("secretKey", authData.domain)) || "",
appId: (await settings.get("appId", authData.domain)) || "",
indexNamePrefix: (await settings.get("indexNamePrefix", authData.domain)) || "",
};
if (!algoliaConfiguration?.appId) {
debug("Missing AppID configuration - returning error response");
return res.status(500).json({
message: `Missing 'appId'`,
});
}
if (!algoliaConfiguration.secretKey) {
debug("Missing SecretKey configuration - returning error response");
return res.status(500).json({
message: `Missing 'secretKey'`,
});
}
const searchProvider = new AlgoliaSearchProvider({
appId: algoliaConfiguration.appId,
apiKey: algoliaConfiguration.secretKey,
indexNamePrefix: algoliaConfiguration.indexNamePrefix,
});
switch (context.payload?.__typename) {
case "ProductCreated": {
const { product } = context.payload;
if (product) {
await searchProvider.createProduct(product);
}
res.status(200).end();
return;
}
case "ProductUpdated": {
const { product } = context.payload;
if (product) {
await searchProvider.updateProduct(product);
}
res.status(200).end();
return;
}
case "ProductDeleted": {
const { product } = context.payload;
if (product) {
await searchProvider.deleteProduct(product);
}
res.status(200).end();
return;
}
case "ProductVariantCreated": {
const { productVariant } = context.payload;
if (productVariant) {
await searchProvider.createProductVariant(productVariant);
}
res.status(200).end();
return;
}
case "ProductVariantUpdated": {
const { productVariant } = context.payload;
if (productVariant) {
await searchProvider.updateProductVariant(productVariant);
}
res.status(200).end();
return;
}
case "ProductVariantDeleted": {
const { productVariant } = context.payload;
if (productVariant) {
await searchProvider.deleteProductVariant(productVariant);
}
res.status(200).end();
return;
}
}
};

View file

@ -1,7 +1,9 @@
import { SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import { ProductEditedDocument, ProductEditedSubscription } from "../../../../../generated/graphql";
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import { ProductCreated, ProductCreatedDocument } from "../../../../../generated/graphql";
import { saleorApp } from "../../../../../saleor-app";
import { handler } from "./_index";
import { AlgoliaSearchProvider } from "../../../../lib/algolia/algoliaSearchProvider";
import { getAlgoliaConfiguration } from "../../../../lib/algolia/getAlgoliaConfiguration";
import { createDebug } from "../../../../lib/debug";
export const config = {
api: {
@ -9,11 +11,43 @@ export const config = {
},
};
export const webhookProductCreated = new SaleorAsyncWebhook<ProductEditedSubscription["event"]>({
export const webhookProductCreated = new SaleorAsyncWebhook<ProductCreated>({
webhookPath: "api/webhooks/saleor/product_created",
asyncEvent: "PRODUCT_CREATED",
apl: saleorApp.apl,
subscriptionQueryAst: ProductEditedDocument,
subscriptionQueryAst: ProductCreatedDocument,
});
export const handler: NextWebhookApiHandler<ProductCreated> = async (req, res, context) => {
const debug = createDebug(`Webhook handler - ${webhookProductCreated.asyncEvent}`);
const { event, authData } = context;
debug(
`New event ${event} (${context.payload?.__typename}) from the ${authData.domain} domain has been received!`
);
const { settings, errors } = await getAlgoliaConfiguration({ authData });
if (errors?.length || !settings) {
debug("Aborting due to lack of settings");
debug(errors);
return res.status(400).json({
message: errors[0].message,
});
}
const searchProvider = new AlgoliaSearchProvider({
appId: settings.appId,
apiKey: settings.secretKey,
indexNamePrefix: settings.indexNamePrefix,
});
const { product } = context.payload;
if (product) {
await searchProvider.createProduct(product);
}
res.status(200).end();
return;
};
export default webhookProductCreated.createHandler(handler);

View file

@ -1,7 +1,9 @@
import { SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import { ProductEditedDocument, ProductEditedSubscription } from "../../../../../generated/graphql";
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import { ProductDeleted, ProductDeletedDocument } from "../../../../../generated/graphql";
import { saleorApp } from "../../../../../saleor-app";
import { handler } from "./_index";
import { AlgoliaSearchProvider } from "../../../../lib/algolia/algoliaSearchProvider";
import { getAlgoliaConfiguration } from "../../../../lib/algolia/getAlgoliaConfiguration";
import { createDebug } from "../../../../lib/debug";
export const config = {
api: {
@ -9,11 +11,43 @@ export const config = {
},
};
export const webhookProductDeleted = new SaleorAsyncWebhook<ProductEditedSubscription["event"]>({
export const webhookProductDeleted = new SaleorAsyncWebhook<ProductDeleted>({
webhookPath: "api/webhooks/saleor/product_deleted",
asyncEvent: "PRODUCT_DELETED",
apl: saleorApp.apl,
subscriptionQueryAst: ProductEditedDocument,
subscriptionQueryAst: ProductDeletedDocument,
});
export const handler: NextWebhookApiHandler<ProductDeleted> = async (req, res, context) => {
const debug = createDebug(`Webhook handler - ${webhookProductDeleted.asyncEvent}`);
const { event, authData } = context;
debug(
`New event ${event} (${context.payload?.__typename}) from the ${authData.domain} domain has been received!`
);
const { settings, errors } = await getAlgoliaConfiguration({ authData });
if (errors?.length || !settings) {
debug("Aborting due to lack of settings");
debug(errors);
return res.status(400).json({
message: errors[0].message,
});
}
const searchProvider = new AlgoliaSearchProvider({
appId: settings.appId,
apiKey: settings.secretKey,
indexNamePrefix: settings.indexNamePrefix,
});
const { product } = context.payload;
if (product) {
await searchProvider.deleteProduct(product);
}
res.status(200).end();
return;
};
export default webhookProductDeleted.createHandler(handler);

View file

@ -1,7 +1,9 @@
import { SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import { ProductEditedDocument, ProductEditedSubscription } from "../../../../../generated/graphql";
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import { ProductUpdated, ProductUpdatedDocument } from "../../../../../generated/graphql";
import { saleorApp } from "../../../../../saleor-app";
import { handler } from "./_index";
import { AlgoliaSearchProvider } from "../../../../lib/algolia/algoliaSearchProvider";
import { getAlgoliaConfiguration } from "../../../../lib/algolia/getAlgoliaConfiguration";
import { createDebug } from "../../../../lib/debug";
export const config = {
api: {
@ -9,11 +11,43 @@ export const config = {
},
};
export const webhookProductUpdated = new SaleorAsyncWebhook<ProductEditedSubscription["event"]>({
export const webhookProductUpdated = new SaleorAsyncWebhook<ProductUpdated>({
webhookPath: "api/webhooks/saleor/product_updated",
asyncEvent: "PRODUCT_UPDATED",
apl: saleorApp.apl,
subscriptionQueryAst: ProductEditedDocument,
subscriptionQueryAst: ProductUpdatedDocument,
});
export const handler: NextWebhookApiHandler<ProductUpdated> = async (req, res, context) => {
const debug = createDebug(`Webhook handler - ${webhookProductUpdated.asyncEvent}`);
const { event, authData } = context;
debug(
`New event ${event} (${context.payload?.__typename}) from the ${authData.domain} domain has been received!`
);
const { settings, errors } = await getAlgoliaConfiguration({ authData });
if (errors?.length || !settings) {
debug("Aborting due to lack of settings");
debug(errors);
return res.status(400).json({
message: errors[0].message,
});
}
const searchProvider = new AlgoliaSearchProvider({
appId: settings.appId,
apiKey: settings.secretKey,
indexNamePrefix: settings.indexNamePrefix,
});
const { product } = context.payload;
if (product) {
await searchProvider.updateProduct(product);
}
res.status(200).end();
return;
};
export default webhookProductUpdated.createHandler(handler);

View file

@ -1,20 +1,56 @@
import { SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import { ProductEditedDocument, ProductEditedSubscription } from "../../../../../generated/graphql";
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import {
ProductVariantCreated,
ProductVariantCreatedDocument,
} from "../../../../../generated/graphql";
import { saleorApp } from "../../../../../saleor-app";
import { handler } from "./_index";
import { AlgoliaSearchProvider } from "../../../../lib/algolia/algoliaSearchProvider";
import { getAlgoliaConfiguration } from "../../../../lib/algolia/getAlgoliaConfiguration";
import { createDebug } from "../../../../lib/debug";
export const config = {
api: {
bodyParser: false,
},
};
export const webhookProductVariantCreated = new SaleorAsyncWebhook<
ProductEditedSubscription["event"]
>({
export const webhookProductVariantCreated = new SaleorAsyncWebhook<ProductVariantCreated>({
webhookPath: "api/webhooks/saleor/product_variant_created",
asyncEvent: "PRODUCT_VARIANT_CREATED",
apl: saleorApp.apl,
subscriptionQueryAst: ProductEditedDocument,
subscriptionQueryAst: ProductVariantCreatedDocument,
});
export const handler: NextWebhookApiHandler<ProductVariantCreated> = async (req, res, context) => {
const debug = createDebug(`Webhook handler - ${webhookProductVariantCreated.asyncEvent}`);
const { event, authData } = context;
debug(
`New event ${event} (${context.payload?.__typename}) from the ${authData.domain} domain has been received!`
);
const { settings, errors } = await getAlgoliaConfiguration({ authData });
if (errors?.length || !settings) {
debug("Aborting due to lack of settings");
debug(errors);
return res.status(400).json({
message: errors[0].message,
});
}
const searchProvider = new AlgoliaSearchProvider({
appId: settings.appId,
apiKey: settings.secretKey,
indexNamePrefix: settings.indexNamePrefix,
});
const { productVariant } = context.payload;
if (productVariant) {
await searchProvider.createProductVariant(productVariant);
}
res.status(200).end();
return;
};
export default webhookProductVariantCreated.createHandler(handler);

View file

@ -1,7 +1,12 @@
import { SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import { ProductEditedDocument, ProductEditedSubscription } from "../../../../../generated/graphql";
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import {
ProductVariantDeleted,
ProductVariantDeletedDocument,
} from "../../../../../generated/graphql";
import { saleorApp } from "../../../../../saleor-app";
import { handler } from "./_index";
import { AlgoliaSearchProvider } from "../../../../lib/algolia/algoliaSearchProvider";
import { getAlgoliaConfiguration } from "../../../../lib/algolia/getAlgoliaConfiguration";
import { createDebug } from "../../../../lib/debug";
export const config = {
api: {
@ -9,13 +14,43 @@ export const config = {
},
};
export const webhookProductVariantDeleted = new SaleorAsyncWebhook<
ProductEditedSubscription["event"]
>({
export const webhookProductVariantDeleted = new SaleorAsyncWebhook<ProductVariantDeleted>({
webhookPath: "api/webhooks/saleor/product_variant_deleted",
asyncEvent: "PRODUCT_VARIANT_DELETED",
apl: saleorApp.apl,
subscriptionQueryAst: ProductEditedDocument,
subscriptionQueryAst: ProductVariantDeletedDocument,
});
export const handler: NextWebhookApiHandler<ProductVariantDeleted> = async (req, res, context) => {
const debug = createDebug(`Webhook handler - ${webhookProductVariantDeleted.asyncEvent}`);
const { event, authData } = context;
debug(
`New event ${event} (${context.payload?.__typename}) from the ${authData.domain} domain has been received!`
);
const { settings, errors } = await getAlgoliaConfiguration({ authData });
if (errors?.length || !settings) {
debug("Aborting due to lack of settings");
debug(errors);
return res.status(400).json({
message: errors[0].message,
});
}
const searchProvider = new AlgoliaSearchProvider({
appId: settings.appId,
apiKey: settings.secretKey,
indexNamePrefix: settings.indexNamePrefix,
});
const { productVariant } = context.payload;
if (productVariant) {
await searchProvider.deleteProductVariant(productVariant);
}
res.status(200).end();
return;
};
export default webhookProductVariantDeleted.createHandler(handler);

View file

@ -1,7 +1,12 @@
import { SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import { ProductEditedDocument, ProductEditedSubscription } from "../../../../../generated/graphql";
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
import {
ProductVariantUpdated,
ProductVariantUpdatedDocument,
} from "../../../../../generated/graphql";
import { saleorApp } from "../../../../../saleor-app";
import { handler } from "./_index";
import { AlgoliaSearchProvider } from "../../../../lib/algolia/algoliaSearchProvider";
import { getAlgoliaConfiguration } from "../../../../lib/algolia/getAlgoliaConfiguration";
import { createDebug } from "../../../../lib/debug";
export const config = {
api: {
@ -9,13 +14,43 @@ export const config = {
},
};
export const webhookProductVariantUpdated = new SaleorAsyncWebhook<
ProductEditedSubscription["event"]
>({
export const webhookProductVariantUpdated = new SaleorAsyncWebhook<ProductVariantUpdated>({
webhookPath: "api/webhooks/saleor/product_variant_updated",
asyncEvent: "PRODUCT_VARIANT_UPDATED",
apl: saleorApp.apl,
subscriptionQueryAst: ProductEditedDocument,
subscriptionQueryAst: ProductVariantUpdatedDocument,
});
export const handler: NextWebhookApiHandler<ProductVariantUpdated> = async (req, res, context) => {
const debug = createDebug(`Webhook handler - ${webhookProductVariantUpdated.asyncEvent}`);
const { event, authData } = context;
debug(
`New event ${event} (${context.payload?.__typename}) from the ${authData.domain} domain has been received!`
);
const { settings, errors } = await getAlgoliaConfiguration({ authData });
if (errors?.length || !settings) {
debug("Aborting due to lack of settings");
debug(errors);
return res.status(400).json({
message: errors[0].message,
});
}
const searchProvider = new AlgoliaSearchProvider({
appId: settings.appId,
apiKey: settings.secretKey,
indexNamePrefix: settings.indexNamePrefix,
});
const { productVariant } = context.payload;
if (productVariant) {
await searchProvider.updateProductVariant(productVariant);
}
res.status(200).end();
return;
};
export default webhookProductVariantUpdated.createHandler(handler);