data seed feature for demo and prod

This commit is contained in:
2025-11-15 20:57:39 +01:00
parent 41439eed09
commit e3ed4a3295
17 changed files with 4574 additions and 1793 deletions

View File

@@ -1,167 +0,0 @@
#!/usr/bin/env python3
"""
Create default admin user for the platform.
Usage:
python scripts/create_admin.py
This script:
- Creates a default admin user if one doesn't exist
- Can be run multiple times safely (idempotent)
- Should be run AFTER database migrations
"""
import sys
from pathlib import Path
# 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
from app.core.database import SessionLocal, engine
from models.database.user import User
from middleware.auth import AuthManager
# Default admin credentials
DEFAULT_ADMIN_EMAIL = "admin@platform.com"
DEFAULT_ADMIN_USERNAME = "admin"
DEFAULT_ADMIN_PASSWORD = "admin123" # Change this in production!
def create_admin_user(db: Session) -> bool:
"""
Create default admin user if it doesn't exist.
Args:
db: Database session
Returns:
bool: True if user was created, False if already exists
"""
auth_manager = AuthManager()
# Check if admin user already exists
existing_admin = db.execute(
select(User).where(User.username == DEFAULT_ADMIN_USERNAME)
).scalar_one_or_none()
if existing_admin:
print(f" Admin user '{DEFAULT_ADMIN_USERNAME}' already exists")
print(f" Email: {existing_admin.email}")
print(f" Role: {existing_admin.role}")
print(f" Active: {existing_admin.is_active}")
return False
# Create new admin user
print(f"📝 Creating admin user...")
admin_user = User(
email=DEFAULT_ADMIN_EMAIL,
username=DEFAULT_ADMIN_USERNAME,
hashed_password=auth_manager.hash_password(DEFAULT_ADMIN_PASSWORD),
role="admin",
is_active=True
)
db.add(admin_user)
db.commit()
db.refresh(admin_user)
print("\n✅ Admin user created successfully!")
print("\n" + "=" * 50)
print(" Admin Credentials:")
print("=" * 50)
print(f" Email: {DEFAULT_ADMIN_EMAIL}")
print(f" Username: {DEFAULT_ADMIN_USERNAME}")
print(f" Password: {DEFAULT_ADMIN_PASSWORD}")
print("=" * 50)
print("\n⚠️ IMPORTANT: Change the password after first login!")
print(f" Login at: http://localhost:8000/static/admin/login.html")
print()
return True
def verify_database_ready() -> bool:
"""
Verify that database tables exist.
Returns:
bool: True if database is ready, False otherwise
"""
try:
# Try to query the users table
with engine.connect() as conn:
from sqlalchemy import text
result = conn.execute(
text("SELECT name FROM sqlite_master WHERE type='table' AND name='users'")
)
tables = result.fetchall()
return len(tables) > 0
except Exception as e:
print(f"❌ Error checking database: {e}")
return False
def main():
"""Main function to create admin user."""
print("\n" + "=" * 50)
print(" Admin User Creation Script")
print("=" * 50 + "\n")
# Step 1: Verify database is ready
print("1⃣ Checking database...")
if not verify_database_ready():
print("\n❌ ERROR: Database not ready!")
print("\n The 'users' table doesn't exist yet.")
print(" Please run database migrations first:")
print()
print(" alembic upgrade head")
print()
print(" Or if using make:")
print(" make migrate-up")
print()
sys.exit(1)
print(" ✓ Database is ready")
# Step 2: Create admin user
print("\n2⃣ Creating admin user...")
db = SessionLocal()
try:
created = create_admin_user(db)
if created:
print("\n🎉 Setup complete! You can now:")
print(" 1. Start the server: uvicorn main:app --reload")
print(" 2. Login at: http://localhost:8000/static/admin/login.html")
print()
else:
print("\n✓ No changes needed - admin user already exists")
print()
sys.exit(0)
except Exception as e:
print(f"\n❌ ERROR: Failed to create admin user")
print(f" {type(e).__name__}: {e}")
print()
print(" Common issues:")
print(" - Database migrations not run")
print(" - Database connection issues")
print(" - Permissions problems")
print()
sys.exit(1)
finally:
db.close()
if __name__ == "__main__":
main()

