#!/usr/bin/env python3 """ Demo Database Seeder for Wizamart Platform Creates DEMO/TEST data for development and testing: - Demo vendors with realistic data - Test customers and addresses - Sample products - Demo orders - Vendor themes and custom domains - Test import jobs ⚠️ WARNING: This script creates FAKE DATA for development only! ⚠️ NEVER run this in production! Prerequisites: - Database migrations must be applied (make migrate-up) - Production initialization must be run (make init-prod) Usage: make seed-demo # Normal demo seeding make seed-demo-minimal # Minimal seeding (1 vendor only) make seed-demo-reset # Delete all data and reseed (DANGEROUS!) This script is idempotent when run normally. """ import sys from pathlib import Path from typing import Dict, List from datetime import datetime, timezone, timedelta from decimal import Decimal # Add project root to path project_root = Path(__file__).parent.parent sys.path.insert(0, str(project_root)) from sqlalchemy.orm import Session from sqlalchemy import select, delete from app.core.database import SessionLocal from app.core.config import settings from app.core.environment import is_production, get_environment from models.database.user import User from models.database.vendor import Vendor, VendorUser, Role from models.database.vendor_domain import VendorDomain from models.database.vendor_theme import VendorTheme from models.database.customer import Customer, CustomerAddress from models.database.product import Product from models.database.marketplace_product import MarketplaceProduct from models.database.marketplace_import_job import MarketplaceImportJob from models.database.order import Order, OrderItem from models.database.admin import PlatformAlert from middleware.auth import AuthManager # ============================================================================= # MODE DETECTION (from environment variable set by Makefile) # ============================================================================= import os SEED_MODE = os.getenv('SEED_MODE', 'normal') # normal, minimal, reset # ============================================================================= # DEMO DATA CONFIGURATION # ============================================================================= # Demo vendor configurations DEMO_VENDORS = [ { "vendor_code": "WIZAMART", "name": "WizaMart", "subdomain": "wizamart", "description": "Premium electronics and gadgets marketplace", "theme_preset": "modern", "custom_domain": "wizamart.shop", }, { "vendor_code": "FASHIONHUB", "name": "Fashion Hub", "subdomain": "fashionhub", "description": "Trendy clothing and accessories", "theme_preset": "vibrant", "custom_domain": "fashionhub.store", }, { "vendor_code": "BOOKSTORE", "name": "The Book Store", "subdomain": "bookstore", "description": "Books, magazines, and educational materials", "theme_preset": "classic", "custom_domain": None, }, ] # Demo users (vendor owners) DEMO_VENDOR_USERS = [ { "username": "vendor1", "email": "vendor1@example.com", "password": "password123", "first_name": "John", "last_name": "Vendor", }, { "username": "vendor2", "email": "vendor2@example.com", "password": "password123", "first_name": "Jane", "last_name": "Merchant", }, { "username": "vendor3", "email": "vendor3@example.com", "password": "password123", "first_name": "Bob", "last_name": "Seller", }, ] # Theme presets THEME_PRESETS = { "modern": { "primary": "#3b82f6", "secondary": "#06b6d4", "accent": "#f59e0b", "background": "#f9fafb", "text": "#111827", }, "classic": { "primary": "#1e40af", "secondary": "#7c3aed", "accent": "#dc2626", "background": "#ffffff", "text": "#374151", }, "vibrant": { "primary": "#ec4899", "secondary": "#f59e0b", "accent": "#8b5cf6", "background": "#fef3c7", "text": "#78350f", }, } # ============================================================================= # HELPER FUNCTIONS # ============================================================================= def print_header(text: str): """Print formatted header.""" print("\n" + "=" * 70) print(f" {text}") print("=" * 70) def print_step(step: int, text: str): """Print step indicator.""" print(f"\n[{step}] {text}") def print_success(text: str): """Print success message.""" print(f" ✓ {text}") def print_warning(text: str): """Print warning message.""" print(f" ⚠ {text}") def print_error(text: str): """Print error message.""" print(f" ✗ {text}") # ============================================================================= # SAFETY CHECKS # ============================================================================= def check_environment(): """Prevent running demo seed in production.""" if is_production(): print_error("Cannot run demo seeding in production!") print(" This script creates FAKE DATA for development only.") print(f" Current environment: {get_environment()}") print("\n To seed in production:") print(" 1. Set ENVIRONMENT=development in .env (or ENV=development)") print(" 2. Run: make seed-demo") sys.exit(1) print_success(f"Environment check passed: {get_environment()}") def check_admin_exists(db: Session) -> bool: """Check if admin user exists.""" admin = db.execute( select(User).where(User.role == "admin") ).scalar_one_or_none() if not admin: print_error("No admin user found!") print(" Run production initialization first:") print(" make init-prod") return False print_success(f"Admin user exists: {admin.email}") return True # ============================================================================= # DATA DELETION (for reset mode) # ============================================================================= def reset_all_data(db: Session): """Delete ALL data from database (except admin user).""" print_warning("RESETTING ALL DATA...") print(" This will delete all vendors, customers, orders, etc.") print(" Admin user will be preserved.") # Get confirmation response = input("\n Type 'DELETE ALL DATA' to confirm: ") if response != "DELETE ALL DATA": print(" Reset cancelled.") sys.exit(0) # Delete in correct order (respecting foreign keys) tables_to_clear = [ OrderItem, Order, CustomerAddress, Customer, MarketplaceImportJob, MarketplaceProduct, Product, VendorDomain, VendorTheme, Role, VendorUser, Vendor, PlatformAlert, ] for table in tables_to_clear: db.execute(delete(table)) # Delete non-admin users db.execute(delete(User).where(User.role != "admin")) db.commit() print_success("All data deleted (admin preserved)") # ============================================================================= # SEEDING FUNCTIONS # ============================================================================= def create_demo_vendors(db: Session, auth_manager: AuthManager) -> List[Vendor]: """Create demo vendors with users.""" vendors = [] # Determine how many vendors to create based on mode vendor_count = 1 if SEED_MODE == 'minimal' else settings.seed_demo_vendors vendors_to_create = DEMO_VENDORS[:vendor_count] users_to_create = DEMO_VENDOR_USERS[:vendor_count] for vendor_data, user_data in zip(vendors_to_create, users_to_create): # Check if vendor already exists existing = db.execute( select(Vendor).where(Vendor.vendor_code == vendor_data["vendor_code"]) ).scalar_one_or_none() if existing: print_warning(f"Vendor already exists: {vendor_data['name']}") vendors.append(existing) continue # Create vendor user vendor_user = User( username=user_data["username"], email=user_data["email"], hashed_password=auth_manager.hash_password(user_data["password"]), role="vendor", first_name=user_data["first_name"], last_name=user_data["last_name"], is_active=True, is_email_verified=True, created_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), ) db.add(vendor_user) db.flush() # Create vendor vendor = Vendor( vendor_code=vendor_data["vendor_code"], name=vendor_data["name"], subdomain=vendor_data["subdomain"], description=vendor_data["description"], is_active=True, is_verified=True, created_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), ) db.add(vendor) db.flush() # Link user to vendor as owner vendor_user_link = VendorUser( vendor_id=vendor.id, user_id=vendor_user.id, user_type="owner", is_active=True, created_at=datetime.now(timezone.utc), ) db.add(vendor_user_link) # Create vendor theme theme_colors = THEME_PRESETS.get(vendor_data["theme_preset"], THEME_PRESETS["modern"]) theme = VendorTheme( vendor_id=vendor.id, theme_name=vendor_data["theme_preset"], primary_color=theme_colors["primary"], secondary_color=theme_colors["secondary"], accent_color=theme_colors["accent"], background_color=theme_colors["background"], text_color=theme_colors["text"], is_active=True, created_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), ) db.add(theme) # Create custom domain if specified if vendor_data.get("custom_domain"): domain = VendorDomain( vendor_id=vendor.id, domain_name=vendor_data["custom_domain"], is_verified=True, # Auto-verified for demo is_active=True, verification_token=None, created_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), ) db.add(domain) vendors.append(vendor) print_success(f"Created vendor: {vendor.name} ({vendor.vendor_code})") db.flush() return vendors def create_demo_customers(db: Session, vendor: Vendor, auth_manager: AuthManager, count: int) -> List[Customer]: """Create demo customers for a vendor.""" customers = [] # Use a simple demo password for all customers demo_password = "customer123" for i in range(1, count + 1): email = f"customer{i}@{vendor.subdomain}.example.com" customer_number = f"CUST-{vendor.vendor_code}-{i:04d}" # Check if customer already exists existing_customer = db.query(Customer).filter( Customer.vendor_id == vendor.id, Customer.email == email ).first() if existing_customer: customers.append(existing_customer) continue # Skip creation, customer already exists customer = Customer( vendor_id=vendor.id, email=email, hashed_password=auth_manager.hash_password(demo_password), first_name=f"Customer{i}", last_name=f"Test", phone=f"+352123456{i:03d}", customer_number=customer_number, is_active=True, created_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), ) db.add(customer) customers.append(customer) db.flush() new_count = len([c for c in customers if c.id is None or db.is_modified(c)]) if new_count > 0: print_success(f"Created {new_count} customers for {vendor.name}") else: print_warning(f"Customers already exist for {vendor.name}") return customers def create_demo_products(db: Session, vendor: Vendor, count: int) -> List[Product]: """Create demo products for a vendor.""" products = [] for i in range(1, count + 1): marketplace_product_id = f"{vendor.vendor_code}-MP-{i:04d}" product_id = f"{vendor.vendor_code}-PROD-{i:03d}" # Check if this product already exists existing_product = db.query(Product).filter( Product.vendor_id == vendor.id, Product.product_id == product_id ).first() if existing_product: products.append(existing_product) continue # Skip creation, product already exists # Check if marketplace product already exists existing_mp = db.query(MarketplaceProduct).filter( MarketplaceProduct.marketplace_product_id == marketplace_product_id ).first() if existing_mp: marketplace_product = existing_mp else: # Create the MarketplaceProduct (base product data) marketplace_product = MarketplaceProduct( marketplace_product_id=marketplace_product_id, title=f"Sample Product {i} - {vendor.name}", description=f"This is a demo product for testing purposes in {vendor.name}. High quality and affordable.", link=f"https://{vendor.subdomain}.example.com/products/sample-{i}", image_link=f"https://{vendor.subdomain}.example.com/images/product-{i}.jpg", price=str(Decimal(f"{(i * 10) % 500 + 9.99}")), # Store as string brand=vendor.name, gtin=f"TEST{vendor.id:02d}{i:010d}", availability="in stock", condition="new", google_product_category="Electronics > Computers > Laptops", marketplace="Wizamart", vendor_name=vendor.name, currency="EUR", created_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), ) db.add(marketplace_product) db.flush() # Flush to get the marketplace_product.id # Create the Product (vendor-specific entry) product = Product( vendor_id=vendor.id, marketplace_product_id=marketplace_product.id, product_id=product_id, price=float(Decimal(f"{(i * 10) % 500 + 9.99}")), # Store as float availability="in stock", condition="new", currency="EUR", is_active=True, is_featured=(i % 5 == 0), # Every 5th product is featured display_order=i, min_quantity=1, created_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), ) db.add(product) products.append(product) db.flush() new_count = len([p for p in products if p.id is None or db.is_modified(p)]) if new_count > 0: print_success(f"Created {new_count} products for {vendor.name}") else: print_warning(f"Products already exist for {vendor.name}") return products # ============================================================================= # MAIN SEEDING # ============================================================================= def seed_demo_data(db: Session, auth_manager: AuthManager): """Seed demo data for development.""" print_header("DEMO DATA SEEDING") print(f" Mode: {SEED_MODE.upper()}") # Step 1: Check environment print_step(1, "Checking environment...") check_environment() # Step 2: Check admin exists print_step(2, "Verifying admin user...") if not check_admin_exists(db): sys.exit(1) # Step 3: Reset data if in reset mode if SEED_MODE == 'reset': print_step(3, "Resetting data...") reset_all_data(db) # Step 4: Create vendors print_step(4, "Creating demo vendors...") vendors = create_demo_vendors(db, auth_manager) # Step 5: Create customers print_step(5, "Creating demo customers...") for vendor in vendors: create_demo_customers( db, vendor, auth_manager, count=settings.seed_customers_per_vendor ) # Step 6: Create products print_step(6, "Creating demo products...") for vendor in vendors: create_demo_products( db, vendor, count=settings.seed_products_per_vendor ) # Commit all changes db.commit() print_success("All demo data committed") def print_summary(db: Session): """Print seeding summary.""" print_header("SEEDING SUMMARY") # Count records vendor_count = db.query(Vendor).count() user_count = db.query(User).count() customer_count = db.query(Customer).count() product_count = db.query(Product).count() print(f"\n📊 Database Status:") print(f" Vendors: {vendor_count}") print(f" Users: {user_count}") print(f" Customers: {customer_count}") print(f" Products: {product_count}") # Show vendor details vendors = db.query(Vendor).all() print(f"\n🏪 Demo Vendors:") for vendor in vendors: print(f"\n {vendor.name} ({vendor.vendor_code})") print(f" Subdomain: {vendor.subdomain}.{settings.platform_domain}") # Query custom domains separately custom_domain = db.query(VendorDomain).filter( VendorDomain.vendor_id == vendor.id, VendorDomain.is_active == True ).first() if custom_domain: # Try different possible field names (model field might vary) domain_value = ( getattr(custom_domain, 'domain', None) or getattr(custom_domain, 'domain_name', None) or getattr(custom_domain, 'name', None) ) if domain_value: print(f" Custom: {domain_value}") print(f" Status: {'✓ Active' if vendor.is_active else '✗ Inactive'}") print(f"\n🔐 Demo Vendor Credentials:") print("─" * 70) for i, vendor_data in enumerate(DEMO_VENDOR_USERS[:vendor_count], 1): vendor = vendors[i - 1] if i <= len(vendors) else None print(f" Vendor {i}:") print(f" Username: {vendor_data['username']}") print(f" Email: {vendor_data['email']}") print(f" Password: {vendor_data['password']}") if vendor: print(f" Login: http://localhost:8000/vendor/{vendor.vendor_code}/login") print(f" or http://{vendor.subdomain}.localhost:8000/vendor/login") print() print(f"\n🛒 Demo Customer Credentials:") print("─" * 70) print(f" All customers:") print(f" Email: customer1@{{subdomain}}.example.com") print(f" Password: customer123") print(f" (Replace {{subdomain}} with vendor subdomain, e.g., wizamart)") print() print(f"\n🏪 Shop Access (Development):") print("─" * 70) for vendor in vendors: print(f" {vendor.name}:") print(f" Path-based: http://localhost:8000/vendors/{vendor.vendor_code}/shop/") print(f" Subdomain: http://{vendor.subdomain}.localhost:8000/") print() print("⚠️ ALL DEMO CREDENTIALS ARE INSECURE - For development only!") print("\n🚀 NEXT STEPS:") print(" 1. Start development: make dev") print(" 2. Login as vendor:") print(" • Path-based: http://localhost:8000/vendor/WIZAMART/login") print(" • Subdomain: http://wizamart.localhost:8000/vendor/login") print(f" 3. Visit vendor shop: http://localhost:8000/vendors/WIZAMART/shop/") print(f" 4. Admin panel: http://localhost:8000/admin/login") print(f" Username: {settings.admin_username}") print(f" Password: {settings.admin_password}") # ============================================================================= # MAIN ENTRY POINT # ============================================================================= def main(): """Main entry point.""" print("\n" + "╔" + "═" * 68 + "╗") print("║" + " " * 20 + "DEMO DATA SEEDING" + " " * 31 + "║") print("╚" + "═" * 68 + "╝") db = SessionLocal() auth_manager = AuthManager() try: seed_demo_data(db, auth_manager) print_summary(db) print_header("✅ DEMO SEEDING COMPLETED") except KeyboardInterrupt: db.rollback() print("\n\n⚠️ Seeding interrupted") sys.exit(1) except Exception as e: db.rollback() print_header("❌ SEEDING FAILED") print(f"\nError: {e}\n") import traceback traceback.print_exc() sys.exit(1) finally: db.close() if __name__ == "__main__": main()