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();