416
scripts/init_production.py Normal file
View File

@@ -0,0 +1,416 @@
#!/usr/bin/env python3
"""
Production Database Initialization for Wizamart Platform
This script initializes ESSENTIAL data required for production:
- Platform admin user
- Default vendor roles and permissions
- Admin settings
- Platform configuration
This is SAFE to run in production and should be run after migrations.
Usage:
make init-prod
This script is idempotent - safe to run multiple times.
"""
import sys
from pathlib import Path
from datetime import datetime, timezone
# 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
from app.core.database import SessionLocal
from app.core.config import settings, print_environment_info, validate_production_settings
from app.core.environment import is_production, get_environment
from models.database.user import User
from models.database.admin import AdminSetting
from middleware.auth import AuthManager
from app.core.permissions import PermissionGroups
# =============================================================================
# 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}")
# =============================================================================
# INITIALIZATION FUNCTIONS
# =============================================================================
def create_admin_user(db: Session, auth_manager: AuthManager) -> User:
"""Create or get the platform admin user."""
# Check if admin already exists
admin = db.execute(
select(User).where(User.email == settings.admin_email)
).scalar_one_or_none()
if admin:
print_warning(f"Admin user already exists: {admin.email}")
return admin
# Create new admin
hashed_password = auth_manager.hash_password(settings.admin_password)
admin = User(
username=settings.admin_username,
email=settings.admin_email,
hashed_password=hashed_password,
role="admin",
first_name=settings.admin_first_name,
last_name=settings.admin_last_name,
is_active=True,
is_email_verified=True,
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc),
)
db.add(admin)
db.flush()
print_success(f"Created admin user: {admin.email}")
return admin
def create_default_role_templates(db: Session) -> dict:
"""Create default role templates (not tied to any vendor).
These serve as templates that can be copied when creating vendor-specific roles.
Note: Actual roles are vendor-specific and created when vendors are onboarded.
"""
print(" Available role presets:")
print(" - Manager: Nearly full access (except team management)")
print(" - Staff: Day-to-day operations")
print(" - Support: Customer service focused")
print(" - Viewer: Read-only access")
print(" - Marketing: Marketing and customer communication")
print_success("Role templates ready for vendor onboarding")
return {
"manager": list(PermissionGroups.MANAGER),
"staff": list(PermissionGroups.STAFF),
"support": list(PermissionGroups.SUPPORT),
"viewer": list(PermissionGroups.VIEWER),
"marketing": list(PermissionGroups.MARKETING),
}
def create_admin_settings(db: Session) -> int:
"""Create essential admin settings."""
settings_created = 0
# Essential platform settings
default_settings = [
{
"key": "platform_name",
"value": settings.project_name,
"value_type": "string",
"description": "Platform name displayed in admin panel",
"is_public": True,
},
{
"key": "platform_url",
"value": f"https://{settings.platform_domain}",
"value_type": "string",
"description": "Main platform URL",
"is_public": True,
},
{
"key": "support_email",
"value": f"support@{settings.platform_domain}",
"value_type": "string",
"description": "Platform support email",
"is_public": True,
},
{
"key": "max_vendors_per_user",
"value": str(settings.max_vendors_per_user),
"value_type": "integer",
"description": "Maximum vendors a user can own",
"is_public": False,
},
{
"key": "max_team_members_per_vendor",
"value": str(settings.max_team_members_per_vendor),
"value_type": "integer",
"description": "Maximum team members per vendor",
"is_public": False,
},
{
"key": "invitation_expiry_days",
"value": str(settings.invitation_expiry_days),
"value_type": "integer",
"description": "Days until team invitation expires",
"is_public": False,
},
{
"key": "require_vendor_verification",
"value": "true",
"value_type": "boolean",
"description": "Require admin verification for new vendors",
"is_public": False,
},
{
"key": "allow_custom_domains",
"value": str(settings.allow_custom_domains).lower(),
"value_type": "boolean",
"description": "Allow vendors to use custom domains",
"is_public": False,
},
{
"key": "maintenance_mode",
"value": "false",
"value_type": "boolean",
"description": "Enable maintenance mode",
"is_public": True,
},
]
for setting_data in default_settings:
# Check if setting already exists
existing = db.execute(
select(AdminSetting).where(
AdminSetting.key == setting_data["key"]
)
).scalar_one_or_none()
if not existing:
setting = AdminSetting(
key=setting_data["key"],
value=setting_data["value"],
value_type=setting_data["value_type"],
description=setting_data.get("description"),
is_public=setting_data.get("is_public", False),
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc),
)
db.add(setting)
settings_created += 1
db.flush()
if settings_created > 0:
print_success(f"Created {settings_created} admin settings")
else:
print_warning("Admin settings already exist")
return settings_created
def verify_rbac_schema(db: Session) -> bool:
"""Verify that RBAC schema is in place."""
try:
from sqlalchemy import inspect
inspector = inspect(db.bind)
tables = inspector.get_table_names()
# Check users table has is_email_verified
if "users" in tables:
user_cols = {col["name"] for col in inspector.get_columns("users")}
if "is_email_verified" not in user_cols:
print_error("Missing 'is_email_verified' column in users table")
return False
# Check vendor_users has RBAC columns
if "vendor_users" in tables:
vu_cols = {col["name"] for col in inspector.get_columns("vendor_users")}
required_cols = {
"user_type",
"invitation_token",
"invitation_sent_at",
"invitation_accepted_at",
}
missing = required_cols - vu_cols
if missing:
print_error(f"Missing columns in vendor_users: {missing}")
return False
# Check roles table exists
if "roles" not in tables:
print_error("Missing 'roles' table")
return False
print_success("RBAC schema verified")
return True
except Exception as e:
print_error(f"Schema verification failed: {e}")
return False
# =============================================================================
# MAIN INITIALIZATION
# =============================================================================
def initialize_production(db: Session, auth_manager: AuthManager):
"""Initialize production database with essential data."""
print_header("PRODUCTION INITIALIZATION")
# Step 1: Verify RBAC schema
print_step(1, "Verifying RBAC schema...")
if not verify_rbac_schema(db):
print_error("RBAC schema not ready. Run migrations first:")
print(" make migrate-up")
sys.exit(1)
# Step 2: Create admin user
print_step(2, "Creating platform admin...")
admin = create_admin_user(db, auth_manager)
# Step 3: Set up default role templates
print_step(3, "Setting up role templates...")
role_templates = create_default_role_templates(db)
# Step 4: Create admin settings
print_step(4, "Creating admin settings...")
create_admin_settings(db)
# Commit all changes
db.commit()
print_success("All changes committed")
def print_summary(db: Session):
"""Print initialization summary."""
print_header("INITIALIZATION SUMMARY")
# Count records
user_count = db.query(User).filter(User.role == "admin").count()
setting_count = db.query(AdminSetting).count()
print(f"\n📊 Database Status:")
print(f" Admin users: {user_count}")
print(f" Admin settings: {setting_count}")
print("\n" + "" * 70)
print("🔐 ADMIN CREDENTIALS")
print("" * 70)
print(f" URL: /admin/login")
print(f" Username: {settings.admin_username}")
print(f" Password: {settings.admin_password}")
print("" * 70)
# Show security warnings if in production
if is_production():
warnings = validate_production_settings()
if warnings:
print("\n⚠️ SECURITY WARNINGS:")
for warning in warnings:
print(f" {warning}")
print("\n Please update your .env file with secure values!")
else:
print("\n⚠️ DEVELOPMENT MODE:")
print(" Default credentials are OK for development")
print(" Change them in production via .env file")
print("\n🚀 NEXT STEPS:")
print(" 1. Login to admin panel")
if is_production():
print(" 2. CHANGE DEFAULT PASSWORD IMMEDIATELY!")
print(" 3. Configure admin settings")
print(" 4. Create first vendor")
else:
print(" 2. Create demo data: make seed-demo")
print(" 3. Start development: make dev")
print("\n📝 FOR DEMO DATA (Development only):")
print(" make seed-demo")
# =============================================================================
# MAIN ENTRY POINT
# =============================================================================
def main():
"""Main entry point."""
print("\n" + "" + "" * 68 + "")
print("" + " " * 16 + "PRODUCTION INITIALIZATION" + " " * 27 + "")
print("" + "" * 68 + "")
# Show environment info
print_environment_info()
# Production safety check
if is_production():
warnings = validate_production_settings()
if warnings:
print("\n⚠️ PRODUCTION WARNINGS DETECTED:")
for warning in warnings:
print(f" {warning}")
print("\nUpdate your .env file before continuing!")
response = input("\nContinue anyway? (yes/no): ")
if response.lower() != "yes":
print("Initialization cancelled.")
sys.exit(0)
db = SessionLocal()
auth_manager = AuthManager()
try:
initialize_production(db, auth_manager)
print_summary(db)
print_header("✅ INITIALIZATION COMPLETED")
except KeyboardInterrupt:
db.rollback()
print("\n\n⚠️ Initialization interrupted")
sys.exit(1)
except Exception as e:
db.rollback()
print_header("❌ INITIALIZATION FAILED")
print(f"\nError: {e}\n")
import traceback
traceback.print_exc()
sys.exit(1)
finally:
db.close()
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

