Updated to handle more then 100 products, and variants

This commit is contained in:
2025-08-27 16:26:17 -05:00
parent 98672fb99d
commit 588dda0413
4 changed files with 89 additions and 36 deletions

View File

@@ -24,4 +24,5 @@ PRICE_ADJUSTMENT_PERCENTAGE=10
# SCHEDULED_EXECUTION_TIME=2024-12-25T10:30:00Z # UTC timezone # SCHEDULED_EXECUTION_TIME=2024-12-25T10:30:00Z # UTC timezone
# SCHEDULED_EXECUTION_TIME=2024-12-25T10:30:00-05:00 # EST timezone # SCHEDULED_EXECUTION_TIME=2024-12-25T10:30:00-05:00 # EST timezone
# Leave commented out or remove to execute immediately (default behavior) # Leave commented out or remove to execute immediately (default behavior)
# Note: It uses military time (24-hour format), and wants months and days to be two digits (e.g., 08 for August, 09 for September)
# SCHEDULED_EXECUTION_TIME=2024-12-25T10:30:00 # SCHEDULED_EXECUTION_TIME=2024-12-25T10:30:00

1
.gitignore vendored
View File

@@ -152,3 +152,4 @@ coverage/
# Build artifacts # Build artifacts
build/ build/
dist/ dist/
.env.test

View File

@@ -22,7 +22,7 @@ async function debugTags() {
const productService = new ProductService(); const productService = new ProductService();
// Fetch products and analyze tags // Fetch products and analyze tags
const products = await productService.debugFetchAllProductTags(100); const products = await productService.debugFetchAllProductTags();
// Check if the target tag exists (case-insensitive search) // Check if the target tag exists (case-insensitive search)
const targetTag = config.targetTag.toLowerCase(); const targetTag = config.targetTag.toLowerCase();

View File

@@ -31,16 +31,6 @@ class ProductService {
id id
title title
tags tags
variants(first: 100) {
edges {
node {
id
price
compareAtPrice
title
}
}
}
} }
cursor cursor
} }
@@ -77,6 +67,34 @@ class ProductService {
`; `;
} }
/**
* GraphQL query to fetch variants for a product with pagination
*/
getVariantsByProductIdQuery() {
return `
query getVariantsByProductId($productId: ID!, $first: Int!, $after: String) {
node(id: $productId) {
... on Product {
variants(first: $first, after: $after) {
edges {
node {
id
price
compareAtPrice
title
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
`;
}
/** /**
* GraphQL mutation to update product variant price and Compare At price * GraphQL mutation to update product variant price and Compare At price
*/ */
@@ -98,6 +116,47 @@ class ProductService {
`; `;
} }
/**
* Fetch all variants for a given product using pagination.
* @param {string} productId - The ID of the product to fetch variants for.
* @returns {Promise<Array>} A list of all variants for the product.
*/
async fetchAllVariants(productId) {
const allVariants = [];
let hasNextPage = true;
let cursor = null;
while (hasNextPage) {
const variables = {
productId: productId,
first: 250,
after: cursor,
};
const response = await this.shopifyService.executeWithRetry(
() =>
this.shopifyService.executeQuery(
this.getVariantsByProductIdQuery(),
variables
),
this.logger
);
const productNode = response.node;
if (productNode && productNode.variants) {
const variants = productNode.variants.edges.map((edge) => edge.node);
allVariants.push(...variants);
hasNextPage = productNode.variants.pageInfo.hasNextPage;
cursor = productNode.variants.pageInfo.endCursor;
} else {
hasNextPage = false;
}
}
return allVariants;
}
/** /**
* Fetch all products with the specified tag using cursor-based pagination * Fetch all products with the specified tag using cursor-based pagination
* @param {string} tag - Tag to filter products by * @param {string} tag - Tag to filter products by
@@ -141,23 +200,14 @@ class ProductService {
const { edges, pageInfo } = response.products; const { edges, pageInfo } = response.products;
// Process products from this page // Process products from this page
const pageProducts = edges.map((edge) => ({ for (const edge of edges) {
id: edge.node.id, const product = edge.node;
title: edge.node.title, product.variants = await this.fetchAllVariants(product.id);
tags: edge.node.tags, allProducts.push(product);
variants: edge.node.variants.edges.map((variantEdge) => ({ }
id: variantEdge.node.id,
price: parseFloat(variantEdge.node.price),
compareAtPrice: variantEdge.node.compareAtPrice
? parseFloat(variantEdge.node.compareAtPrice)
: null,
title: variantEdge.node.title,
})),
}));
allProducts.push(...pageProducts);
await this.logger.info( await this.logger.info(
`Found ${pageProducts.length} products on page ${pageCount}` `Found ${edges.length} products on page ${pageCount}`
); );
// Update pagination info // Update pagination info
@@ -214,15 +264,16 @@ class ProductService {
// Check if variants have valid price data // Check if variants have valid price data
const validVariants = []; const validVariants = [];
for (const variant of product.variants) { for (const variant of product.variants) {
if (typeof variant.price !== "number" || isNaN(variant.price)) { const price = parseFloat(variant.price);
if (typeof price !== "number" || isNaN(price)) {
await this.logger.warning( await this.logger.warning(
`Skipping variant "${variant.title}" in product "${product.title}" - invalid price: ${variant.price}` `Skipping variant "${variant.title}" in product "${product.title}" - invalid price: ${variant.price}`
); );
continue; continue;
} }
if (variant.price < 0) { if (price < 0) {
await this.logger.warning( await this.logger.warning(
`Skipping variant "${variant.title}" in product "${product.title}" - negative price: ${variant.price}` `Skipping variant "${variant.title}" in product "${product.title}" - negative price: ${price}`
); );
continue; continue;
} }
@@ -865,7 +916,7 @@ class ProductService {
try { try {
// Prepare price update with Compare At price // Prepare price update with Compare At price
const priceUpdate = preparePriceUpdate( const priceUpdate = preparePriceUpdate(
variant.price, parseFloat(variant.price),
priceAdjustmentPercentage priceAdjustmentPercentage
); );
@@ -925,7 +976,7 @@ class ProductService {
* @param {number} limit - Maximum number of products to fetch for debugging * @param {number} limit - Maximum number of products to fetch for debugging
* @returns {Promise<Array>} Array of products with their tags * @returns {Promise<Array>} Array of products with their tags
*/ */
async debugFetchAllProductTags(limit = 50) { async debugFetchAllProductTags(limit = Infinity) {
await this.logger.info( await this.logger.info(
`Fetching up to ${limit} products to analyze tags...` `Fetching up to ${limit} products to analyze tags...`
); );