Files
orion/scripts/seed_demo.py

659 lines
21 KiB
Python

#!/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()