658
scripts/seed_demo.py Normal file
View File

@@ -0,0 +1,658 @@
#!/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()

View File

@@ -1,66 +0,0 @@
@echo off
setlocal enabledelayedexpansion
echo Generating frontend structure with statistics...
echo.
set OUTPUT=frontend-structure.txt
echo Frontend Folder Structure > %OUTPUT%
echo Generated: %date% %time% >> %OUTPUT%
echo ============================================================================== >> %OUTPUT%
echo. >> %OUTPUT%
echo. >> %OUTPUT%
echo ╔══════════════════════════════════════════════════════════════════╗ >> %OUTPUT%
echo ║ JINJA2 TEMPLATES ║ >> %OUTPUT%
echo ║ Location: app/templates ║ >> %OUTPUT%
echo ╚══════════════════════════════════════════════════════════════════╝ >> %OUTPUT%
echo. >> %OUTPUT%
tree /F /A app\templates >> %OUTPUT%
echo. >> %OUTPUT%
echo. >> %OUTPUT%
echo ╔══════════════════════════════════════════════════════════════════╗ >> %OUTPUT%
echo ║ STATIC ASSETS ║ >> %OUTPUT%
echo ║ Location: static ║ >> %OUTPUT%
echo ╚══════════════════════════════════════════════════════════════════╝ >> %OUTPUT%
echo. >> %OUTPUT%
tree /F /A static >> %OUTPUT%
echo. >> %OUTPUT%
echo. >> %OUTPUT%
echo ╔══════════════════════════════════════════════════════════════════╗ >> %OUTPUT%
echo ║ STATISTICS ║ >> %OUTPUT%
echo ╚══════════════════════════════════════════════════════════════════╝ >> %OUTPUT%
echo. >> %OUTPUT%
echo Templates: >> %OUTPUT%
echo - Total HTML files: >> %OUTPUT%
dir /S /B app\templates\*.html 2>nul | find /C ".html" >> %OUTPUT%
echo - Total Jinja2 files: >> %OUTPUT%
dir /S /B app\templates\*.j2 2>nul | find /C ".j2" >> %OUTPUT%
echo. >> %OUTPUT%
echo Static Assets: >> %OUTPUT%
echo - JavaScript files: >> %OUTPUT%
dir /S /B static\*.js 2>nul | find /C ".js" >> %OUTPUT%
echo - CSS files: >> %OUTPUT%
dir /S /B static\*.css 2>nul | find /C ".css" >> %OUTPUT%
echo - Image files: >> %OUTPUT%
for %%e in (png jpg jpeg gif svg webp ico) do (
dir /S /B static\*.%%e 2>nul | find /C ".%%e" >> %OUTPUT%
)
echo. >> %OUTPUT%
echo ============================================================================== >> %OUTPUT%
echo End of structure >> %OUTPUT%
echo.
echo ✅ Structure saved to %OUTPUT%
echo.
echo Opening file...
notepad %OUTPUT%
endlocal

