From 0b6ce3a549a26b8392153b29d5162d2a8ebbc9e9 Mon Sep 17 00:00:00 2001 From: Jamal Soueidan Date: Mon, 1 Jul 2024 02:53:27 +0200 Subject: [PATCH 1/3] Refactor webhook functions and add new product update orchestration --- .../{ => order}/data-order-with-shipping.ts | 0 .../data-order-with-without-shipping.ts | 0 .../webhook/{ => order}/data-order.ts | 0 .../webhook/{ => order}/order.spec.ts | 0 src/functions/webhook/{ => order}/order.ts | 0 src/functions/webhook/product/update.ts | 53 +++++++++++++++++++ 6 files changed, 53 insertions(+) rename src/functions/webhook/{ => order}/data-order-with-shipping.ts (100%) rename src/functions/webhook/{ => order}/data-order-with-without-shipping.ts (100%) rename src/functions/webhook/{ => order}/data-order.ts (100%) rename src/functions/webhook/{ => order}/order.spec.ts (100%) rename src/functions/webhook/{ => order}/order.ts (100%) create mode 100644 src/functions/webhook/product/update.ts diff --git a/src/functions/webhook/data-order-with-shipping.ts b/src/functions/webhook/order/data-order-with-shipping.ts similarity index 100% rename from src/functions/webhook/data-order-with-shipping.ts rename to src/functions/webhook/order/data-order-with-shipping.ts diff --git a/src/functions/webhook/data-order-with-without-shipping.ts b/src/functions/webhook/order/data-order-with-without-shipping.ts similarity index 100% rename from src/functions/webhook/data-order-with-without-shipping.ts rename to src/functions/webhook/order/data-order-with-without-shipping.ts diff --git a/src/functions/webhook/data-order.ts b/src/functions/webhook/order/data-order.ts similarity index 100% rename from src/functions/webhook/data-order.ts rename to src/functions/webhook/order/data-order.ts diff --git a/src/functions/webhook/order.spec.ts b/src/functions/webhook/order/order.spec.ts similarity index 100% rename from src/functions/webhook/order.spec.ts rename to src/functions/webhook/order/order.spec.ts diff --git a/src/functions/webhook/order.ts b/src/functions/webhook/order/order.ts similarity index 100% rename from src/functions/webhook/order.ts rename to src/functions/webhook/order/order.ts diff --git a/src/functions/webhook/product/update.ts b/src/functions/webhook/product/update.ts new file mode 100644 index 00000000..1fdede9f --- /dev/null +++ b/src/functions/webhook/product/update.ts @@ -0,0 +1,53 @@ +import { InvocationContext } from "@azure/functions"; +import * as df from "durable-functions"; +import { OrchestrationContext } from "durable-functions"; +import { + updateArticle, + updateArticleName, +} from "~/functions/customer/orchestrations/customer/update/update-article"; +import { + updateProduct, + updateProductName, +} from "~/functions/customer/orchestrations/product/update/update-product"; +import { activityType } from "~/library/orchestration"; + +const orchestrator: df.OrchestrationHandler = function* ( + context: OrchestrationContext +) { + const input = context.df.getInput() as Input; + + const productUpdated: Awaited> = + yield context.df.callActivity( + updateProductName, + activityType(input) + ); + + const article: Awaited> = + yield context.df.callActivity( + updateArticleName, + activityType(input) + ); + + return { productUpdated, article }; +}; + +df.app.orchestration("WebhookUpdateProductOrchestration", orchestrator); + +type Input = { customerId: number; productId: number }; + +export const WebhookUpdateProductOrchestration = async ( + input: Input, + context: InvocationContext +): Promise => { + const client = df.getClient(context); + const instanceId: string = await client.startNew( + "WebhookUpdateProductOrchestration", + { + input, + } + ); + + context.log(`Started orchestration with ID = '${instanceId}'.`); + + return instanceId; +}; From 38334c3fdf2c14134331a9c86e6ddcb457aea94e Mon Sep 17 00:00:00 2001 From: Jamal Soueidan Date: Mon, 1 Jul 2024 02:53:40 +0200 Subject: [PATCH 2/3] Add article update logic to product orchestration and fix import paths This commit message captures the addition of article update functionality to the product update orchestration and the correction of import paths for order data in test files. --- .../customer/orchestrations/product/update.ts | 18 +++++++++++++++++- .../product/update/update-product.ts | 10 ++++++---- .../services/booking/get-by-group.spec.ts | 2 +- .../customer/services/booking/range.spec.ts | 4 ++-- .../customer/services/order/get.spec.ts | 2 +- 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/functions/customer/orchestrations/product/update.ts b/src/functions/customer/orchestrations/product/update.ts index 93dc541a..96295685 100644 --- a/src/functions/customer/orchestrations/product/update.ts +++ b/src/functions/customer/orchestrations/product/update.ts @@ -2,6 +2,10 @@ import { InvocationContext } from "@azure/functions"; import * as df from "durable-functions"; import { OrchestrationContext } from "durable-functions"; import { activityType } from "~/library/orchestration"; +import { + updateArticle, + updateArticleName, +} from "../customer/update/update-article"; import { updateUserMetaobject, updateUserMetaobjectName, @@ -36,6 +40,12 @@ const orchestrator: df.OrchestrationHandler = function* ( activityType(input) ); + const article: Awaited> = + yield context.df.callActivity( + updateArticleName, + activityType(input) + ); + const userField: Awaited> = yield context.df.callActivity( updateUserMetaobjectName, @@ -49,7 +59,13 @@ const orchestrator: df.OrchestrationHandler = function* ( activityType(input) ); - return { productUpdated, priceUpdated, userField, scheduleLocationsField }; + return { + productUpdated, + priceUpdated, + article, + userField, + scheduleLocationsField, + }; }; df.app.orchestration("CustomerProductUpdateOrchestration", orchestrator); diff --git a/src/functions/customer/orchestrations/product/update/update-product.ts b/src/functions/customer/orchestrations/product/update/update-product.ts index efde654b..1d4fb7ba 100644 --- a/src/functions/customer/orchestrations/product/update/update-product.ts +++ b/src/functions/customer/orchestrations/product/update/update-product.ts @@ -95,10 +95,11 @@ export const updateProduct = async ({ categories?.forEach((category) => { tags.push(`collectionid-${GidFormat.parse(category.id)}`); - - category.ruleSet?.rules.forEach((r) => { - tags.push(r.condition); - }); + if (category.ruleSet?.rules.length === 1) { + category.ruleSet?.rules.forEach((r) => { + tags.push(r.condition); + }); + } }); await ScheduleModel.updateOne( @@ -110,6 +111,7 @@ export const updateProduct = async ({ $set: { "products.$": { ...product, + active: user.active, collectionIds: categories?.map((c) => GidFormat.parse(c.id)) || product.collectionIds, diff --git a/src/functions/customer/services/booking/get-by-group.spec.ts b/src/functions/customer/services/booking/get-by-group.spec.ts index 6c4b9523..86b7aa1e 100644 --- a/src/functions/customer/services/booking/get-by-group.spec.ts +++ b/src/functions/customer/services/booking/get-by-group.spec.ts @@ -1,6 +1,6 @@ import { OrderModel } from "~/functions/order/order.models"; import { Order } from "~/functions/order/order.types"; -import { orderWithoutShipping } from "~/functions/webhook/data-order-with-without-shipping"; +import { orderWithoutShipping } from "~/functions/webhook/order/data-order-with-without-shipping"; import { createUser } from "~/library/jest/helpers"; import { createLocation } from "~/library/jest/helpers/location"; import { CustomerBookingServiceGetByGroup } from "./get-by-group"; diff --git a/src/functions/customer/services/booking/range.spec.ts b/src/functions/customer/services/booking/range.spec.ts index fd0cc94b..f009e41c 100644 --- a/src/functions/customer/services/booking/range.spec.ts +++ b/src/functions/customer/services/booking/range.spec.ts @@ -1,8 +1,8 @@ import { addMinutes, subMinutes } from "date-fns"; import { OrderModel } from "~/functions/order/order.models"; import { Order } from "~/functions/order/order.types"; -import { orderWithShipping } from "~/functions/webhook/data-order-with-shipping"; -import { orderWithoutShipping } from "~/functions/webhook/data-order-with-without-shipping"; +import { orderWithShipping } from "~/functions/webhook/order/data-order-with-shipping"; +import { orderWithoutShipping } from "~/functions/webhook/order/data-order-with-without-shipping"; import { createUser } from "~/library/jest/helpers"; import { createLocation } from "~/library/jest/helpers/location"; import { createShipping } from "~/library/jest/helpers/shipping"; diff --git a/src/functions/customer/services/order/get.spec.ts b/src/functions/customer/services/order/get.spec.ts index 33618199..d035cd64 100644 --- a/src/functions/customer/services/order/get.spec.ts +++ b/src/functions/customer/services/order/get.spec.ts @@ -1,6 +1,6 @@ import { OrderModel } from "~/functions/order/order.models"; import { Order } from "~/functions/order/order.types"; -import { orderWithoutShipping } from "~/functions/webhook/data-order-with-without-shipping"; +import { orderWithoutShipping } from "~/functions/webhook/order/data-order-with-without-shipping"; import { createUser } from "~/library/jest/helpers"; import { createLocation } from "~/library/jest/helpers/location"; import { createShipping } from "~/library/jest/helpers/shipping"; From dd4eaffcb40df37363a8f22292cce4f4e9985822 Mon Sep 17 00:00:00 2001 From: Jamal Soueidan Date: Mon, 1 Jul 2024 02:53:51 +0200 Subject: [PATCH 3/3] Add new tunnel script and refactor product categorization logic - Add a new SSH tunnel script 'tunnel2' to package.json - Simplify the product categorization logic in product-categorize.ts by removing the filter and using all collection nodes - Implement a new webhook for product updates in webook-product.function.ts - Move webhookOrderProcess import to a new directory structure in webhook-order.function.ts --- package.json | 1 + .../openai/services/product-categorize.ts | 7 +- src/functions/webhook-customer.function.ts | 6 +- src/functions/webhook-order.function.ts | 4 +- src/functions/webook-product.function.ts | 98 +++++++++++++++++++ 5 files changed, 106 insertions(+), 10 deletions(-) create mode 100644 src/functions/webook-product.function.ts diff --git a/package.json b/package.json index 7c107917..5494b711 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ }, "scripts": { "tunnel": "ssh -R 80:localhost:7071 serveo.net", + "tunnel2": "ssh -R 80:localhost:7071 nokey@localhost.run", "prebuild": "npm run clean", "build": "tsc --project tsconfig.build.json", "watch": "tsc -w", diff --git a/src/functions/openai/services/product-categorize.ts b/src/functions/openai/services/product-categorize.ts index 71f49c74..b17af97e 100644 --- a/src/functions/openai/services/product-categorize.ts +++ b/src/functions/openai/services/product-categorize.ts @@ -16,12 +16,7 @@ export const OpenAIServiceProductCategorize = async ({ try { const { data } = await shopifyAdmin().request(COLLECTIONS); - const collections = data?.collections.nodes.filter( - (collection: any) => collection.ruleSet.rules.length == 1 - ); - - // Prepare collections data as context - const collectionsContext = JSON.stringify(collections, null, 2); + const collectionsContext = JSON.stringify(data?.collections.nodes, null, 2); const content = ` ### Product Details: diff --git a/src/functions/webhook-customer.function.ts b/src/functions/webhook-customer.function.ts index f31f68c3..5836ec9b 100644 --- a/src/functions/webhook-customer.function.ts +++ b/src/functions/webhook-customer.function.ts @@ -127,8 +127,8 @@ app.http("webhookCustomerUpdate", { } return { body: "" }; - } catch(err) { - return {body: ""} + } catch (err) { + return { body: "" }; } }, }); @@ -150,6 +150,8 @@ app.http("webhookCustomerDelete", { }); if (response.deletedCount > 0) { + //TODO: + //Delete also his content in shopify? ScheduleModel.deleteMany({ customerId, }); diff --git a/src/functions/webhook-order.function.ts b/src/functions/webhook-order.function.ts index d362e6a2..e83ded53 100644 --- a/src/functions/webhook-order.function.ts +++ b/src/functions/webhook-order.function.ts @@ -1,13 +1,13 @@ import "module-alias/register"; import { + app, HttpRequest, HttpResponseInit, InvocationContext, - app, output, } from "@azure/functions"; -import { webhookOrderProcess } from "./webhook/order"; +import { webhookOrderProcess } from "./webhook/order/order"; export const orderQueueName = "webhook-order"; export const orderQueueOutput = output.storageQueue({ diff --git a/src/functions/webook-product.function.ts b/src/functions/webook-product.function.ts new file mode 100644 index 00000000..c62ec8ca --- /dev/null +++ b/src/functions/webook-product.function.ts @@ -0,0 +1,98 @@ +import "module-alias/register"; + +import { app, HttpRequest, InvocationContext } from "@azure/functions"; +import * as df from "durable-functions"; +import { connect } from "~/library/mongoose"; +import { WebhookUpdateProductOrchestration } from "./webhook/product/update"; + +type Product = { + admin_graphql_api_id: string; + body_html: string; + created_at: string; + handle: string; + id: number; + product_type: string; + published_at: string; + template_suffix: any; + title: string; + updated_at: string; + vendor: string; + status: string; + published_scope: string; + tags: string; + variants: Array<{ + admin_graphql_api_id: string; + barcode: string; + compare_at_price: string; + created_at: string; + fulfillment_service: string; + id: number; + inventory_management: any; + inventory_policy: string; + position: number; + price: string; + product_id: number; + sku: string; + taxable: boolean; + title: string; + updated_at: string; + option1: string; + option2: any; + option3: any; + grams: number; + image_id: any; + weight: number; + weight_unit: string; + inventory_item_id: number; + inventory_quantity: number; + old_inventory_quantity: number; + requires_shipping: boolean; + }>; + options: Array<{ + name: string; + id: number; + product_id: number; + position: number; + values: Array; + }>; + images: Array; + image: any; + variant_gids: Array<{ + admin_graphql_api_id: string; + updated_at: string; + }>; +}; + +app.http("webhookProductUpdate", { + methods: ["POST"], + authLevel: "anonymous", + route: "webhooks/product/update", + extraInputs: [df.input.durableClient()], + handler: async (request: HttpRequest, context: InvocationContext) => { + try { + await connect(); + const shopifyProduct = (await request.json()) as unknown as Product; + const shouldProcessUpdate = shopifyProduct.tags.includes("update"); + const regex = /userid-(\d+)/; + const match = shopifyProduct.tags.match(regex); + + if (match && shouldProcessUpdate) { + // The user ID is in the first capturing group + const customerId = match[1]; + console.log(`User ID: ${customerId}, - ${shopifyProduct.id}`); + + await WebhookUpdateProductOrchestration( + { + customerId: parseInt(customerId), + productId: shopifyProduct.id, + }, + context + ); + } + + return { body: "" }; + } catch (err) { + return { body: "" }; + } + }, +});