← back to Flockedwallpaper

fetch-flocked-products.js

266 lines

require('dotenv').config();
const axios = require('axios');
const fs = require('fs');

const SHOPIFY_STORE = process.env.SHOPIFY_STORE_NAME;
const ACCESS_TOKEN = process.env.SHOPIFY_ACCESS_TOKEN;
const API_VERSION = process.env.SHOPIFY_API_VERSION || '2024-10';

// Shopify Admin API GraphQL endpoint
const GRAPHQL_ENDPOINT = `https://${SHOPIFY_STORE}.myshopify.com/admin/api/${API_VERSION}/graphql.json`;

async function fetchAllProducts() {
    console.log('🔍 Fetching flocked products from Shopify...');

    const allProducts = [];
    let hasNextPage = true;
    let cursor = null;

    while (hasNextPage) {
        const query = `
            query ($cursor: String) {
                products(first: 250, after: $cursor, query: "flock status:active") {
                    edges {
                        cursor
                        node {
                            id
                            title
                            handle
                            description
                            descriptionHtml
                            productType
                            vendor
                            tags
                            createdAt
                            updatedAt
                            status
                            variants(first: 10) {
                                edges {
                                    node {
                                        id
                                        title
                                        sku
                                        price
                                        inventoryQuantity
                                    }
                                }
                            }
                            featuredImage {
                                url
                                altText
                            }
                            images(first: 5) {
                                edges {
                                    node {
                                        url
                                        altText
                                    }
                                }
                            }
                        }
                    }
                    pageInfo {
                        hasNextPage
                        endCursor
                    }
                }
            }
        `;

        try {
            const response = await axios.post(
                GRAPHQL_ENDPOINT,
                {
                    query: query,
                    variables: { cursor: cursor }
                },
                {
                    headers: {
                        'Content-Type': 'application/json',
                        'X-Shopify-Access-Token': ACCESS_TOKEN
                    }
                }
            );

            if (response.data.errors) {
                console.error('GraphQL Errors:', response.data.errors);
                break;
            }

            const products = response.data.data.products.edges;
            allProducts.push(...products);

            hasNextPage = response.data.data.products.pageInfo.hasNextPage;
            cursor = response.data.data.products.pageInfo.endCursor;

            console.log(`  Fetched ${allProducts.length} products so far...`);

            // Rate limiting: wait 500ms between requests
            await new Promise(resolve => setTimeout(resolve, 500));

        } catch (error) {
            console.error('❌ Error fetching products:', error.message);
            if (error.response) {
                console.error('Response data:', error.response.data);
            }
            break;
        }
    }

    console.log(`\n✅ Total products fetched: ${allProducts.length}`);
    return allProducts;
}