528
scripts/show_structure.py Normal file
View File

@@ -0,0 +1,528 @@
#!/usr/bin/env python3
"""
Cross-platform structure generator for Wizamart project.
Works on Windows, Linux, and macOS.
Usage:
python show_structure.py frontend
python show_structure.py backend
python show_structure.py tests
python show_structure.py all
"""
import os
import sys
import subprocess
from datetime import datetime
from pathlib import Path
from typing import List, Dict
def count_files(directory: str, pattern: str) -> int:
"""Count files matching pattern in directory."""
if not os.path.exists(directory):
return 0
count = 0
for root, dirs, files in os.walk(directory):
# Skip __pycache__ and other cache directories
dirs[:] = [d for d in dirs if d not in ['__pycache__', '.pytest_cache', '.git', 'node_modules']]
for file in files:
if pattern == '*' or file.endswith(pattern):
count += 1
return count
def get_tree_structure(directory: str, exclude_patterns: List[str] = None) -> str:
"""Generate tree structure for directory."""
if not os.path.exists(directory):
return f"Directory {directory} not found"
# Try to use system tree command first
try:
if sys.platform == 'win32':
# Windows tree command
result = subprocess.run(
['tree', '/F', '/A', directory],
capture_output=True,
text=True,
encoding='utf-8',
errors='replace'
)
return result.stdout
else:
# Linux/Mac tree command with exclusions
exclude_args = []
if exclude_patterns:
exclude_args = ['-I', '|'.join(exclude_patterns)]
result = subprocess.run(
['tree', '-F', '-a'] + exclude_args + [directory],
capture_output=True,
text=True
)
if result.returncode == 0:
return result.stdout
except (subprocess.SubprocessError, FileNotFoundError):
pass
# Fallback: generate tree structure manually
return generate_manual_tree(directory, exclude_patterns)
def generate_manual_tree(directory: str, exclude_patterns: List[str] = None, prefix: str = "") -> str:
"""Generate tree structure manually when tree command is not available."""
if exclude_patterns is None:
exclude_patterns = ['__pycache__', '.pytest_cache', '.git', 'node_modules', '*.pyc', '*.pyo']
output = []
path = Path(directory)
try:
items = sorted(path.iterdir(), key=lambda x: (not x.is_dir(), x.name))
for i, item in enumerate(items):
# Skip excluded patterns
skip = False
for pattern in exclude_patterns:
if pattern.startswith('*'):
# File extension pattern
if item.name.endswith(pattern[1:]):
skip = True
break
else:
# Directory or exact name pattern
if item.name == pattern or pattern in str(item):
skip = True
break
if skip:
continue
is_last = i == len(items) - 1
current_prefix = "└── " if is_last else "├── "
if item.is_dir():
output.append(f"{prefix}{current_prefix}{item.name}/")
extension = " " if is_last else ""
subtree = generate_manual_tree(str(item), exclude_patterns, prefix + extension)
if subtree:
output.append(subtree)
else:
output.append(f"{prefix}{current_prefix}{item.name}")
except PermissionError:
output.append(f"{prefix}[Permission Denied]")
return "\n".join(output)
def generate_frontend_structure() -> str:
"""Generate frontend structure report."""
output = []
output.append("Frontend Folder Structure")
output.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
output.append("=" * 78)
output.append("")
# Templates section
output.append("")
output.append("╔══════════════════════════════════════════════════════════════════╗")
output.append("║ JINJA2 TEMPLATES ║")
output.append("║ Location: app/templates ║")
output.append("╚══════════════════════════════════════════════════════════════════╝")
output.append("")
output.append(get_tree_structure("app/templates"))
# Static assets section
output.append("")
output.append("")
output.append("╔══════════════════════════════════════════════════════════════════╗")
output.append("║ STATIC ASSETS ║")
output.append("║ Location: static ║")
output.append("╚══════════════════════════════════════════════════════════════════╝")
output.append("")
output.append(get_tree_structure("static"))
# Documentation section (if exists)
if os.path.exists("docs"):
output.append("")
output.append("")
output.append("╔══════════════════════════════════════════════════════════════════╗")
output.append("║ DOCUMENTATION ║")
output.append("║ Location: docs ║")
output.append("║ (also listed in tools structure) ║")
output.append("╚══════════════════════════════════════════════════════════════════╝")
output.append("")
output.append("Note: Documentation is also included in tools structure")
output.append(" for infrastructure/DevOps context.")
# Statistics section
output.append("")
output.append("")
output.append("╔══════════════════════════════════════════════════════════════════╗")
output.append("║ STATISTICS ║")
output.append("╚══════════════════════════════════════════════════════════════════╝")
output.append("")
output.append("Templates:")
output.append(f" - Total HTML files: {count_files('app/templates', '.html')}")
output.append(f" - Total Jinja2 files: {count_files('app/templates', '.j2')}")
output.append("")
output.append("Static Assets:")
output.append(f" - JavaScript files: {count_files('static', '.js')}")
output.append(f" - CSS files: {count_files('static', '.css')}")
output.append(" - Image files:")
for ext in ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'ico']:
count = count_files('static', f'.{ext}')
if count > 0:
output.append(f" - .{ext}: {count}")
if os.path.exists("docs"):
output.append("")
output.append("Documentation:")
output.append(f" - Markdown files: {count_files('docs', '.md')}")
output.append(f" - reStructuredText files: {count_files('docs', '.rst')}")
output.append("")
output.append("=" * 78)
output.append("End of structure")
return "\n".join(output)
def generate_backend_structure() -> str:
"""Generate backend structure report."""
output = []
output.append("Backend Folder Structure")
output.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
output.append("=" * 78)
output.append("")
exclude = ['__pycache__', '*.pyc', '*.pyo', '.pytest_cache', '*.egg-info', 'templates']
# Backend directories to include
backend_dirs = [
('app', 'Application Code'),
('middleware', 'Middleware Components'),
('models', 'Database Models'),
('storage', 'File Storage'),
('tasks', 'Background Tasks'),
('logs', 'Application Logs'),
]
for directory, title in backend_dirs:
if os.path.exists(directory):
output.append("")
output.append("╔══════════════════════════════════════════════════════════════════╗")
output.append(f"{title.upper().center(62)}")
output.append(f"║ Location: {directory + '/'.ljust(51)}")
output.append("╚══════════════════════════════════════════════════════════════════╝")
output.append("")
output.append(get_tree_structure(directory, exclude))
# Statistics section
output.append("")
output.append("")
output.append("╔══════════════════════════════════════════════════════════════════╗")
output.append("║ STATISTICS ║")
output.append("╚══════════════════════════════════════════════════════════════════╝")
output.append("")
output.append("Python Files by Directory:")
total_py_files = 0
for directory, title in backend_dirs:
if os.path.exists(directory):
count = count_files(directory, '.py')
total_py_files += count
output.append(f" - {directory}/: {count} files")
output.append(f" - Total Python files: {total_py_files}")
output.append("")
output.append("Application Components (if in app/):")
components = ['routes', 'services', 'schemas', 'exceptions', 'utils']
for component in components:
component_path = f"app/{component}"
if os.path.exists(component_path):
count = count_files(component_path, '.py')
output.append(f" - app/{component}: {count} files")
output.append("")
output.append("=" * 78)
output.append("End of structure")
return "\n".join(output)
def generate_tools_structure() -> str:
"""Generate tools/infrastructure structure report."""
output = []
output.append("Tools & Infrastructure Structure")
output.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
output.append("=" * 78)
output.append("")
exclude = ['__pycache__', '*.pyc', '*.pyo', '.pytest_cache', '*.egg-info']
# Tools directories to include
tools_dirs = [
('alembic', 'Database Migrations'),
('scripts', 'Utility Scripts'),
('docker', 'Docker Configuration'),
('docs', 'Documentation'),
]
for directory, title in tools_dirs:
if os.path.exists(directory):
output.append("")
output.append("╔══════════════════════════════════════════════════════════════════╗")
output.append(f"{title.upper().center(62)}")
output.append(f"║ Location: {directory + '/'.ljust(51)}")
output.append("╚══════════════════════════════════════════════════════════════════╝")
output.append("")
output.append(get_tree_structure(directory, exclude))
# Configuration files section
output.append("")
output.append("")
output.append("╔══════════════════════════════════════════════════════════════════╗")
output.append("║ CONFIGURATION FILES ║")
output.append("╚══════════════════════════════════════════════════════════════════╝")
output.append("")
output.append("Root configuration files:")
config_files = [
('Makefile', 'Build automation'),
('requirements.txt', 'Python dependencies'),
('pyproject.toml', 'Python project config'),
('setup.py', 'Python setup script'),
('setup.cfg', 'Setup configuration'),
('alembic.ini', 'Alembic migrations config'),
('mkdocs.yml', 'MkDocs documentation config'),
('Dockerfile', 'Docker image definition'),
('docker-compose.yml', 'Docker services'),
('.dockerignore', 'Docker ignore patterns'),
('.gitignore', 'Git ignore patterns'),
('.env.example', 'Environment variables template'),
]
for file, description in config_files:
if os.path.exists(file):
output.append(f"{file.ljust(25)} - {description}")
# Statistics section
output.append("")
output.append("")
output.append("╔══════════════════════════════════════════════════════════════════╗")
output.append("║ STATISTICS ║")
output.append("╚══════════════════════════════════════════════════════════════════╝")
output.append("")
output.append("Database Migrations:")
if os.path.exists("alembic/versions"):
migration_count = count_files("alembic/versions", ".py")
output.append(f" - Total migrations: {migration_count}")
if migration_count > 0:
# Get first and last migration
try:
migrations = sorted([f for f in os.listdir("alembic/versions") if f.endswith('.py')])
if migrations:
output.append(f" - First: {migrations[0][:40]}...")
if len(migrations) > 1:
output.append(f" - Latest: {migrations[-1][:40]}...")
except Exception:
pass
else:
output.append(" - No alembic/versions directory found")
output.append("")
output.append("Scripts:")
if os.path.exists("scripts"):
script_types = {
'.py': 'Python scripts',
'.sh': 'Shell scripts',
'.bat': 'Batch scripts',
}
for ext, desc in script_types.items():
count = count_files("scripts", ext)
if count > 0:
output.append(f" - {desc}: {count}")
else:
output.append(" - No scripts directory found")
output.append("")
output.append("Documentation:")
if os.path.exists("docs"):
doc_types = {
'.md': 'Markdown files',
'.rst': 'reStructuredText files',
}
for ext, desc in doc_types.items():
count = count_files("docs", ext)
if count > 0:
output.append(f" - {desc}: {count}")
else:
output.append(" - No docs directory found")
output.append("")
output.append("Docker:")
docker_files = ['Dockerfile', 'docker-compose.yml', '.dockerignore']
docker_exists = any(os.path.exists(f) for f in docker_files)
if docker_exists:
output.append(" ✓ Docker configuration present")
if os.path.exists("docker"):
output.append(f" - Docker directory files: {count_files('docker', '*')}")
else:
output.append(" - No Docker configuration found")
output.append("")
output.append("=" * 78)
output.append("End of structure")
return "\n".join(output)
def generate_test_structure() -> str:
"""Generate test structure report."""
output = []
output.append("Test Folder Structure")
output.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
output.append("=" * 78)
output.append("")
# Test files section
output.append("")
output.append("╔══════════════════════════════════════════════════════════════════╗")
output.append("║ TEST FILES ║")
output.append("║ Location: tests/ ║")
output.append("╚══════════════════════════════════════════════════════════════════╝")
output.append("")
exclude = ['__pycache__', '*.pyc', '*.pyo', '.pytest_cache', '*.egg-info']
output.append(get_tree_structure("tests", exclude))
# Configuration section
output.append("")
output.append("")
output.append("╔══════════════════════════════════════════════════════════════════╗")
output.append("║ TEST CONFIGURATION ║")
output.append("╚══════════════════════════════════════════════════════════════════╝")
output.append("")
output.append("Test configuration files:")
test_config_files = ['pytest.ini', 'conftest.py', 'tests/conftest.py', '.coveragerc']
for file in test_config_files:
if os.path.exists(file):
output.append(f"{file}")
# Statistics section
output.append("")
output.append("")
output.append("╔══════════════════════════════════════════════════════════════════╗")
output.append("║ STATISTICS ║")
output.append("╚══════════════════════════════════════════════════════════════════╝")
output.append("")
# Count test files
test_file_count = 0
if os.path.exists("tests"):
for root, dirs, files in os.walk("tests"):
dirs[:] = [d for d in dirs if d != '__pycache__']
for file in files:
if file.startswith("test_") and file.endswith(".py"):
test_file_count += 1
output.append("Test Files:")
output.append(f" - Total test files: {test_file_count}")
output.append("")
output.append("By Category:")
categories = ['unit', 'integration', 'system', 'e2e', 'performance']
for category in categories:
category_path = f"tests/{category}"
if os.path.exists(category_path):
count = 0
for root, dirs, files in os.walk(category_path):
dirs[:] = [d for d in dirs if d != '__pycache__']
for file in files:
if file.startswith("test_") and file.endswith(".py"):
count += 1
output.append(f" - tests/{category}: {count} files")
# Count test functions
test_function_count = 0
if os.path.exists("tests"):
for root, dirs, files in os.walk("tests"):
dirs[:] = [d for d in dirs if d != '__pycache__']
for file in files:
if file.startswith("test_") and file.endswith(".py"):
filepath = os.path.join(root, file)
try:
with open(filepath, 'r', encoding='utf-8') as f:
for line in f:
if line.strip().startswith("def test_"):
test_function_count += 1
except Exception:
pass
output.append("")
output.append("Test Functions:")
output.append(f" - Total test functions: {test_function_count}")
output.append("")
output.append("Coverage Files:")
if os.path.exists(".coverage"):
output.append(" ✓ .coverage (coverage data file exists)")
if os.path.exists("htmlcov"):
output.append(" ✓ htmlcov/ (HTML coverage report exists)")
output.append("")
output.append("=" * 78)
output.append("End of structure")
return "\n".join(output)
def main():
"""Main entry point."""
if len(sys.argv) < 2:
print("Usage: python show_structure.py [frontend|backend|tests|tools|all]")
sys.exit(1)
structure_type = sys.argv[1].lower()
generators = {
'frontend': ('frontend-structure.txt', generate_frontend_structure),
'backend': ('backend-structure.txt', generate_backend_structure),
'tests': ('test-structure.txt', generate_test_structure),
'tools': ('tools-structure.txt', generate_tools_structure),
}
if structure_type == 'all':
for name, (filename, generator) in generators.items():
print(f"\n{'=' * 60}")
print(f"Generating {name} structure...")
print('=' * 60)
content = generator()
with open(filename, 'w', encoding='utf-8') as f:
f.write(content)
print(f"{name.capitalize()} structure saved to {filename}")
print(f"\n{content}\n")
elif structure_type in generators:
filename, generator = generators[structure_type]
print(f"Generating {structure_type} structure...")
content = generator()
with open(filename, 'w', encoding='utf-8') as f:
f.write(content)
print(f"\n✅ Structure saved to {filename}\n")
print(content)
else:
print(f"Error: Unknown structure type '{structure_type}'")
print("Valid options: frontend, backend, tests, tools, all")
sys.exit(1)
if __name__ == "__main__":
main()