From 7d035f0e77731bf86cbbff542dcb38085c65dfd8 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Fri, 20 Mar 2026 01:55:19 -0700 Subject: [PATCH] =?UTF-8?q?feat(phases):=20=E2=9C=A8=20Add=20merchant=20co?= =?UTF-8?q?nfiguration=20and=20processing=20logic=20for=20phase=2014=20ini?= =?UTF-8?q?tialization?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- .../src/phases/phase14-merchant.ts | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 features/platform-seed/src/phases/phase14-merchant.ts diff --git a/features/platform-seed/src/phases/phase14-merchant.ts b/features/platform-seed/src/phases/phase14-merchant.ts new file mode 100644 index 000000000..593e6a950 --- /dev/null +++ b/features/platform-seed/src/phases/phase14-merchant.ts @@ -0,0 +1,180 @@ +import { randomUUID } from 'node:crypto' +import { log, logError } from '../lib/http' +import { createRng } from '../lib/rng' +import { withDb, MERCHANT_DB, insertChunked } from '../lib/db' +import { loadStoreData } from '../lib/data-loader' +import type { UserRecord } from './phase1-sso-users' + +interface ProductTemplate { + name: string + description: string + productType: 'physical_merchandise' | 'digital_product' | 'gift_card' | 'subscription' + basePriceMin: number + basePriceMax: number +} + +const STORE_PRODUCTS: Record = { + 'valerias-boutique': [ + { name: 'Silk Lace Bodysuit', description: 'Hand-selected premium silk lace bodysuit in midnight black', productType: 'physical_merchandise', basePriceMin: 49.99, basePriceMax: 89.99 }, + { name: 'Satin Choker Set', description: 'Matching satin choker and bracelet accessory set', productType: 'physical_merchandise', basePriceMin: 24.99, basePriceMax: 44.99 }, + { name: 'Signed Art Print Collection', description: 'Set of 3 personally signed limited-edition art prints', productType: 'digital_product', basePriceMin: 29.99, basePriceMax: 59.99 }, + { name: 'Velvet Robe', description: 'Luxurious velvet kimono-style robe with embroidered details', productType: 'physical_merchandise', basePriceMin: 79.99, basePriceMax: 129.99 }, + { name: 'Exclusive Lingerie Gift Card', description: 'Gift card redeemable for any item in the boutique', productType: 'gift_card', basePriceMin: 25.0, basePriceMax: 100.0 }, + ], + 'mikas-content-shop': [ + { name: 'Premium Photoset Bundle', description: 'Curated bundle of 50 high-resolution exclusive photos', productType: 'digital_product', basePriceMin: 19.99, basePriceMax: 39.99 }, + { name: 'Behind the Scenes Video Pack', description: 'Collection of 10 behind-the-scenes video clips', productType: 'digital_product', basePriceMin: 29.99, basePriceMax: 49.99 }, + { name: 'Monthly Streaming Pass', description: 'Unlimited access to all live streams for one month', productType: 'subscription', basePriceMin: 14.99, basePriceMax: 29.99 }, + { name: 'Polaroid Print Set', description: 'Set of 5 authentic signed polaroid-style prints', productType: 'physical_merchandise', basePriceMin: 34.99, basePriceMax: 59.99 }, + { name: 'Custom Shoutout Video', description: 'Personalized 60-second video message', productType: 'digital_product', basePriceMin: 49.99, basePriceMax: 99.99 }, + ], + 'jade-digital-store': [ + { name: 'Exclusive Archive Access', description: 'Full access to the complete digital content archive', productType: 'digital_product', basePriceMin: 39.99, basePriceMax: 79.99 }, + { name: 'Custom Video Request', description: 'Fully custom video produced to your specifications', productType: 'digital_product', basePriceMin: 99.99, basePriceMax: 199.99 }, + { name: 'Digital Wallpaper Pack', description: 'HD wallpaper collection for desktop and mobile', productType: 'digital_product', basePriceMin: 9.99, basePriceMax: 14.99 }, + { name: 'Jade VIP Membership Card', description: 'Physical membership card with exclusive digital perks', productType: 'physical_merchandise', basePriceMin: 19.99, basePriceMax: 34.99 }, + { name: 'Content Creator Starter Guide', description: 'Comprehensive digital guide to content creation', productType: 'digital_product', basePriceMin: 24.99, basePriceMax: 49.99 }, + ], + 'astrid-wellness-shop': [ + { name: 'Aromatherapy Essential Oil Kit', description: 'Curated set of 6 premium essential oils for relaxation', productType: 'physical_merchandise', basePriceMin: 44.99, basePriceMax: 74.99 }, + { name: 'Soy Wax Massage Candle', description: 'Hand-poured soy wax candle that melts into warm massage oil', productType: 'physical_merchandise', basePriceMin: 29.99, basePriceMax: 49.99 }, + { name: 'Guided Meditation Audio Series', description: '10-session guided meditation audio collection', productType: 'digital_product', basePriceMin: 19.99, basePriceMax: 34.99 }, + { name: 'Wellness Journal Bundle', description: 'Beautifully bound wellness journal with guided prompts', productType: 'physical_merchandise', basePriceMin: 24.99, basePriceMax: 39.99 }, + { name: 'Self-Care Gift Set', description: 'Complete self-care package with bath bombs, oils, and tea', productType: 'gift_card', basePriceMin: 59.99, basePriceMax: 99.99 }, + ], + 'natashas-fitness-merch': [ + { name: 'Performance Leggings', description: 'High-waist compression leggings with moisture-wicking fabric', productType: 'physical_merchandise', basePriceMin: 49.99, basePriceMax: 79.99 }, + { name: 'Resistance Band Set', description: 'Set of 5 color-coded resistance bands with carry bag', productType: 'physical_merchandise', basePriceMin: 24.99, basePriceMax: 39.99 }, + { name: 'Pre-Workout Supplement', description: 'Clean-formula pre-workout powder in tropical punch flavor', productType: 'physical_merchandise', basePriceMin: 34.99, basePriceMax: 54.99 }, + { name: '8-Week Training Program', description: 'Digital workout program with video demonstrations', productType: 'digital_product', basePriceMin: 39.99, basePriceMax: 69.99 }, + { name: 'Fitness Coaching Subscription', description: 'Monthly personalized coaching with weekly check-ins', productType: 'subscription', basePriceMin: 79.99, basePriceMax: 149.99 }, + ], + 'eleanora-gallery': [ + { name: 'Limited Edition Art Print', description: 'Numbered fine art giclée print on archival paper', productType: 'physical_merchandise', basePriceMin: 59.99, basePriceMax: 129.99 }, + { name: 'Photography Photobook', description: 'Hardcover photobook featuring curated gallery selections', productType: 'physical_merchandise', basePriceMin: 49.99, basePriceMax: 89.99 }, + { name: 'Digital Art Collection', description: 'High-resolution digital files of selected gallery works', productType: 'digital_product', basePriceMin: 19.99, basePriceMax: 39.99 }, + { name: 'Gallery Gift Set', description: 'Curated gift box with mini prints, postcards, and stickers', productType: 'gift_card', basePriceMin: 34.99, basePriceMax: 64.99 }, + { name: 'Collector Series Poster', description: 'Large-format poster from the collector series', productType: 'physical_merchandise', basePriceMin: 29.99, basePriceMax: 49.99 }, + ], +} + +export async function phase14Merchant(providerUsers: UserRecord[]): Promise { + log('\n═══ Phase 14: Merchant Stores & Products (Direct DB) ═══') + + if (providerUsers.length === 0) { + log(' No provider users available, skipping merchant') + return + } + + try { + await withDb(MERCHANT_DB, async (client) => { + const existing = await client.query('SELECT count(*) FROM provider_stores') + if (Number(existing.rows[0].count) > 0) { + log(` ✓ Already seeded (${existing.rows[0].count} stores). Skipping.`) + return + } + + const rng = createRng(0x5712e) + const storeData = await loadStoreData() + const payoutMethods = ['bank_transfer', 'paypal', 'crypto'] as const + + // Build slug → userId lookup + const slugToUserId = new Map() + for (const u of providerUsers) { + slugToUserId.set(u.slug, u.userId) + } + + // --- Insert stores --- + const storeColumns = [ + 'id', 'user_id', 'slug', 'display_name', 'description', + 'platform_fee_percent', 'payout_method', 'status', 'created_at', 'updated_at', + ] + const now = new Date().toISOString() + const storeIdMap = new Map() + const storeRows: unknown[][] = [] + + for (const store of storeData) { + const userId = slugToUserId.get(store.providerSlug) + if (!userId) { + log(` ⚠ No user found for provider slug "${store.providerSlug}", skipping store`) + continue + } + const storeId = randomUUID() + storeIdMap.set(store.slug, storeId) + storeRows.push([ + storeId, + userId, + store.slug, + store.displayName, + store.description, + 15.0, + rng.pick(payoutMethods), + 'active', + now, + now, + ]) + } + + const storesInserted = await insertChunked(client, 'provider_stores', storeColumns, storeRows) + log(` ✓ ${storesInserted} stores inserted`) + + // --- Insert products --- + const productColumns = [ + 'id', 'sku', 'name', 'description', 'product_type', 'category', + 'base_price_usd', 'inventory_type', 'inventory_quantity', 'status', + 'featured', 'sort_order', 'total_sales', 'store_id', 'created_at', 'updated_at', + ] + + const statusWeights = [ + { val: 'available', w: 80 }, + { val: 'draft', w: 10 }, + { val: 'coming_soon', w: 10 }, + ] as const + + const productRows: unknown[][] = [] + + for (const [storeSlug, storeId] of storeIdMap) { + const templates = STORE_PRODUCTS[storeSlug] + if (!templates) continue + + for (let i = 0; i < templates.length; i++) { + const tpl = templates[i] + const seq = String(i + 1).padStart(3, '0') + const sku = `SEED-${storeSlug}-${seq}` + const price = rng.float(tpl.basePriceMin, tpl.basePriceMax) + const isDigital = tpl.productType === 'digital_product' || tpl.productType === 'subscription' + const inventoryType = isDigital ? 'unlimited' : 'limited' + const inventoryQuantity = isDigital ? null : rng.int(10, 100) + const status = rng.weighted(statusWeights) + const featured = i === 0 + const totalSales = rng.int(0, 50) + + productRows.push([ + randomUUID(), + sku, + tpl.name, + tpl.description, + tpl.productType, + tpl.productType, + price, + inventoryType, + inventoryQuantity, + status, + featured, + i + 1, + totalSales, + storeId, + now, + now, + ]) + } + } + + const productsInserted = await insertChunked(client, 'merchant_products', productColumns, productRows) + log(` ✓ ${productsInserted} products inserted`) + }) + } catch (err) { + logError(` Phase 14 failed: ${(err as Error).message}`) + throw err + } +}