← back to Glassbeadedwallpaper

fetch-glass-bead-products.js

325 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 glass bead, sequin, and glitter products from Shopify...');

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

    // Search for products with "glass bead", "sequin", or "glitter" in title/tags
    const searchQueries = [
        'glass bead',
        'glass beaded',
        'sequin',
        'glitter'
    ];

    for (const searchTerm of searchQueries) {
        console.log(`\n🔎 Searching for: "${searchTerm}"`);
        hasNextPage = true;
        cursor = null;

        while (hasNextPage) {
            const query = `
                query ($cursor: String) {
                    products(first: 250, after: $cursor, query: "${searchTerm} 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;

                // Add products, avoiding duplicates
                products.forEach(product => {
                    const exists = allProducts.find(p => p.node.id === product.node.id);
                    if (!exists) {
                        allProducts.push(product);
                    }
                });

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

                console.log(`  Found ${products.length} products (Total unique: ${allProducts.length})`);

                // 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 unique products fetched: ${allProducts.length}`);
    return allProducts;
}

function filterGlassBeadProducts(products) {
    console.log('\n🔍 Processing glass bead, sequin, and glitter products...');

    const glassBeadProducts = [];

    // Color families for sorting
    const colorFamilies = {
        'Gold': ['gold', 'golden', 'champagne', 'bronze'],
        'Silver': ['silver', 'metallic', 'chrome', 'platinum'],
        'White': ['white', 'ivory', 'cream', 'ecru', 'pearl', 'chalk'],
        'Black': ['black', 'onyx', 'charcoal', 'ebony', 'midnight'],
        'Pink': ['pink', 'blush', 'fuchsia', 'magenta', 'rose'],
        'Red': ['red', 'crimson', 'burgundy', 'wine', 'claret'],
        'Purple': ['purple', 'plum', 'aubergine', 'amethyst', 'violet', 'lavender'],
        'Blue': ['blue', 'navy', 'cobalt', 'azure', 'indigo', 'sapphire', 'aqua', 'teal', 'turquoise'],
        'Green': ['green', 'emerald', 'jade', 'olive', 'viridian'],
        'Yellow': ['yellow', 'amber', 'citrine'],
        'Orange': ['orange', 'coral', 'apricot', 'peach', 'copper'],
        'Brown': ['brown', 'chocolate', 'mocha', 'tan', 'camel'],
        'Gray': ['gray', 'grey', 'gunmetal', 'slate'],
        'Multi': ['multi', 'rainbow', 'iridescent', 'holographic']
    };

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

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

        const titleLower = node.title.toLowerCase();
        const tagsLower = node.tags.map(t => t.toLowerCase());
        const descLower = (node.description || '').toLowerCase();

        // Check if product matches our criteria
        const matchesGlassBead = titleLower.includes('glass bead') ||
                                  titleLower.includes('glass beaded') ||
                                  tagsLower.some(t => t.includes('glass bead'));

        const matchesSequin = titleLower.includes('sequin') ||
                             tagsLower.some(t => t.includes('sequin'));

        const matchesGlitter = titleLower.includes('glitter') ||
                              tagsLower.some(t => t.includes('glitter'));

        // Only include if it matches one of our criteria
        if (!matchesGlassBead && !matchesSequin && !matchesGlitter) {
            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();
        const searchString = `${titleLower} ${tagString}`;

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

        // Determine product category
        let category = 'Glass Beaded';
        if (matchesSequin) category = 'Sequin';
        if (matchesGlitter) category = 'Glitter';
        if (matchesGlassBead && matchesSequin) category = 'Glass Beaded & Sequin';

        glassBeadProducts.push({
            id: node.id,
            title: node.title,
            handle: node.handle,
            description: node.description,
            vendor: node.vendor,
            productType: node.productType,
            tags: node.tags,
            primaryColor: primaryColor,
            category: category,
            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,
            fireRated: true  // All products are Class A Fire Rated
        });

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

    console.log(`\n✅ Found ${glassBeadProducts.length} active glass bead/sequin/glitter products`);
    return glassBeadProducts;
}

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

    // Define color order
    const colorOrder = [
        'Gold', 'Silver', 'White', 'Black', 'Pink', 'Red',
        'Purple', 'Blue', 'Green', 'Yellow', 'Orange',
        'Brown', 'Gray', 'Multi', '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 = {};
    const categoryCounts = {};

    sorted.forEach(p => {
        colorCounts[p.primaryColor] = (colorCounts[p.primaryColor] || 0) + 1;
        categoryCounts[p.category] = (categoryCounts[p.category] || 0) + 1;
    });

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

    console.log('\n📊 Products per category:');
    Object.entries(categoryCounts).forEach(([category, count]) => {
        console.log(`  ${category}: ${count} products`);
    });

    return sorted;
}

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

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

        // Filter glass bead products
        const glassBeadProducts = filterGlassBeadProducts(allProducts);

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

        // Save to JSON file
        const outputPath = './glass-bead-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 Searched: ${allProducts.length}`);
        console.log(`  Glass Bead/Sequin/Glitter Products: ${glassBeadProducts.length}`);
        console.log(`  All products are Class "A" Fire Rated`);

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

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

main();