exercises

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

generate_products_enhanced.js (10706B)


      1 #!/usr/bin/env node
      2 
      3 /**
      4  * Generate 2000 enhanced product records with categories and specifications
      5  * Outputs SQL INSERT statements to schemas/data/products_data_enhanced.sql
      6  */
      7 
      8 const fs = require('fs');
      9 const path = require('path');
     10 
     11 // Categories with their IDs (matching the migration seed data)
     12 const categories = [
     13     { id: 1, name: 'Electronics', slug: 'electronics' },
     14     { id: 2, name: 'Accessories', slug: 'accessories' },
     15     { id: 3, name: 'Gaming', slug: 'gaming' },
     16     { id: 4, name: 'Office', slug: 'office' },
     17     { id: 5, name: 'Audio', slug: 'audio' },
     18     { id: 6, name: 'Computers', slug: 'computers' },
     19     { id: 7, name: 'Mobile', slug: 'mobile' },
     20     { id: 8, name: 'Wearables', slug: 'wearables' },
     21     { id: 9, name: 'Smart Home', slug: 'smart-home' },
     22     { id: 10, name: 'Camera', slug: 'camera' },
     23     { id: 11, name: 'Networking', slug: 'networking' },
     24     { id: 12, name: 'Storage', slug: 'storage' }
     25 ];
     26 
     27 // Product types per category
     28 const productTypes = {
     29     'Electronics': ['Monitor', 'Laptop', 'Desktop', 'Tablet', 'E-Reader', 'TV', 'Projector'],
     30     'Accessories': ['USB-C Cable', 'HDMI Cable', 'Power Adapter', 'Phone Case', 'Screen Protector'],
     31     'Gaming': ['Gaming Mouse', 'Gaming Keyboard', 'Gaming Headset', 'Controller', 'Gaming Chair', 'Mouse Pad'],
     32     'Office': ['Desk Lamp', 'Office Chair', 'Standing Desk', 'Whiteboard', 'Desk Organizer'],
     33     'Audio': ['Bluetooth Speaker', 'Headphones', 'Earbuds', 'Microphone', 'Sound Bar'],
     34     'Computers': ['Keyboard', 'Mouse', 'Webcam', 'External Drive', 'RAM Module', 'SSD'],
     35     'Mobile': ['Smartphone', 'Phone Charger', 'Wireless Charger', 'Car Mount', 'Battery Pack'],
     36     'Wearables': ['Smart Watch', 'Fitness Tracker', 'Smart Ring', 'Smart Glasses'],
     37     'Smart Home': ['Smart Bulb', 'Smart Plug', 'Security Camera', 'Video Doorbell', 'Smart Lock'],
     38     'Camera': ['DSLR Camera', 'Mirrorless Camera', 'Action Camera', 'Camera Lens', 'Tripod'],
     39     'Networking': ['Router', 'WiFi Extender', 'Network Switch', 'Modem', 'Ethernet Cable'],
     40     'Storage': ['External SSD', 'External HDD', 'NAS Drive', 'USB Flash Drive', 'SD Card Reader']
     41 };
     42 
     43 const adjectives = ['Pro', 'Plus', 'Max', 'Ultra', 'Premium', 'Elite', 'Advanced', 'Deluxe',
     44                     'HD', '4K', 'Wireless', 'Portable', 'Compact', 'Mini', 'XL', 'RGB'];
     45 
     46 const brands = ['TechPro', 'InnovateTech', 'SmartGear', 'ProElectronics', 'EliteTech',
     47                 'DigitalWave', 'FutureTech', 'PrimeTech', 'CoreTech', 'NexGen'];
     48 
     49 const colors = [['Black'], ['White'], ['Silver'], ['Black', 'White'], ['Blue', 'Red'],
     50                 ['Gray', 'Black', 'White'], null];
     51 
     52 const sizes = [['S', 'M', 'L'], ['XS', 'S', 'M', 'L', 'XL'], ['One Size'], null];
     53 
     54 // Specifications templates per category
     55 const specTemplates = {
     56     'Electronics': [
     57         ['Display Size', ['24"', '27"', '32"', '43"']],
     58         ['Resolution', ['1080p', '4K', '2K', '8K']],
     59         ['Refresh Rate', ['60Hz', '120Hz', '144Hz']],
     60         ['Warranty', ['1 Year', '2 Years', '3 Years']]
     61     ],
     62     'Gaming': [
     63         ['DPI', ['800-3200', '1000-4000', '400-6400']],
     64         ['Connection', ['Wireless', 'Wired', 'Both']],
     65         ['RGB Lighting', ['Yes', 'No']],
     66         ['Warranty', ['1 Year', '2 Years']]
     67     ],
     68     'Audio': [
     69         ['Driver Size', ['40mm', '50mm', '60mm']],
     70         ['Frequency Response', ['20Hz-20kHz', '10Hz-40kHz']],
     71         ['Impedance', ['32 Ohms', '80 Ohms', '250 Ohms']],
     72         ['Warranty', ['1 Year', '2 Years']]
     73     ],
     74     'Computers': [
     75         ['Interface', ['USB 3.0', 'USB-C', 'Thunderbolt']],
     76         ['Capacity', ['256GB', '512GB', '1TB', '2TB']],
     77         ['Speed', ['3000MB/s', '5000MB/s', '7000MB/s']],
     78         ['Warranty', ['3 Years', '5 Years']]
     79     ]
     80 };
     81 
     82 function randomInt(min, max) {
     83     return Math.floor(Math.random() * (max - min + 1)) + min;
     84 }
     85 
     86 function randomChoice(array) {
     87     return array[Math.floor(Math.random() * array.length)];
     88 }
     89 
     90 function generateASIN() {
     91     const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
     92     return Array.from({length: 10}, () => chars[randomInt(0, chars.length - 1)]).join('');
     93 }
     94 
     95 function generateSKU(category, index) {
     96     return `${category.slug.toUpperCase().replace('-', '')}-${index.toString().padStart(6, '0')}`;
     97 }
     98 
     99 function generatePrice() {
    100     const prices = [9.99, 12.99, 14.99, 19.99, 24.99, 29.99, 34.99, 39.99, 49.99, 59.99,
    101                     69.99, 79.99, 89.99, 99.99, 119.99, 149.99, 199.99, 249.99, 299.99,
    102                     349.99, 399.99, 449.99, 499.99, 599.99, 699.99, 799.99, 899.99,
    103                     999.99, 1299.99, 1499.99, 1999.99, 2499.99];
    104     return randomChoice(prices);
    105 }
    106 
    107 function generateDescription(productName, category) {
    108     const templates = [
    109         `High-quality ${productName.toLowerCase()} designed for ${category.name.toLowerCase()} enthusiasts.`,
    110         `Premium ${productName.toLowerCase()} with advanced features and superior performance.`,
    111         `Professional-grade ${productName.toLowerCase()} perfect for work and play.`,
    112         `Innovative ${productName.toLowerCase()} combining style and functionality.`,
    113         `Top-rated ${productName.toLowerCase()} with excellent build quality.`
    114     ];
    115     return randomChoice(templates);
    116 }
    117 
    118 function generateProduct(index) {
    119     const category = randomChoice(categories);
    120     const productType = randomChoice(productTypes[category.name]);
    121     const adjective = Math.random() > 0.5 ? randomChoice(adjectives) + ' ' : '';
    122     const variant = randomInt(1000, 9999);
    123 
    124     const productName = `${adjective}${productType} ${variant}`;
    125     const brand = randomChoice(brands);
    126     const description = generateDescription(productType, category);
    127     const price = generatePrice();
    128     const stock = randomInt(0, 500);
    129     const asin = generateASIN();
    130     const sku = generateSKU(category, index);
    131 
    132     return {
    133         asin,
    134         sku,
    135         product_name: productName,
    136         brand,
    137         description,
    138         category_id: category.id,
    139         category_name: category.name,
    140         price,
    141         stock_quantity: stock,
    142         sizes: randomChoice(sizes),
    143         colors: randomChoice(colors),
    144         available_for_delivery: Math.random() > 0.1,
    145         available_for_pickup: Math.random() > 0.7,
    146         free_returns: Math.random() > 0.5
    147     };
    148 }
    149 
    150 function generateSpecs(product, productIndex) {
    151     const category = categories.find(c => c.id === product.category_id);
    152     const templates = specTemplates[category.name] || [];
    153 
    154     if (templates.length === 0) return [];
    155 
    156     const specs = [];
    157     const numSpecs = randomInt(2, Math.min(4, templates.length));
    158 
    159     const selectedTemplates = [];
    160     while (selectedTemplates.length < numSpecs && selectedTemplates.length < templates.length) {
    161         const template = randomChoice(templates);
    162         if (!selectedTemplates.includes(template)) {
    163             selectedTemplates.push(template);
    164         }
    165     }
    166 
    167     selectedTemplates.forEach(([name, values]) => {
    168         specs.push({
    169             product_index: productIndex + 1,
    170             spec_name: name,
    171             spec_value: randomChoice(values)
    172         });
    173     });
    174 
    175     return specs;
    176 }
    177 
    178 function escapeSql(str) {
    179     if (!str) return str;
    180     return str.replace(/'/g, "''");
    181 }
    182 
    183 function arrayToPostgres(arr) {
    184     if (!arr) return 'NULL';
    185     return `ARRAY[${arr.map(item => `'${escapeSql(item)}'`).join(', ')}]`;
    186 }
    187 
    188 function generateSQL(products, specifications) {
    189     const lines = [
    190         '-- Generated enhanced products data',
    191         '-- Total products: ' + products.length,
    192         '-- Total specifications: ' + specifications.length,
    193         '-- Generated at: ' + new Date().toISOString(),
    194         '',
    195         'SET search_path TO products, public;',
    196         '',
    197         '-- Insert products',
    198         'INSERT INTO products (asin, sku, product_name, brand, description, category_id, price, stock_quantity, sizes, colors, available_for_delivery, available_for_pickup, free_returns) VALUES'
    199     ];
    200 
    201     const productLines = products.map((product, index) => {
    202         const isLast = index === products.length - 1;
    203         const name = escapeSql(product.product_name);
    204         const desc = escapeSql(product.description);
    205         const brand = escapeSql(product.brand);
    206         const asin = escapeSql(product.asin);
    207         const sku = escapeSql(product.sku);
    208 
    209         return `    ('${asin}', '${sku}', '${name}', '${brand}', '${desc}', ${product.category_id}, ${product.price}, ${product.stock_quantity}, ${arrayToPostgres(product.sizes)}, ${arrayToPostgres(product.colors)}, ${product.available_for_delivery}, ${product.available_for_pickup}, ${product.free_returns})${isLast ? ';' : ','}`;
    210     });
    211 
    212     lines.push(...productLines);
    213     lines.push('');
    214     lines.push('-- Insert product specifications');
    215     lines.push('INSERT INTO product_specifications (product_id, spec_name, spec_value) VALUES');
    216 
    217     const specLines = specifications.map((spec, index) => {
    218         const isLast = index === specifications.length - 1;
    219         const name = escapeSql(spec.spec_name);
    220         const value = escapeSql(spec.spec_value);
    221         return `    (${spec.product_index}, '${name}', '${value}')${isLast ? ';' : ','}`;
    222     });
    223 
    224     lines.push(...specLines);
    225 
    226     return lines.join('\n');
    227 }
    228 
    229 function main() {
    230     console.log('Generating 2000 enhanced products...');
    231 
    232     const products = [];
    233     const specifications = [];
    234 
    235     for (let i = 0; i < 2000; i++) {
    236         const product = generateProduct(i);
    237         products.push(product);
    238 
    239         // Generate 2-4 specifications for each product
    240         const specs = generateSpecs(product, i);
    241         specifications.push(...specs);
    242 
    243         if ((i + 1) % 500 === 0) {
    244             console.log(`Generated ${i + 1} products with ${specifications.length} specs...`);
    245         }
    246     }
    247 
    248     console.log('Generating SQL...');
    249     const sql = generateSQL(products, specifications);
    250 
    251     const outputPath = path.join(__dirname, 'data', 'products_data_enhanced.sql');
    252     fs.writeFileSync(outputPath, sql, 'utf8');
    253 
    254     console.log(`✓ Successfully generated ${products.length} products`);
    255     console.log(`✓ Successfully generated ${specifications.length} specifications`);
    256     console.log(`✓ SQL file saved to: ${outputPath}`);
    257     console.log(`✓ File size: ${(sql.length / 1024).toFixed(2)} KB`);
    258 
    259     // Generate category distribution
    260     const distribution = {};
    261     products.forEach(p => {
    262         const cat = categories.find(c => c.id === p.category_id).name;
    263         distribution[cat] = (distribution[cat] || 0) + 1;
    264     });
    265     console.log('\nCategory distribution:');
    266     Object.entries(distribution).sort((a, b) => b[1] - a[1]).forEach(([cat, count]) => {
    267         console.log(`  ${cat}: ${count} products`);
    268     });
    269 }
    270 
    271 main();