function filterFlockedProducts(products) {
    console.log('\n🔍 Processing flocked products...');

    const flockedProducts = [];

    // Color families for sorting
    const colorFamilies = {
        'Black': ['black', 'onyx', 'charcoal', 'ebony', 'midnight'],
        'White': ['white', 'ivory', 'cream', 'ecru', 'pearl', 'chalk', 'vanilla'],
        'Red': ['red', 'crimson', 'burgundy', 'wine', 'claret', 'cabernet', 'vermillion', 'carnelian'],
        'Pink': ['pink', 'blush', 'fuchsia', 'magenta', 'rose'],
        'Purple': ['purple', 'plum', 'aubergine', 'amethyst', 'violet', 'lavender'],
        'Blue': ['blue', 'navy', 'cobalt', 'azure', 'indigo', 'sapphire', 'slate', 'wedgewood'],
        'Green': ['green', 'emerald', 'jade', 'olive', 'viridian', 'hunter', 'evergreen'],
        'Aqua': ['aqua', 'teal', 'turquoise', 'cyan'],
        'Yellow': ['yellow', 'gold', 'golden'],
        'Orange': ['orange', 'coral', 'apricot', 'peach'],
        'Brown': ['brown', 'chocolate', 'mocha', 'tan', 'camel', 'java', 'mahogany'],
        'Beige': ['beige', 'taupe', 'buff', 'greige', 'birch', 'sand'],
        'Gray': ['gray', 'grey', 'silver', 'slate', 'frost', 'glacier'],
        'Neutral': ['neutral', 'linen', 'putty', 'concrete']
    };

    products.forEach(product => {
        const node = product.node;

        // Only include ACTIVE products
        if (node.status !== 'ACTIVE') {
            return;
        }

        // Extract SKU from variants
        const variants = node.variants.edges;
        const skus = variants.map(v => v.node.sku).filter(sku => sku);

        // Determine primary color family
        let primaryColor = 'Other';
        const tagString = node.tags.join(' ').toLowerCase();

        for (const [family, keywords] of Object.entries(colorFamilies)) {
            if (keywords.some(keyword => tagString.includes(keyword))) {
                primaryColor = family;
                break;
            }
        }

        flockedProducts.push({
            id: node.id,
            title: node.title,
            handle: node.handle,
            description: node.description,
            vendor: node.vendor,
            productType: node.productType,
            tags: node.tags,
            primaryColor: primaryColor,
            skus: skus,
            primarySku: skus[0] || 'N/A',
            price: variants[0]?.node.price || '0.00',
            image: node.featuredImage?.url || node.images.edges[0]?.node.url,
            imageAlt: node.featuredImage?.altText || node.images.edges[0]?.node.altText,
            productUrl: `https://designerwallcoverings.com/products/${node.handle}`,
            status: node.status,
            createdAt: node.createdAt
        });

        console.log(`  ✅ ${node.title} (SKU: ${skus.join(', ')})`);
    });

    console.log(`\n✅ Found ${flockedProducts.length} active flocked products`);
    return flockedProducts;
}

function sortByColor(products) {
    console.log('\n🎨 Sorting products by color...');

    // Define color order
    const colorOrder = [
        'Black', 'White', 'Red', 'Pink', 'Purple',
        'Blue', 'Green', 'Aqua', 'Yellow', 'Orange',
        'Brown', 'Beige', 'Gray', 'Neutral', 'Other'
    ];

    // Sort products by color family
    const sorted = products.sort((a, b) => {
        const indexA = colorOrder.indexOf(a.primaryColor);
        const indexB = colorOrder.indexOf(b.primaryColor);

        if (indexA !== indexB) {
            return indexA - indexB;
        }

        // Within same color, sort by title
        return a.title.localeCompare(b.title);
    });

    // Count products per color
    const colorCounts = {};
    sorted.forEach(p => {
        colorCounts[p.primaryColor] = (colorCounts[p.primaryColor] || 0) + 1;
    });

    console.log('\n📊 Products per color:');
    colorOrder.forEach(color => {
        if (colorCounts[color]) {
            console.log(`  ${color}: ${colorCounts[color]} products`);
        }
    });

    return sorted;
}

async function main() {
    try {
        console.log('🚀 Starting Flocked Product Fetch Process\n');
        console.log(`Store: ${SHOPIFY_STORE}`);
        console.log(`API Version: ${API_VERSION}\n`);

        // Fetch all products
        const allProducts = await fetchAllProducts();

        // Filter flocked products
        const flockedProducts = filterFlockedProducts(allProducts);

        // Sort by color
        const sortedProducts = sortByColor(flockedProducts);

        // Save to JSON file
        const outputPath = './flocked-products.json';
        fs.writeFileSync(outputPath, JSON.stringify(sortedProducts, null, 2));
        console.log(`\n💾 Saved ${sortedProducts.length} products to ${outputPath}`);

        // Generate a summary
        console.log('\n📊 SUMMARY:');
        console.log(`  Total Products: ${allProducts.length}`);
        console.log(`  Flocked Products: ${flockedProducts.length}`);
        console.log(`  Percentage: ${((flockedProducts.length / allProducts.length) * 100).toFixed(2)}%`);

        // Show first 5 products
        console.log('\n📦 Sample Products:');
        flockedProducts.slice(0, 5).forEach((product, index) => {
            console.log(`  ${index + 1}. ${product.title}`);
            console.log(`     SKU: ${product.primarySku}`);
            console.log(`     URL: ${product.productUrl}`);
        });

    } catch (error) {
        console.error('\n❌ Fatal Error:', error.message);
        process.exit(1);
    }
}

main();