From fc2aa2d32151ccb2e6ee4513e0f5ccce168e43f0 Mon Sep 17 00:00:00 2001 From: Jakub Vacek Date: Fri, 5 May 2023 11:47:54 +0200 Subject: [PATCH 1/7] feat: shopify demo use cases --- .../get-customer/maps/shopify.suma.js | 35 +++ .../get-customer/profile.supr | 166 +++++++++++++ .../product-update/maps/shopify.suma.js | 35 +++ .../product-update/profile.supr | 219 ++++++++++++++++++ .../subscribe-webhook/map/shopify.suma.js | 44 ++++ .../subscribe-webhook/profile.supr | 58 +++++ providers/shopify.json | 30 +++ 7 files changed, 587 insertions(+) create mode 100644 grid/customer-management/get-customer/maps/shopify.suma.js create mode 100644 grid/customer-management/get-customer/profile.supr create mode 100644 grid/product-management/product-update/maps/shopify.suma.js create mode 100644 grid/product-management/product-update/profile.supr create mode 100644 grid/webhook-management/subscribe-webhook/map/shopify.suma.js create mode 100644 grid/webhook-management/subscribe-webhook/profile.supr create mode 100644 providers/shopify.json diff --git a/grid/customer-management/get-customer/maps/shopify.suma.js b/grid/customer-management/get-customer/maps/shopify.suma.js new file mode 100644 index 00000000..4beb4864 --- /dev/null +++ b/grid/customer-management/get-customer/maps/shopify.suma.js @@ -0,0 +1,35 @@ +function RetrieveCustomer({ input, parameters, services }) { + const getCustomerResult = getCustomer(input, services, parameters); + + return getCustomerResult; +} + +function getCustomer(input, services, parameters) { + const url = `${services.default}/admin/api/${parameters.api_version}/customers/${input.customer_id}.json`; + const options = { + method: 'GET', + query: { + fields: input.fields, + }, + headers: { + 'Content-Type': 'application/json', + }, + security: 'apiKey', + }; + + const response = std.unstable.fetch(url, options).response(); + const body = response.bodyAuto() ?? {}; + + if (response.status !== 200) { + throw new std.unstable.MapError({ + code: response.status, + message: 'HTTP call failed', + }); + } + + const result = { + customer: body.customer, + }; + + return result; +} \ No newline at end of file diff --git a/grid/customer-management/get-customer/profile.supr b/grid/customer-management/get-customer/profile.supr new file mode 100644 index 00000000..04c66793 --- /dev/null +++ b/grid/customer-management/get-customer/profile.supr @@ -0,0 +1,166 @@ +name = "customer-management/get-customer" +version = "0.0.0" + +"Retrieves a single customer by their ID." +usecase RetrieveCustomer safe { + input { + "The unique identifier of the customer being retrieved." + customer_id! string! + + "Show only certain fields, specified by a comma-separated list of field names." + fields string! + } + result { + customer! { + id! number! + + email! string! + + accepts_marketing! boolean! + + created_at! string! + + updated_at! string! + + first_name! string! + + last_name! string! + + orders_count! number! + + state! string! + + total_spent! string! + + last_order_id! number! + + note! string + + verified_email! boolean! + + multipass_identifier! string + + tax_exempt! boolean! + + tags! string! + + last_order_name! string! + + currency! string! + + phone! string! + + addresses! [{ + id! number! + + customer_id! number! + + first_name string + + last_name string + + company string + + address1! string! + + address2! string! + + city! string! + + province! string! + + country! string! + + zip! string! + + phone! string! + + name! string! + + province_code! string! + + country_code! string! + + country_name! string! + + default! boolean! + }!]! + + accepts_marketing_updated_at! string! + + marketing_opt_in_level! string + + tax_exemptions! [string!]! + + email_marketing_consent! { + state! string! + + opt_in_level! string + + consent_updated_at! string! + }! + + sms_marketing_consent! { + state! string! + + opt_in_level! string! + + consent_updated_at! string! + + consent_collected_from! string! + }! + + admin_graphql_api_id! string! + + default_address! { + id! number! + + customer_id! number! + + first_name string + + last_name string + + company string + + address1! string! + + address2! string! + + city! string! + + province! string! + + country! string! + + zip! string! + + phone! string! + + name! string! + + province_code! string! + + country_code! string! + + country_name! string! + + default! boolean! + }! + }! + }! + error { + errors! [{ + message string! + + code string! + }!]! + }! + example InputExample { + input { + customer_id = '207119551', + fields = 'id,email,first_name,last_name', + } + } +} + diff --git a/grid/product-management/product-update/maps/shopify.suma.js b/grid/product-management/product-update/maps/shopify.suma.js new file mode 100644 index 00000000..08f671b1 --- /dev/null +++ b/grid/product-management/product-update/maps/shopify.suma.js @@ -0,0 +1,35 @@ +function UpdateProduct({ input, parameters, services }) { + const updateProductResult = updateProduct(input, services, parameters); + + return updateProductResult; +} + +function updateProduct(input, services, parameters) { + const url = `${services.default}/admin/api/${parameters.api_version}/products/${input.product.id}.json`; + const options = { + method: 'PUT', + body: { + product: input.product, + }, + headers: { + 'Content-Type': 'application/json', + }, + security: 'apiKey', + }; + + const response = std.unstable.fetch(url, options).response(); + const body = response.bodyAuto() ?? {}; + + if (response.status !== 200) { + throw new std.unstable.MapError({ + code: response.status, + message: 'HTTP call failed', + }); + } + + const result = { + product: body.product, + }; + + return result; +} \ No newline at end of file diff --git a/grid/product-management/product-update/profile.supr b/grid/product-management/product-update/profile.supr new file mode 100644 index 00000000..e29bc7c0 --- /dev/null +++ b/grid/product-management/product-update/profile.supr @@ -0,0 +1,219 @@ +name = "product-management/product-update" +version = "0.0.0" + +"Updates a product's information" +usecase UpdateProduct safe { + input { + product! { + "The description of the product." + body_html! string! + + "A unique human-readable string for the product." + handle! string! + + "The unique numeric identifier for the product." + id! number! + + options! { + "The name of the option." + name! string! + + "The values for the option." + values! [string!]! + }! + + "A categorization for the product." + product_type! string! + + "The status of the product." + status! string! + + "A string of comma-separated tags." + tags! string! + + "The suffix of the Liquid template used for the product page." + template_suffix! string! + + "The name of the product." + title! string! + + variants! [{ + "The barcode of the product variant." + barcode! string! + + "The weight of the product variant in grams." + grams! number! + + "The unique numeric identifier for the product variant." + id! number! + + "The available inventory quantity for the product variant." + inventory_quantity! number! + + "The first option value for the product variant." + option1! string! + + "The price of the product variant." + price! number! + + "A unique identifier for the product variant." + sku! string! + + "The title of the product variant." + title! string! + }!]! + + "The name of the product's vendor." + vendor! string! + }! + } + result { + product { + body_html string! + + created_at string! + + handle string! + + id number! + + images [{ + id number! + + product_id number! + + position number! + + created_at string! + + updated_at string! + + width number! + + height number! + + src string! + + variant_ids [{ + }!]! + }!]! + + options { + id number! + + product_id number! + + name string! + + position number! + + values [string!]! + }! + + product_type string! + + published_at string! + + published_scope string! + + status string! + + tags string! + + template_suffix string! + + title string! + + updated_at string! + + variants [{ + barcode string! + + compare_at_price None + + created_at string! + + fulfillment_service string! + + grams number! + + weight number! + + weight_unit string! + + id number! + + inventory_item_id number! + + inventory_management string! + + inventory_policy string! + + inventory_quantity number! + + option1 string! + + position number! + + price number! + + product_id number! + + requires_shipping boolean! + + sku string! + + taxable boolean! + + title string! + + updated_at string! + }!]! + + vendor string! + }! + }! + error { + errors! [{ + code! string! + + field! string! + + message! string! + }!]! + }! + example InputExample { + input { + product = { + body_html = "It's the small iPod with a big idea: Video.", + handle = 'ipod-nano', + id = 632910392, + options = { + name = 'Color', + values = [ + 'placeholder' + ], + }, + product_type = 'Cult Products', + status = 'active', + tags = 'Emotive, Flash Memory, MP3, Music', + template_suffix = 'special', + title = 'IPod Nano - 8GB', + variants = [ + { + barcode = '1234_pink', + grams = 567, + id = 808950810, + inventory_quantity = 10, + option1 = 'Pink', + price = 199.99, + sku = 'IPOD2008PINK', + title = 'Pink', + } + ], + vendor = 'Apple', + }, + } + } +} + diff --git a/grid/webhook-management/subscribe-webhook/map/shopify.suma.js b/grid/webhook-management/subscribe-webhook/map/shopify.suma.js new file mode 100644 index 00000000..82fba39a --- /dev/null +++ b/grid/webhook-management/subscribe-webhook/map/shopify.suma.js @@ -0,0 +1,44 @@ +function WebhookSubscription({ input, parameters, services }) { + const createWebhookResult = createWebhook(input, services, parameters); + + return createWebhookResult; +} + +function createWebhook(input, services, parameters) { + const url = `${services.default}admin/api/${parameters.api_version}/webhooks.json`; + const options = { + method: 'POST', + body: input.webhook, + headers: { + 'Content-Type': 'application/json', + }, + security: 'apiKey', + }; + + const response = std.unstable.fetch(url, options).response(); + const body = response.bodyAuto() ?? {}; + + if (response.status !== 201) { + throw new std.unstable.MapError({ + code: response.status, + message: 'HTTP call failed', + }); + } + + const result = { + webhook: { + id: body.webhook.id, + address: body.webhook.address, + topic: body.webhook.topic, + created_at: body.webhook.created_at, + updated_at: body.webhook.updated_at, + format: body.webhook.format, + fields: body.webhook.fields, + metafield_namespaces: body.webhook.metafield_namespaces, + api_version: body.webhook.api_version, + private_metafield_namespaces: body.webhook.private_metafield_namespaces, + }, + }; + + return result; +} \ No newline at end of file diff --git a/grid/webhook-management/subscribe-webhook/profile.supr b/grid/webhook-management/subscribe-webhook/profile.supr new file mode 100644 index 00000000..bba268fa --- /dev/null +++ b/grid/webhook-management/subscribe-webhook/profile.supr @@ -0,0 +1,58 @@ +name = "webhook-management/subscribe-webhook" +version = "0.0.0" + +"Create a new webhook subscription by specifying an address and a topic." +usecase WebhookSubscription safe { + input { + webhook! { + "The address where the webhook should send the HTTP request." + address! string! + + "The event that triggers the webhook." + topic! string! + + "The format of the data that the webhook should send." + format! string! + }! + } + result { + webhook! { + id! number! + + address! string! + + topic! string! + + created_at! string! + + updated_at! string! + + format! string! + + fields! [string!]! + + metafield_namespaces! [string!]! + + api_version! string! + + private_metafield_namespaces! [string!]! + }! + }! + error { + error! { + message! string! + + code! number! + }! + }! + example InputExample { + input { + webhook = { + address = 'https://example.com/webhook', + topic = 'customers/update', + format = 'json', + }, + } + } +} + diff --git a/providers/shopify.json b/providers/shopify.json new file mode 100644 index 00000000..c1de7f53 --- /dev/null +++ b/providers/shopify.json @@ -0,0 +1,30 @@ +{ + "name": "shopify", + "defaultService": "default", + "services": [ + { + "id": "default", + "baseUrl": "https://{SHOP}.myshopify.com/" + } + ], + "securitySchemes": [ + { + "id": "apiKey", + "type": "apiKey", + "in": "header", + "name": "X-Shopify-Access-Token" + } + ], + "parameters": [ + { + "name": "SHOP", + "description": "Your shop. You can find it in Shopify account settings.", + "default": "myshopify" + }, + { + "name": "api_version", + "description": "The version of the API being used.", + "default": "2023-04" + } + ] +} \ No newline at end of file From ff5cca5c06e0e6791f1a94579a920a20e02424f6 Mon Sep 17 00:00:00 2001 From: Jakub Vacek Date: Tue, 9 May 2023 10:47:55 +0200 Subject: [PATCH 2/7] fix: error schema --- .../create-customer/maps/shopify.suma.js | 65 +++++ .../create-customer/profile.supr | 234 ++++++++++++++++++ .../get-customer/maps/shopify.suma.js | 8 +- .../get-customer/profile.supr | 4 +- .../product-update/maps/shopify.suma.js | 9 +- .../product-update/profile.supr | 85 ++++--- .../subscribe-webhook/map/shopify.suma.js | 13 +- .../subscribe-webhook/profile.supr | 15 +- providers/shopify.json | 5 +- 9 files changed, 391 insertions(+), 47 deletions(-) create mode 100644 grid/customer-management/create-customer/maps/shopify.suma.js create mode 100644 grid/customer-management/create-customer/profile.supr diff --git a/grid/customer-management/create-customer/maps/shopify.suma.js b/grid/customer-management/create-customer/maps/shopify.suma.js new file mode 100644 index 00000000..9cce7b23 --- /dev/null +++ b/grid/customer-management/create-customer/maps/shopify.suma.js @@ -0,0 +1,65 @@ +function CreateCustomer({ input, parameters, services }) { + const createCustomerResult = createCustomer(input, services, parameters); + + return createCustomerResult; +} + +function createCustomer(input, services, parameters) { + const url = `${services.default}/admin/api/${parameters.api_version}/customers.json`; + const options = { + method: 'POST', + body: input.customer, + headers: { + 'Content-Type': 'application/json', + }, + security: 'apiKey', + }; + + const response = std.unstable.fetch(url, options).response(); + const body = response.bodyAuto() ?? {}; + + if (response.status !== 201) { + throw new std.unstable.MapError({ + errors: [ + { + message: 'Failed to create customer', + code: response.status.toString(), + }, + ], + }); + } + + const result = { + customer: { + id: body.customer?.id, + email: body.customer?.email, + accepts_marketing: body.customer?.accepts_marketing, + created_at: body.customer?.created_at, + updated_at: body.customer?.updated_at, + first_name: body.customer?.first_name, + last_name: body.customer?.last_name, + orders_count: body.customer?.orders_count, + state: body.customer?.state, + total_spent: body.customer?.total_spent, + last_order_id: body.customer?.last_order_id, + note: body.customer?.note, + verified_email: body.customer?.verified_email, + multipass_identifier: body.customer?.multipass_identifier, + tax_exempt: body.customer?.tax_exempt, + tags: body.customer?.tags, + last_order_name: body.customer?.last_order_name, + currency: body.customer?.currency, + phone: body.customer?.phone, + addresses: body.customer?.addresses, + accepts_marketing_updated_at: body.customer?.accepts_marketing_updated_at, + marketing_opt_in_level: body.customer?.marketing_opt_in_level, + tax_exemptions: body.customer?.tax_exemptions, + email_marketing_consent: body.customer?.email_marketing_consent, + sms_marketing_consent: body.customer?.sms_marketing_consent, + admin_graphql_api_id: body.customer?.admin_graphql_api_id, + default_address: body.customer?.default_address, + }, + }; + + return result; +} \ No newline at end of file diff --git a/grid/customer-management/create-customer/profile.supr b/grid/customer-management/create-customer/profile.supr new file mode 100644 index 00000000..e866397d --- /dev/null +++ b/grid/customer-management/create-customer/profile.supr @@ -0,0 +1,234 @@ +name = "customer-management/create-customer" +version = "0.0.0" + +"Creates a new customer with the provided information." +usecase CreateCustomer unsafe { + input { + customer! { + "The customer's first name." + first_name! string! + + "The customer's last name." + last_name! string! + + "The unique email address of the customer." + email! string! + + "The unique phone number (E.164 format) for this customer." + phone! string! + + "Whether the customer has verified their email address." + verified_email! boolean! + + addresses! [{ + "The first line of the address." + address1! string! + + "The city of the address." + city! string! + + "The province or state of the address." + province! string! + + "The phone number associated with the address." + phone! string! + + "The postal or zip code of the address." + zip! string! + + "The last name of the person associated with the address." + last_name! string! + + "The first name of the person associated with the address." + first_name! string! + + "The country of the address." + country! string! + }!]! + + "The customer's password." + password! string! + + "The customer's password that's confirmed." + password_confirmation! string! + + "Whether to send a welcome email to the customer." + send_email_welcome! boolean! + }! + } + result { + customer { + id number! + + email string! + + accepts_marketing boolean! + + created_at string! + + updated_at string! + + first_name string! + + last_name string! + + orders_count number! + + state string! + + total_spent string! + + last_order_id number! + + note string + + verified_email boolean! + + multipass_identifier string + + tax_exempt boolean! + + tags string! + + last_order_name string! + + currency string! + + phone string! + + addresses [{ + id number! + + customer_id number! + + first_name string + + last_name string + + company string + + address1 string! + + address2 string! + + city string! + + province string! + + country string! + + zip string! + + phone string! + + name string! + + province_code string! + + country_code string! + + country_name string! + + default boolean! + }!]! + + accepts_marketing_updated_at string! + + marketing_opt_in_level string + + tax_exemptions [string!]! + + email_marketing_consent { + state string! + + opt_in_level string + + consent_updated_at string! + }! + + sms_marketing_consent { + state string! + + opt_in_level string! + + consent_updated_at string! + + consent_collected_from string! + }! + + admin_graphql_api_id string! + + default_address { + id number! + + customer_id number! + + first_name string + + last_name string + + company string + + address1 string! + + address2 string! + + city string! + + province string! + + country string! + + zip string! + + phone string! + + name string! + + province_code string! + + country_code string! + + country_name string! + + default boolean! + }! + }! + }! + error { + errors! [{ + message! string! + + field string! + + code string! + }!]! + }! + example InputExample { + input { + customer = { + first_name = 'Steve', + last_name = 'Lastnameson', + email = 'steve.lastnameson@example.com', + phone = '+15142546011', + verified_email = true, + addresses = [ + { + address1 = '123 Oak St', + city = 'Ottawa', + province = 'ON', + phone = '555-1212', + zip = '123 ABC', + last_name = 'Lastnameson', + first_name = 'Mother', + country = 'CA', + } + ], + password = 'newpass', + password_confirmation = 'newpass', + send_email_welcome = false, + }, + } + } +} + diff --git a/grid/customer-management/get-customer/maps/shopify.suma.js b/grid/customer-management/get-customer/maps/shopify.suma.js index 4beb4864..825a4663 100644 --- a/grid/customer-management/get-customer/maps/shopify.suma.js +++ b/grid/customer-management/get-customer/maps/shopify.suma.js @@ -22,8 +22,12 @@ function getCustomer(input, services, parameters) { if (response.status !== 200) { throw new std.unstable.MapError({ - code: response.status, - message: 'HTTP call failed', + errors: [ + { + message: 'Failed to fetch customer data', + code: response.status.toString(), + }, + ], }); } diff --git a/grid/customer-management/get-customer/profile.supr b/grid/customer-management/get-customer/profile.supr index 04c66793..0ff741e3 100644 --- a/grid/customer-management/get-customer/profile.supr +++ b/grid/customer-management/get-customer/profile.supr @@ -4,10 +4,10 @@ version = "0.0.0" "Retrieves a single customer by their ID." usecase RetrieveCustomer safe { input { - "The unique identifier of the customer being retrieved." + "The unique identifier of the customer." customer_id! string! - "Show only certain fields, specified by a comma-separated list of field names." + "Comma-separated list of field names to show in the response." fields string! } result { diff --git a/grid/product-management/product-update/maps/shopify.suma.js b/grid/product-management/product-update/maps/shopify.suma.js index 08f671b1..c9be5509 100644 --- a/grid/product-management/product-update/maps/shopify.suma.js +++ b/grid/product-management/product-update/maps/shopify.suma.js @@ -22,8 +22,13 @@ function updateProduct(input, services, parameters) { if (response.status !== 200) { throw new std.unstable.MapError({ - code: response.status, - message: 'HTTP call failed', + errors: [ + { + code: response.status.toString(), + field: 'HTTP', + message: 'HTTP call failed', + }, + ], }); } diff --git a/grid/product-management/product-update/profile.supr b/grid/product-management/product-update/profile.supr index e29bc7c0..025ef182 100644 --- a/grid/product-management/product-update/profile.supr +++ b/grid/product-management/product-update/profile.supr @@ -1,12 +1,12 @@ name = "product-management/product-update" version = "0.0.0" -"Updates a product's information" -usecase UpdateProduct safe { +"Updates an existing product with new information." +usecase UpdateProduct idempotent { input { product! { "The description of the product." - body_html! string! + body_html string! "A unique human-readable string for the product." handle! string! @@ -14,52 +14,71 @@ usecase UpdateProduct safe { "The unique numeric identifier for the product." id! number! - options! { + images [{ + "The unique numeric identifier for the image." + id! number! + + "The unique numeric identifier for the product." + product_id! number! + + "The position of the image in the product's image list." + position! number! + + "The URL of the image." + src! string! + }!]! + + options { + "The unique numeric identifier for the option." + id! number! + + "The unique numeric identifier for the product." + product_id! number! + "The name of the option." name! string! - "The values for the option." + "The position of the option in the product's option list." + position! number! + values! [string!]! }! - "A categorization for the product." - product_type! string! + "The type of the product." + product_type string! "The status of the product." status! string! "A string of comma-separated tags." - tags! string! + tags string! "The suffix of the Liquid template used for the product page." - template_suffix! string! + template_suffix string! - "The name of the product." + "The title of the product." title! string! - variants! [{ - "The barcode of the product variant." - barcode! string! - - "The weight of the product variant in grams." - grams! number! - - "The unique numeric identifier for the product variant." + variants [{ + "The unique numeric identifier for the variant." id! number! - "The available inventory quantity for the product variant." - inventory_quantity! number! + "The unique numeric identifier for the product." + product_id! number! - "The first option value for the product variant." + "The value of the first option." option1! string! - "The price of the product variant." + "The position of the variant in the product's variant list." + position! number! + + "The price of the variant." price! number! - "A unique identifier for the product variant." + "A unique identifier for the variant." sku! string! - "The title of the product variant." + "The title of the variant." title! string! }!]! @@ -188,10 +207,21 @@ usecase UpdateProduct safe { body_html = "It's the small iPod with a big idea: Video.", handle = 'ipod-nano', id = 632910392, + images = [ + { + id = 850703190, + product_id = 632910392, + position = 1, + src = 'http://example.com/burton.jpg', + } + ], options = { + id = 594680422, + product_id = 632910392, name = 'Color', + position = 1, values = [ - 'placeholder' + 'Pink' ], }, product_type = 'Cult Products', @@ -201,11 +231,10 @@ usecase UpdateProduct safe { title = 'IPod Nano - 8GB', variants = [ { - barcode = '1234_pink', - grams = 567, id = 808950810, - inventory_quantity = 10, + product_id = 632910392, option1 = 'Pink', + position = 1, price = 199.99, sku = 'IPOD2008PINK', title = 'Pink', diff --git a/grid/webhook-management/subscribe-webhook/map/shopify.suma.js b/grid/webhook-management/subscribe-webhook/map/shopify.suma.js index 82fba39a..40921a27 100644 --- a/grid/webhook-management/subscribe-webhook/map/shopify.suma.js +++ b/grid/webhook-management/subscribe-webhook/map/shopify.suma.js @@ -8,7 +8,7 @@ function createWebhook(input, services, parameters) { const url = `${services.default}admin/api/${parameters.api_version}/webhooks.json`; const options = { method: 'POST', - body: input.webhook, + body: input, headers: { 'Content-Type': 'application/json', }, @@ -20,8 +20,13 @@ function createWebhook(input, services, parameters) { if (response.status !== 201) { throw new std.unstable.MapError({ - code: response.status, - message: 'HTTP call failed', + errors: [ + { + code: response.status.toString(), + message: 'Failed to create webhook', + options: body, + }, + ], }); } @@ -41,4 +46,4 @@ function createWebhook(input, services, parameters) { }; return result; -} \ No newline at end of file +} diff --git a/grid/webhook-management/subscribe-webhook/profile.supr b/grid/webhook-management/subscribe-webhook/profile.supr index bba268fa..a10cc5db 100644 --- a/grid/webhook-management/subscribe-webhook/profile.supr +++ b/grid/webhook-management/subscribe-webhook/profile.supr @@ -2,7 +2,7 @@ name = "webhook-management/subscribe-webhook" version = "0.0.0" "Create a new webhook subscription by specifying an address and a topic." -usecase WebhookSubscription safe { +usecase WebhookSubscription unsafe { input { webhook! { "The address where the webhook should send the HTTP request." @@ -11,7 +11,7 @@ usecase WebhookSubscription safe { "The event that triggers the webhook." topic! string! - "The format of the data that the webhook should send." + "The format of the data that will be sent in the webhook." format! string! }! } @@ -39,16 +39,19 @@ usecase WebhookSubscription safe { }! }! error { - error! { + errors! [{ + code! string! + message! string! - code! number! - }! + options { + }! + }!]! }! example InputExample { input { webhook = { - address = 'https://example.com/webhook', + address = 'pubsub://projectName:topicName', topic = 'customers/update', format = 'json', }, diff --git a/providers/shopify.json b/providers/shopify.json index c1de7f53..b39374f5 100644 --- a/providers/shopify.json +++ b/providers/shopify.json @@ -4,7 +4,7 @@ "services": [ { "id": "default", - "baseUrl": "https://{SHOP}.myshopify.com/" + "baseUrl": "https://{SHOP}.myshopify.com" } ], "securitySchemes": [ @@ -18,8 +18,7 @@ "parameters": [ { "name": "SHOP", - "description": "Your shop. You can find it in Shopify account settings.", - "default": "myshopify" + "description": "Your shop. You can find it in Shopify account settings." }, { "name": "api_version", From 895730f359a6f80d9bc4f0bfb028393dfa6ebf37 Mon Sep 17 00:00:00 2001 From: Jakub Vacek Date: Tue, 9 May 2023 12:32:59 +0200 Subject: [PATCH 3/7] fix: review fixes --- .../create-customer/maps/shopify.suma.js | 8 +------- grid/customer-management/create-customer/profile.supr | 3 --- .../get-customer/maps/shopify.suma.js | 8 +------- .../product-update/maps/shopify.suma.js | 9 +-------- grid/product-management/product-update/profile.supr | 4 +--- .../subscribe-webhook/map/shopify.suma.js | 11 ++--------- .../webhook-management/subscribe-webhook/profile.supr | 3 --- providers/shopify.json | 5 ----- 8 files changed, 6 insertions(+), 45 deletions(-) diff --git a/grid/customer-management/create-customer/maps/shopify.suma.js b/grid/customer-management/create-customer/maps/shopify.suma.js index 9cce7b23..3e6ab25c 100644 --- a/grid/customer-management/create-customer/maps/shopify.suma.js +++ b/grid/customer-management/create-customer/maps/shopify.suma.js @@ -1,11 +1,5 @@ function CreateCustomer({ input, parameters, services }) { - const createCustomerResult = createCustomer(input, services, parameters); - - return createCustomerResult; -} - -function createCustomer(input, services, parameters) { - const url = `${services.default}/admin/api/${parameters.api_version}/customers.json`; + const url = `${services.default}/admin/api/2023-04/customers.json`; const options = { method: 'POST', body: input.customer, diff --git a/grid/customer-management/create-customer/profile.supr b/grid/customer-management/create-customer/profile.supr index e866397d..800e1c5e 100644 --- a/grid/customer-management/create-customer/profile.supr +++ b/grid/customer-management/create-customer/profile.supr @@ -198,9 +198,6 @@ usecase CreateCustomer unsafe { error { errors! [{ message! string! - - field string! - code string! }!]! }! diff --git a/grid/customer-management/get-customer/maps/shopify.suma.js b/grid/customer-management/get-customer/maps/shopify.suma.js index 825a4663..db9fe049 100644 --- a/grid/customer-management/get-customer/maps/shopify.suma.js +++ b/grid/customer-management/get-customer/maps/shopify.suma.js @@ -1,11 +1,5 @@ function RetrieveCustomer({ input, parameters, services }) { - const getCustomerResult = getCustomer(input, services, parameters); - - return getCustomerResult; -} - -function getCustomer(input, services, parameters) { - const url = `${services.default}/admin/api/${parameters.api_version}/customers/${input.customer_id}.json`; + const url = `${services.default}/admin/api/2023-04/customers/${input.customer_id}.json`; const options = { method: 'GET', query: { diff --git a/grid/product-management/product-update/maps/shopify.suma.js b/grid/product-management/product-update/maps/shopify.suma.js index c9be5509..2d7c5c85 100644 --- a/grid/product-management/product-update/maps/shopify.suma.js +++ b/grid/product-management/product-update/maps/shopify.suma.js @@ -1,11 +1,5 @@ function UpdateProduct({ input, parameters, services }) { - const updateProductResult = updateProduct(input, services, parameters); - - return updateProductResult; -} - -function updateProduct(input, services, parameters) { - const url = `${services.default}/admin/api/${parameters.api_version}/products/${input.product.id}.json`; + const url = `${services.default}/admin/api/2023-04/products/${input.product.id}.json`; const options = { method: 'PUT', body: { @@ -25,7 +19,6 @@ function updateProduct(input, services, parameters) { errors: [ { code: response.status.toString(), - field: 'HTTP', message: 'HTTP call failed', }, ], diff --git a/grid/product-management/product-update/profile.supr b/grid/product-management/product-update/profile.supr index 025ef182..ce490df1 100644 --- a/grid/product-management/product-update/profile.supr +++ b/grid/product-management/product-update/profile.supr @@ -195,9 +195,7 @@ usecase UpdateProduct idempotent { error { errors! [{ code! string! - - field! string! - + message! string! }!]! }! diff --git a/grid/webhook-management/subscribe-webhook/map/shopify.suma.js b/grid/webhook-management/subscribe-webhook/map/shopify.suma.js index 40921a27..fb4a4b8c 100644 --- a/grid/webhook-management/subscribe-webhook/map/shopify.suma.js +++ b/grid/webhook-management/subscribe-webhook/map/shopify.suma.js @@ -1,11 +1,5 @@ function WebhookSubscription({ input, parameters, services }) { - const createWebhookResult = createWebhook(input, services, parameters); - - return createWebhookResult; -} - -function createWebhook(input, services, parameters) { - const url = `${services.default}admin/api/${parameters.api_version}/webhooks.json`; + const url = `${services.default}/admin/api/2023-04/webhooks.json`; const options = { method: 'POST', body: input, @@ -24,7 +18,6 @@ function createWebhook(input, services, parameters) { { code: response.status.toString(), message: 'Failed to create webhook', - options: body, }, ], }); @@ -46,4 +39,4 @@ function createWebhook(input, services, parameters) { }; return result; -} +} \ No newline at end of file diff --git a/grid/webhook-management/subscribe-webhook/profile.supr b/grid/webhook-management/subscribe-webhook/profile.supr index a10cc5db..5aacf9a1 100644 --- a/grid/webhook-management/subscribe-webhook/profile.supr +++ b/grid/webhook-management/subscribe-webhook/profile.supr @@ -43,9 +43,6 @@ usecase WebhookSubscription unsafe { code! string! message! string! - - options { - }! }!]! }! example InputExample { diff --git a/providers/shopify.json b/providers/shopify.json index b39374f5..eba521dd 100644 --- a/providers/shopify.json +++ b/providers/shopify.json @@ -19,11 +19,6 @@ { "name": "SHOP", "description": "Your shop. You can find it in Shopify account settings." - }, - { - "name": "api_version", - "description": "The version of the API being used.", - "default": "2023-04" } ] } \ No newline at end of file From 8fa8f378183c6f83035408c9b671e5d809eb8c55 Mon Sep 17 00:00:00 2001 From: Jakub Vacek Date: Wed, 10 May 2023 12:42:33 +0200 Subject: [PATCH 4/7] fix: review fixes --- .../create-customer/maps/shopify.suma.js | 82 +++++---- .../create-customer/profile.supr | 161 ++++++++++-------- 2 files changed, 137 insertions(+), 106 deletions(-) diff --git a/grid/customer-management/create-customer/maps/shopify.suma.js b/grid/customer-management/create-customer/maps/shopify.suma.js index 3e6ab25c..1da61819 100644 --- a/grid/customer-management/create-customer/maps/shopify.suma.js +++ b/grid/customer-management/create-customer/maps/shopify.suma.js @@ -1,8 +1,8 @@ -function CreateCustomer({ input, parameters, services }) { +function CreateCustomer({ input, services }) { const url = `${services.default}/admin/api/2023-04/customers.json`; const options = { method: 'POST', - body: input.customer, + body: input, headers: { 'Content-Type': 'application/json', }, @@ -13,45 +13,57 @@ function CreateCustomer({ input, parameters, services }) { const body = response.bodyAuto() ?? {}; if (response.status !== 201) { + let errorMessage = 'An error occurred while creating the customer.'; + if (response.status === 401) { + errorMessage = '[API] Invalid API key or access token (unrecognized login or wrong password)'; + } else if (response.status === 402) { + errorMessage = "This shop's plan does not have access to this feature"; + } else if (response.status === 403) { + errorMessage = 'User does not have access'; + } else if (response.status === 404) { + errorMessage = 'Not Found'; + } else if (response.status === 422) { + errorMessage = 'The request body contains semantic errors.'; + } else if (response.status === 429) { + errorMessage = 'Exceeded 2 calls per second for api client. Reduce request rates to resume uninterrupted service.'; + } else if (response.status >= 500) { + errorMessage = 'An unexpected error occurred'; + } + throw new std.unstable.MapError({ - errors: [ - { - message: 'Failed to create customer', - code: response.status.toString(), - }, - ], + errors: errorMessage, + status: response.status, + retryAfter: response.headers['Retry-After']?.[0] ? Number(response.headers['Retry-After'][0]) : null, + xShopifyShopApiCallLimit: response.headers['X-Shopify-Shop-Api-Call-Limit']?.[0] ? response.headers['X-Shopify-Shop-Api-Call-Limit'][0] : null, }); } const result = { customer: { - id: body.customer?.id, - email: body.customer?.email, - accepts_marketing: body.customer?.accepts_marketing, - created_at: body.customer?.created_at, - updated_at: body.customer?.updated_at, - first_name: body.customer?.first_name, - last_name: body.customer?.last_name, - orders_count: body.customer?.orders_count, - state: body.customer?.state, - total_spent: body.customer?.total_spent, - last_order_id: body.customer?.last_order_id, - note: body.customer?.note, - verified_email: body.customer?.verified_email, - multipass_identifier: body.customer?.multipass_identifier, - tax_exempt: body.customer?.tax_exempt, - tags: body.customer?.tags, - last_order_name: body.customer?.last_order_name, - currency: body.customer?.currency, - phone: body.customer?.phone, - addresses: body.customer?.addresses, - accepts_marketing_updated_at: body.customer?.accepts_marketing_updated_at, - marketing_opt_in_level: body.customer?.marketing_opt_in_level, - tax_exemptions: body.customer?.tax_exemptions, - email_marketing_consent: body.customer?.email_marketing_consent, - sms_marketing_consent: body.customer?.sms_marketing_consent, - admin_graphql_api_id: body.customer?.admin_graphql_api_id, - default_address: body.customer?.default_address, + id: body.customer.id, + email: body.customer.email, + created_at: body.customer.created_at, + updated_at: body.customer.updated_at, + first_name: body.customer.first_name, + last_name: body.customer.last_name, + orders_count: body.customer.orders_count, + state: body.customer.state, + total_spent: body.customer.total_spent, + last_order_id: body.customer.last_order_id, + note: body.customer.note, + verified_email: body.customer.verified_email, + multipass_identifier: body.customer.multipass_identifier, + tax_exempt: body.customer.tax_exempt, + tags: body.customer.tags, + last_order_name: body.customer.last_order_name, + currency: body.customer.currency, + phone: body.customer.phone, + addresses: body.customer.addresses, + tax_exemptions: body.customer.tax_exemptions, + email_marketing_consent: body.customer.email_marketing_consent, + sms_marketing_consent: body.customer.sms_marketing_consent, + admin_graphql_api_id: body.customer.admin_graphql_api_id, + default_address: body.customer.default_address, }, }; diff --git a/grid/customer-management/create-customer/profile.supr b/grid/customer-management/create-customer/profile.supr index 800e1c5e..de6367ea 100644 --- a/grid/customer-management/create-customer/profile.supr +++ b/grid/customer-management/create-customer/profile.supr @@ -11,15 +11,16 @@ usecase CreateCustomer unsafe { "The customer's last name." last_name! string! - "The unique email address of the customer." + "The customer's email address." email! string! - "The unique phone number (E.164 format) for this customer." + "The customer's phone number." phone! string! "Whether the customer has verified their email address." verified_email! boolean! + "A list of the customer's addresses." addresses! [{ "The first line of the address." address1! string! @@ -49,7 +50,7 @@ usecase CreateCustomer unsafe { "The customer's password." password! string! - "The customer's password that's confirmed." + "The confirmation of the customer's password." password_confirmation! string! "Whether to send a welcome email to the customer." @@ -57,49 +58,61 @@ usecase CreateCustomer unsafe { }! } result { + "The new customer object." customer { - id number! + "The customer's ID." + id! number! - email string! - - accepts_marketing boolean! - - created_at string! + "The unique email address of the customer. Attempting to assign the same email address to multiple customers returns an error." + email! string! - updated_at string! + "The date and time (ISO 8601 format) when the customer was created." + created_at! string! - first_name string! + "The date and time (ISO 8601 format) when the customer information was last updated." + updated_at! string! - last_name string! + first_name! string! - orders_count number! + last_name! string! - state string! + orders_count! number! - total_spent string! + state! string! + + "The total amount of money that the customer has spent across their order history." + total_spent! string! - last_order_id number! + last_order_id! number! + "A note about the customer." note string - - verified_email boolean! - + + "Whether the customer has verified their email address." + verified_email! boolean! + + "A unique identifier for the customer that's used with Multipass login." multipass_identifier string - tax_exempt boolean! - - tags string! + "Whether the customer is exempt from paying taxes on their order. If true, then taxes won't be applied to an order at checkout. If false, then taxes will be applied at checkout." + tax_exempt! boolean! + + "Tags that the shop owner has attached to the customer, formatted as a string of comma-separated values. A customer can have up to 250 tags. Each tag can have up to 255 characters." + tags! string! - last_order_name string! + "The name of the customer's last order. This is directly related to the name field on the Order resource." + last_order_name! string! - currency string! + "The three-letter code (ISO 4217 format) for the currency that the customer used when they paid for their last order. Defaults to the shop currency. Returns the shop currency for test orders." + currency! string! - phone string! + "The unique phone number (E.164 format) for this customer. Attempting to assign the same phone number to multiple customers returns an error." + phone! string! - addresses [{ - id number! + addresses! [{ + id! number! - customer_id number! + customer_id! number! first_name string @@ -107,61 +120,60 @@ usecase CreateCustomer unsafe { company string - address1 string! + address1! string! address2 string! - city string! + city! string! - province string! + province! string! - country string! + country! string! - zip string! + zip! string! - phone string! + phone! string! - name string! + name! string! - province_code string! + province_code! string! - country_code string! + country_code! string! - country_name string! + country_name! string! - default boolean! + default! boolean! }!]! - - accepts_marketing_updated_at string! - - marketing_opt_in_level string - + tax_exemptions [string!]! - email_marketing_consent { - state string! + "The marketing consent information when the customer consented to receiving marketing material by email. The email property is required to create a customer with email consent information and to update a customer for email consent that doesn't have an email recorded. The customer must have a unique email address associated to the record. The email marketing consent has the following properties:" + email_marketing_consent! { + state! string! opt_in_level string - consent_updated_at string! + consent_updated_at! string! }! - sms_marketing_consent { - state string! + "The marketing consent information when the customer consented to receiving marketing material by SMS. The phone property is required to create a customer with SMS consent information and to perform an SMS update on a customer that doesn't have a phone number recorded. The customer must have a unique phone number associated to the record. The SMS marketing consent has the following properties:" + sms_marketing_consent! { + state! string! - opt_in_level string! + opt_in_level! string! - consent_updated_at string! + consent_updated_at! string! - consent_collected_from string! + consent_collected_from! string! }! - admin_graphql_api_id string! + admin_graphql_api_id! string! - default_address { - id number! + "The default address for the customer." + default_address! { + id! number! - customer_id number! + customer_id! number! first_name string @@ -169,37 +181,44 @@ usecase CreateCustomer unsafe { company string - address1 string! + address1! string! address2 string! - city string! + city! string! - province string! + province! string! - country string! + country! string! - zip string! + zip! string! - phone string! + phone! string! - name string! + name! string! - province_code string! + province_code! string! - country_code string! + country_code! string! - country_name string! + country_name! string! - default boolean! + default! boolean! }! }! }! error { - errors! [{ - message! string! - code string! - }!]! + "Error message describing the reason for the failed operation." + errors! string! + + "HTTP status code associated with the error." + status! number! + + "The number of seconds to wait before retrying the query. This property is only present in case of a 429 Too Many Requests error." + retryAfter number + + "The header showing the number of requests made by the client and the total number of requests allowed per minute. This property is only present in the response headers." + xShopifyShopApiCallLimit string }! example InputExample { input { From 51bec20196597385435f90ebedc93a0a211563b6 Mon Sep 17 00:00:00 2001 From: Jakub Vacek Date: Wed, 10 May 2023 14:34:45 +0200 Subject: [PATCH 5/7] chore: update get customer --- .../get-customer/maps/shopify.suma.js | 29 ++++++-- .../get-customer/profile.supr | 67 ++++++++++++------- 2 files changed, 64 insertions(+), 32 deletions(-) diff --git a/grid/customer-management/get-customer/maps/shopify.suma.js b/grid/customer-management/get-customer/maps/shopify.suma.js index db9fe049..fb0f39cb 100644 --- a/grid/customer-management/get-customer/maps/shopify.suma.js +++ b/grid/customer-management/get-customer/maps/shopify.suma.js @@ -1,4 +1,4 @@ -function RetrieveCustomer({ input, parameters, services }) { +function RetrieveCustomer({ input, services }) { const url = `${services.default}/admin/api/2023-04/customers/${input.customer_id}.json`; const options = { method: 'GET', @@ -15,13 +15,28 @@ function RetrieveCustomer({ input, parameters, services }) { const body = response.bodyAuto() ?? {}; if (response.status !== 200) { + let errorMessage = 'An error occurred while retrieving the customer.'; + if (response.status === 401) { + errorMessage = '[API] Invalid API key or access token (unrecognized login or wrong password)'; + } else if (response.status === 402) { + errorMessage = "This shop's plan does not have access to this feature"; + } else if (response.status === 403) { + errorMessage = 'User does not have access'; + } else if (response.status === 404) { + errorMessage = 'Not Found'; + } else if (response.status === 422) { + errorMessage = 'The request body contains semantic errors.'; + } else if (response.status === 429) { + errorMessage = 'Exceeded 2 calls per second for api client. Reduce request rates to resume uninterrupted service.'; + } else if (response.status >= 500) { + errorMessage = 'An unexpected error occurred'; + } + throw new std.unstable.MapError({ - errors: [ - { - message: 'Failed to fetch customer data', - code: response.status.toString(), - }, - ], + errors: errorMessage, + status: response.status, + retryAfter: response.headers['Retry-After']?.[0] ? Number(response.headers['Retry-After'][0]) : null, + xShopifyShopApiCallLimit: response.headers['X-Shopify-Shop-Api-Call-Limit']?.[0] ? response.headers['X-Shopify-Shop-Api-Call-Limit'][0] : null, }); } diff --git a/grid/customer-management/get-customer/profile.supr b/grid/customer-management/get-customer/profile.supr index 0ff741e3..d8ebc996 100644 --- a/grid/customer-management/get-customer/profile.supr +++ b/grid/customer-management/get-customer/profile.supr @@ -3,23 +3,26 @@ version = "0.0.0" "Retrieves a single customer by their ID." usecase RetrieveCustomer safe { - input { - "The unique identifier of the customer." + input { + "The ID of the customer to retrieve." customer_id! string! "Comma-separated list of field names to show in the response." fields string! } result { - customer! { + "The new customer object." + customer { + "The customer's ID." id! number! + "The unique email address of the customer. Attempting to assign the same email address to multiple customers returns an error." email! string! - accepts_marketing! boolean! - + "The date and time (ISO 8601 format) when the customer was created." created_at! string! + "The date and time (ISO 8601 format) when the customer information was last updated." updated_at! string! first_name! string! @@ -29,25 +32,34 @@ usecase RetrieveCustomer safe { orders_count! number! state! string! - + + "The total amount of money that the customer has spent across their order history." total_spent! string! last_order_id! number! - note! string - + "A note about the customer." + note string + + "Whether the customer has verified their email address." verified_email! boolean! + + "A unique identifier for the customer that's used with Multipass login." + multipass_identifier string - multipass_identifier! string - + "Whether the customer is exempt from paying taxes on their order. If true, then taxes won't be applied to an order at checkout. If false, then taxes will be applied at checkout." tax_exempt! boolean! - + + "Tags that the shop owner has attached to the customer, formatted as a string of comma-separated values. A customer can have up to 250 tags. Each tag can have up to 255 characters." tags! string! + "The name of the customer's last order. This is directly related to the name field on the Order resource." last_order_name! string! + "The three-letter code (ISO 4217 format) for the currency that the customer used when they paid for their last order. Defaults to the shop currency. Returns the shop currency for test orders." currency! string! + "The unique phone number (E.164 format) for this customer. Attempting to assign the same phone number to multiple customers returns an error." phone! string! addresses! [{ @@ -63,7 +75,7 @@ usecase RetrieveCustomer safe { address1! string! - address2! string! + address2 string! city! string! @@ -85,21 +97,19 @@ usecase RetrieveCustomer safe { default! boolean! }!]! + + tax_exemptions [string!]! - accepts_marketing_updated_at! string! - - marketing_opt_in_level! string - - tax_exemptions! [string!]! - + "The marketing consent information when the customer consented to receiving marketing material by email. The email property is required to create a customer with email consent information and to update a customer for email consent that doesn't have an email recorded. The customer must have a unique email address associated to the record. The email marketing consent has the following properties:" email_marketing_consent! { state! string! - opt_in_level! string + opt_in_level string consent_updated_at! string! }! + "The marketing consent information when the customer consented to receiving marketing material by SMS. The phone property is required to create a customer with SMS consent information and to perform an SMS update on a customer that doesn't have a phone number recorded. The customer must have a unique phone number associated to the record. The SMS marketing consent has the following properties:" sms_marketing_consent! { state! string! @@ -112,6 +122,7 @@ usecase RetrieveCustomer safe { admin_graphql_api_id! string! + "The default address for the customer." default_address! { id! number! @@ -125,7 +136,7 @@ usecase RetrieveCustomer safe { address1! string! - address2! string! + address2 string! city! string! @@ -150,11 +161,17 @@ usecase RetrieveCustomer safe { }! }! error { - errors! [{ - message string! - - code string! - }!]! + "Error message describing the reason for the failed operation." + errors! string! + + "HTTP status code associated with the error." + status! number! + + "Number of seconds to wait before retrying the query, applicable for 429 Too Many Requests error." + retryAfter number + + "API call limit information, showing the number of requests made and the total number allowed per minute." + xShopifyShopApiCallLimit string }! example InputExample { input { From 05c6588cb31e6aaf7f88a582c194659ffac240d6 Mon Sep 17 00:00:00 2001 From: Jakub Vacek Date: Wed, 10 May 2023 15:01:44 +0200 Subject: [PATCH 6/7] feat: update product error handling --- .../product-update/maps/shopify.suma.js | 31 ++++++++++++++----- .../product-update/profile.supr | 16 +++++++--- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/grid/product-management/product-update/maps/shopify.suma.js b/grid/product-management/product-update/maps/shopify.suma.js index 2d7c5c85..398f274d 100644 --- a/grid/product-management/product-update/maps/shopify.suma.js +++ b/grid/product-management/product-update/maps/shopify.suma.js @@ -1,4 +1,4 @@ -function UpdateProduct({ input, parameters, services }) { +function UpdateProduct({ input, services }) { const url = `${services.default}/admin/api/2023-04/products/${input.product.id}.json`; const options = { method: 'PUT', @@ -14,14 +14,29 @@ function UpdateProduct({ input, parameters, services }) { const response = std.unstable.fetch(url, options).response(); const body = response.bodyAuto() ?? {}; - if (response.status !== 200) { + if (response.status >= 400) { + let errorMessage = 'An error occurred while retrieving the customer.'; + if (response.status === 401) { + errorMessage = '[API] Invalid API key or access token (unrecognized login or wrong password)'; + } else if (response.status === 402) { + errorMessage = "This shop's plan does not have access to this feature"; + } else if (response.status === 403) { + errorMessage = 'User does not have access'; + } else if (response.status === 404) { + errorMessage = 'Not Found'; + } else if (response.status === 422) { + errorMessage = 'The request body contains semantic errors.'; + } else if (response.status === 429) { + errorMessage = 'Exceeded 2 calls per second for api client. Reduce request rates to resume uninterrupted service.'; + } else if (response.status >= 500) { + errorMessage = 'An unexpected error occurred'; + } + throw new std.unstable.MapError({ - errors: [ - { - code: response.status.toString(), - message: 'HTTP call failed', - }, - ], + errors: errorMessage, + status: response.status, + retryAfter: response.headers['Retry-After']?.[0] ? Number(response.headers['Retry-After'][0]) : null, + xShopifyShopApiCallLimit: response.headers['X-Shopify-Shop-Api-Call-Limit']?.[0] ? response.headers['X-Shopify-Shop-Api-Call-Limit'][0] : null, }); } diff --git a/grid/product-management/product-update/profile.supr b/grid/product-management/product-update/profile.supr index ce490df1..86837f81 100644 --- a/grid/product-management/product-update/profile.supr +++ b/grid/product-management/product-update/profile.supr @@ -193,11 +193,17 @@ usecase UpdateProduct idempotent { }! }! error { - errors! [{ - code! string! - - message! string! - }!]! + "An array of error messages." + errors! [string!]! + + "HTTP status code of the error." + status number + + "The number of requests made and the total number allowed per minute." + xShopifyShopApiCallLimit string + + "The number of seconds to wait until retrying the query." + retryAfter number }! example InputExample { input { From 9fe810cf8d78e84984b3e3950cdd660b391024b8 Mon Sep 17 00:00:00 2001 From: Jakub Vacek Date: Wed, 10 May 2023 15:22:01 +0200 Subject: [PATCH 7/7] feat: update create a webhook --- .../product-update/maps/shopify.suma.js | 2 +- .../subscribe-webhook/map/shopify.suma.js | 27 ++++++++++++++---- .../subscribe-webhook/profile.supr | 28 +++++++++++++++---- 3 files changed, 44 insertions(+), 13 deletions(-) diff --git a/grid/product-management/product-update/maps/shopify.suma.js b/grid/product-management/product-update/maps/shopify.suma.js index 398f274d..2803df24 100644 --- a/grid/product-management/product-update/maps/shopify.suma.js +++ b/grid/product-management/product-update/maps/shopify.suma.js @@ -15,7 +15,7 @@ function UpdateProduct({ input, services }) { const body = response.bodyAuto() ?? {}; if (response.status >= 400) { - let errorMessage = 'An error occurred while retrieving the customer.'; + let errorMessage = 'An error occurred while updating a product.'; if (response.status === 401) { errorMessage = '[API] Invalid API key or access token (unrecognized login or wrong password)'; } else if (response.status === 402) { diff --git a/grid/webhook-management/subscribe-webhook/map/shopify.suma.js b/grid/webhook-management/subscribe-webhook/map/shopify.suma.js index fb4a4b8c..98f1d8f4 100644 --- a/grid/webhook-management/subscribe-webhook/map/shopify.suma.js +++ b/grid/webhook-management/subscribe-webhook/map/shopify.suma.js @@ -13,13 +13,28 @@ function WebhookSubscription({ input, parameters, services }) { const body = response.bodyAuto() ?? {}; if (response.status !== 201) { + let errorMessage = 'An error occurred while creating the webhook.'; + if (response.status === 401) { + errorMessage = '[API] Invalid API key or access token (unrecognized login or wrong password)'; + } else if (response.status === 402) { + errorMessage = "This shop's plan does not have access to this feature"; + } else if (response.status === 403) { + errorMessage = 'User does not have access'; + } else if (response.status === 404) { + errorMessage = 'Not Found'; + } else if (response.status === 422) { + errorMessage = 'The request body contains semantic errors.'; + } else if (response.status === 429) { + errorMessage = 'Exceeded 2 calls per second for api client. Reduce request rates to resume uninterrupted service.'; + } else if (response.status >= 500) { + errorMessage = 'An unexpected error occurred'; + } + throw new std.unstable.MapError({ - errors: [ - { - code: response.status.toString(), - message: 'Failed to create webhook', - }, - ], + errors: errorMessage, + status: response.status, + retryAfter: response.headers['Retry-After']?.[0] ? Number(response.headers['Retry-After'][0]) : null, + xShopifyShopApiCallLimit: response.headers['X-Shopify-Shop-Api-Call-Limit']?.[0] ? response.headers['X-Shopify-Shop-Api-Call-Limit'][0] : null, }); } diff --git a/grid/webhook-management/subscribe-webhook/profile.supr b/grid/webhook-management/subscribe-webhook/profile.supr index 5aacf9a1..502e40be 100644 --- a/grid/webhook-management/subscribe-webhook/profile.supr +++ b/grid/webhook-management/subscribe-webhook/profile.supr @@ -11,39 +11,55 @@ usecase WebhookSubscription unsafe { "The event that triggers the webhook." topic! string! - "The format of the data that will be sent in the webhook." + "The format of the data that the webhook should send." format! string! }! } result { webhook! { + "Unique numeric identifier for the webhook subscription." id! number! + "Destination URI to which the webhook subscription should send the POST request when an event occurs." address! string! + "Event that triggers the webhook. You can retrieve data in either JSON or XML. " topic! string! + "Date and time when the webhook subscription was created. " created_at! string! + "Date and time when the webhook subscription was last modified." updated_at! string! + "Format in which the webhook subscription should send the data. Valid values are JSON and XML. Defaults to JSON." format! string! + "An optional array of top-level resource fields that should be serialized and sent in the POST request. If absent, all fields will be sent." fields! [string!]! + "Optional array of namespaces for any metafields that should be included with each webhook." metafield_namespaces! [string!]! + "The Admin API version that Shopify uses to serialize webhook events. This value is inherited from the app that created the webhook subscription." api_version! string! + "Optional array of namespaces for any private metafields that should be included with each webhook." private_metafield_namespaces! [string!]! }! }! error { - errors! [{ - code! string! - - message! string! - }!]! + "Error message indicating the reason for the failed operation." + errors! string! + + "HTTP status code associated with the error." + status! number! + + "Number of seconds to wait before retrying the query, only applicable for 429 Too Many Requests error." + retryAfter number + + "Header showing the number of requests made and the total number allowed per minute, only applicable for REST API responses." + xShopifyShopApiCallLimit string }! example InputExample { input {