Shopidy product variants can now be queried
This commit is contained in:
@@ -15,12 +15,15 @@ const trigger = new Trigger({
|
||||
run: async (event, ctx) => {
|
||||
await ctx.logger.info("Get Shopify products for my store");
|
||||
|
||||
const response = await shopify.getProducts("get-shopify-products");
|
||||
const response = await shopify.searchProductVariants(
|
||||
"get-shopify-variants",
|
||||
{}
|
||||
);
|
||||
console.log(response);
|
||||
|
||||
await ctx.logger.debug("Debug message");
|
||||
|
||||
return response.message;
|
||||
return response;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,10 +5,14 @@ import {
|
||||
PerformRequestOptions,
|
||||
RequestIntegration,
|
||||
} from "../types";
|
||||
import { createClient, gql } from "@urql/core";
|
||||
import { Client, createClient, gql } from "@urql/core";
|
||||
import { shopify } from "internal-providers";
|
||||
import { z } from "zod";
|
||||
|
||||
const log = debug("trigger:integrations:slack");
|
||||
|
||||
type SearchVariantsSuccessResponse = z.infer<
|
||||
typeof shopify.schemas.SearchVariantsSuccessResponseSchema
|
||||
>;
|
||||
class ShopifyRequestIntegration implements RequestIntegration {
|
||||
constructor(
|
||||
private readonly baseUrlFormat: string = "https://{shop}.myshopify.com/admin/api/2021-07/graphql.json"
|
||||
@@ -39,14 +43,65 @@ class ShopifyRequestIntegration implements RequestIntegration {
|
||||
},
|
||||
});
|
||||
|
||||
switch (options.endpoint) {
|
||||
case "productVariants.search": {
|
||||
return this.#searchProductVariants(client, options.params);
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unknown endpoint: ${options.endpoint}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
displayProperties(endpoint: string, params: any): DisplayProperties {
|
||||
return {
|
||||
title: "Temporary",
|
||||
};
|
||||
throw new Error(`Unknown endpoint: ${endpoint}`);
|
||||
}
|
||||
|
||||
async #searchProductVariants(
|
||||
client: Client,
|
||||
params: any
|
||||
): Promise<PerformedRequestResponse> {
|
||||
const parsedParams = shopify.schemas.SearchVariantsBodySchema.parse(params);
|
||||
log("productVariants.search %O", parsedParams);
|
||||
|
||||
try {
|
||||
const firstLast = buildFirstLast(parsedParams);
|
||||
const filters = parsedParams.filter
|
||||
? buildFilter(parsedParams.filter)
|
||||
: undefined;
|
||||
|
||||
const query = gql`
|
||||
query {
|
||||
products(first: 5) {
|
||||
productVariants(${firstLast}${filters ? `, ${filters}` : ""}) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
handle
|
||||
title
|
||||
createdAt
|
||||
updatedAt
|
||||
price
|
||||
product {
|
||||
id
|
||||
}
|
||||
sku
|
||||
barcode
|
||||
compareAtPrice
|
||||
fulfillmentService {
|
||||
id
|
||||
}
|
||||
image {
|
||||
id
|
||||
}
|
||||
inventoryQuantity
|
||||
requiresShipping
|
||||
position
|
||||
taxCode
|
||||
taxable
|
||||
weight
|
||||
weightUnit
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
@@ -58,7 +113,7 @@ class ShopifyRequestIntegration implements RequestIntegration {
|
||||
const result = await client.query(query, {}).toPromise();
|
||||
|
||||
if (result.error) {
|
||||
console.error("Shopify result error", result.error);
|
||||
log("productVariants.search failed %O", result.error);
|
||||
return {
|
||||
ok: false,
|
||||
isRetryable: false,
|
||||
@@ -69,18 +124,63 @@ class ShopifyRequestIntegration implements RequestIntegration {
|
||||
};
|
||||
}
|
||||
|
||||
console.log("Shopify success result", result.data);
|
||||
if (result.data === undefined) {
|
||||
log("productVariants.search data undefined %O");
|
||||
return {
|
||||
ok: false,
|
||||
isRetryable: false,
|
||||
response: {
|
||||
output: {
|
||||
message: "No data returned",
|
||||
},
|
||||
context: {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
console.log("result.data", JSON.stringify(result.data));
|
||||
|
||||
const parsed = VariantsSearchQueryResultSchema.parse(result.data);
|
||||
|
||||
const response: SearchVariantsSuccessResponse = {
|
||||
count: parsed.productVariants.edges.length,
|
||||
productVariants: parsed.productVariants.edges.map((p) => ({
|
||||
id: p.node.id,
|
||||
title: p.node.id,
|
||||
createdAt: p.node.createdAt,
|
||||
updatedAt: p.node.updatedAt,
|
||||
price: p.node.price,
|
||||
product: p.node.product,
|
||||
sku: p.node.sku,
|
||||
barcode: p.node.barcode,
|
||||
compareAtPrice: p.node.compareAtPrice,
|
||||
fulfillmentService: p.node.fulfillmentService,
|
||||
image: p.node.image,
|
||||
inventoryQuantity: p.node.inventoryQuantity,
|
||||
requiresShipping: p.node.requiresShipping,
|
||||
position: p.node.position,
|
||||
taxCode: p.node.taxCode,
|
||||
taxable: p.node.taxable,
|
||||
weight: p.node.weight,
|
||||
weightUnit: p.node.weightUnit,
|
||||
})),
|
||||
};
|
||||
|
||||
const performedRequest = {
|
||||
ok: true,
|
||||
isRetryable: false,
|
||||
response: {
|
||||
output: result.data,
|
||||
output: response,
|
||||
context: {},
|
||||
},
|
||||
};
|
||||
|
||||
log("productVariants.search performedRequest %O", performedRequest);
|
||||
|
||||
return performedRequest;
|
||||
} catch (error) {
|
||||
console.error("Shopify query error", error);
|
||||
console.error("productVariants.search query error %O", error);
|
||||
log("productVariants.search query error %O", error);
|
||||
return {
|
||||
ok: false,
|
||||
isRetryable: false,
|
||||
@@ -91,13 +191,41 @@ class ShopifyRequestIntegration implements RequestIntegration {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
displayProperties(endpoint: string, params: any): DisplayProperties {
|
||||
return {
|
||||
title: "Temporary",
|
||||
};
|
||||
throw new Error(`Unknown endpoint: ${endpoint}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const requests = new ShopifyRequestIntegration();
|
||||
|
||||
function buildFirstLast(
|
||||
firstLast: z.infer<typeof shopify.schemas.FirstOrLastSchema>
|
||||
) {
|
||||
let first = firstLast.first;
|
||||
if (first === undefined && firstLast.last === undefined) {
|
||||
first = 100;
|
||||
}
|
||||
|
||||
if (first !== undefined) {
|
||||
return `first: ${first}`;
|
||||
}
|
||||
return `last: ${firstLast.last}`;
|
||||
}
|
||||
|
||||
function buildFilter(filter: Record<string, string[]>): string {
|
||||
let filterQueries: string[] = [];
|
||||
|
||||
for (const [key, values] of Object.entries(filter)) {
|
||||
filterQueries.push(
|
||||
`(${values.map((value) => `${key}:${value}`).join(" OR ")})`
|
||||
);
|
||||
}
|
||||
|
||||
return filterQueries.join(" AND ");
|
||||
}
|
||||
|
||||
const VariantsSearchQueryResultSchema = z.object({
|
||||
productVariants: z.object({
|
||||
edges: z.array(z.object({ node: shopify.schemas.ProductVariantSchema })),
|
||||
pageInfo: z.object({
|
||||
hasNextPage: z.boolean(),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// import * as schemas from "./schemas";
|
||||
import * as schemas from "./schemas";
|
||||
|
||||
export const shopify = {
|
||||
name: "Shopify",
|
||||
@@ -20,5 +20,5 @@ export const shopify = {
|
||||
],
|
||||
documentation: `1. Follow (this guide)[https://help.shopify.com/en/manual/apps/custom-apps] to enable Custom apps`,
|
||||
},
|
||||
schemas: {},
|
||||
schemas,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const FirstOrLastSchema = z
|
||||
.object({
|
||||
first: z.number().optional(),
|
||||
last: z.number().optional(),
|
||||
})
|
||||
.default({ first: 100 });
|
||||
|
||||
const objectWithId = z.object({
|
||||
id: z.string(),
|
||||
});
|
||||
|
||||
export const ProductVariantSchema = z.object({
|
||||
id: z.string(),
|
||||
title: z.string().nullable(),
|
||||
createdAt: z.string().nullable(),
|
||||
updatedAt: z.string().nullable(),
|
||||
price: z.string().nullable(),
|
||||
product: objectWithId,
|
||||
sku: z.string().nullable(),
|
||||
barcode: z.string().nullable(),
|
||||
compareAtPrice: z.string().nullable(),
|
||||
fulfillmentService: objectWithId.nullable(),
|
||||
image: objectWithId.nullable(),
|
||||
inventoryQuantity: z.number().nullable(),
|
||||
requiresShipping: z.boolean().nullable(),
|
||||
position: z.number().nullable(),
|
||||
taxCode: z.string().nullable(),
|
||||
taxable: z.boolean().nullable(),
|
||||
weight: z.number().nullable(),
|
||||
weightUnit: z.string().nullable(),
|
||||
});
|
||||
|
||||
export const SearchVariantsBodySchema = FirstOrLastSchema.and(
|
||||
z.object({
|
||||
filter: z
|
||||
.object({
|
||||
productIds: z.array(z.string()).optional(),
|
||||
skus: z.array(z.string()).optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
);
|
||||
|
||||
export const SearchVariantsSuccessResponseSchema = z.object({
|
||||
count: z.number(),
|
||||
productVariants: z.array(ProductVariantSchema),
|
||||
});
|
||||
|
||||
@@ -2,7 +2,18 @@ import { getTriggerRun } from "@trigger.dev/sdk";
|
||||
import { z } from "zod";
|
||||
import { shopify } from "internal-providers";
|
||||
|
||||
export async function getProducts(key: string): Promise<any> {
|
||||
export type SearchVariantsOptions = z.infer<
|
||||
typeof shopify.schemas.SearchVariantsBodySchema
|
||||
>;
|
||||
|
||||
export type SearchVariantsResponse = z.infer<
|
||||
typeof shopify.schemas.SearchVariantsSuccessResponseSchema
|
||||
>;
|
||||
|
||||
export async function searchProductVariants(
|
||||
key: string,
|
||||
options: SearchVariantsOptions
|
||||
): Promise<SearchVariantsResponse> {
|
||||
const run = getTriggerRun();
|
||||
|
||||
if (!run) {
|
||||
@@ -11,10 +22,10 @@ export async function getProducts(key: string): Promise<any> {
|
||||
|
||||
const output = await run.performRequest(key, {
|
||||
service: "shopify",
|
||||
endpoint: "products.get",
|
||||
params: {},
|
||||
endpoint: "productVariants.search",
|
||||
params: options,
|
||||
response: {
|
||||
schema: z.any(),
|
||||
schema: shopify.schemas.SearchVariantsSuccessResponseSchema,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -373,6 +373,8 @@ export class TriggerClient<TSchema extends z.ZodTypeAny> {
|
||||
};
|
||||
}
|
||||
|
||||
console.error(anyError);
|
||||
|
||||
return {
|
||||
name: "UnknownError",
|
||||
message: "An unknown error occurred",
|
||||
|
||||
Reference in New Issue
Block a user