diff --git a/Makefile b/Makefile index 7b8a64d9..66667723 100644 --- a/Makefile +++ b/Makefile @@ -90,10 +90,24 @@ migrate-status: # DATABASE INITIALIZATION & SEEDING # ============================================================================= +# Production initialization - Run on first setup (production OR development) init-prod: @echo "šŸ”§ Initializing production database..." + @echo "" + @echo "Step 1/4: Creating admin user and platform alerts..." $(PYTHON) scripts/init_production.py + @echo "" + @echo "Step 2/4: Initializing log settings..." + $(PYTHON) scripts/init_log_settings.py + @echo "" + @echo "Step 3/4: Creating default CMS content pages..." + $(PYTHON) scripts/create_default_content_pages.py + @echo "" + @echo "Step 4/4: Creating platform pages and landing..." + $(PYTHON) scripts/create_platform_pages.py + @echo "" @echo "āœ… Production initialization completed" + @echo "✨ Platform is ready for production OR development" # Demo data seeding - Cross-platform using Python to set environment seed-demo: @@ -122,23 +136,35 @@ else SEED_MODE=reset $(PYTHON) scripts/seed_demo.py endif -db-setup: migrate-up init-prod create-cms-defaults seed-demo +db-setup: migrate-up init-prod seed-demo + @echo "" @echo "āœ… Database setup complete!" - @echo "Run 'make dev' to start development server" + @echo "✨ Run 'make dev' to start development server" -db-reset: migrate-down migrate-up seed-demo-reset +db-reset: migrate-down migrate-up init-prod seed-demo-reset + @echo "" @echo "āœ… Database completely reset!" backup-db: @echo "Creating database backup..." @$(PYTHON) scripts/backup_database.py -# CMS default content pages +# Utility commands (usually not needed - init-prod handles these) create-cms-defaults: @echo "šŸ“„ Creating default CMS content pages..." $(PYTHON) scripts/create_default_content_pages.py @echo "āœ… CMS defaults created" +create-platform-pages: + @echo "šŸ  Creating platform pages and landing..." + $(PYTHON) scripts/create_platform_pages.py + @echo "āœ… Platform pages created" + +init-logging: + @echo "šŸ“ Initializing log settings..." + $(PYTHON) scripts/init_log_settings.py + @echo "āœ… Log settings initialized" + # ============================================================================= # TESTING # ============================================================================= @@ -320,12 +346,11 @@ help: @echo " migrate-up - Apply pending migrations" @echo " migrate-down - Rollback last migration" @echo " migrate-status - Show migration status" - @echo " init-prod - Initialize production essentials" - @echo " seed-demo - Seed demo data (3 vendors)" - @echo " seed-demo-minimal - Seed minimal demo (1 vendor)" - @echo " seed-demo-reset - DELETE ALL and reseed" - @echo " create-cms-defaults - Create default CMS content pages" - @echo " db-setup - Full dev setup (migrate + init + cms + seed)" + @echo " init-prod - Initialize platform (admin, logging, CMS, pages)" + @echo " seed-demo - Seed demo data (3 companies + vendors)" + @echo " seed-demo-minimal - Seed minimal demo (1 company + vendor)" + @echo " seed-demo-reset - DELETE ALL demo data and reseed" + @echo " db-setup - Full dev setup (migrate + init-prod + seed-demo)" @echo " backup-db - Backup database" @echo "" @echo "=== TESTING ===" @@ -372,30 +397,44 @@ help-db: @echo " migrate-down - Rollback last migration" @echo " migrate-status - Show current status and history" @echo "" - @echo "INITIALIZATION:" - @echo " init-prod - Create admin user + settings (SAFE for production)" + @echo "PLATFORM INITIALIZATION (Production + Development):" + @echo "──────────────────────────────────────────────────────────" + @echo " init-prod - Complete platform setup (4 steps):" + @echo " 1. Create admin user + alerts" + @echo " 2. Initialize log settings" + @echo " 3. Create CMS defaults" + @echo " 4. Create platform pages" @echo "" - @echo "DEMO DATA (Development only):" - @echo " seed-demo - Create 3 demo vendors with data" - @echo " seed-demo-minimal - Create 1 demo vendor only" - @echo " seed-demo-reset - DELETE ALL data and reseed (DANGEROUS!)" + @echo "DEMO DATA (Development Only - NEVER in production):" + @echo "──────────────────────────────────────────────────────────" + @echo " seed-demo - Create 3 demo companies + vendors + data" + @echo " seed-demo-minimal - Create 1 demo company + vendor only" + @echo " seed-demo-reset - DELETE ALL demo data and reseed (DANGEROUS!)" @echo "" - @echo "CMS CONTENT:" - @echo " create-cms-defaults - Create default content pages (about, faq, etc.)" + @echo "UTILITY COMMANDS (Advanced - usually not needed):" + @echo "──────────────────────────────────────────────────────────" + @echo " create-cms-defaults - Re-create CMS pages only" + @echo " create-platform-pages - Re-create platform pages only" + @echo " init-logging - Re-initialize logging only" @echo "" - @echo "WORKFLOWS:" - @echo " db-setup - Complete dev setup (migrate + init + cms + seed)" - @echo " db-reset - Nuclear option: rollback + reset + reseed" + @echo "QUICK WORKFLOWS:" + @echo "──────────────────────────────────────────────────────────" + @echo " db-setup - Full dev setup (migrate + init-prod + seed-demo)" + @echo " db-reset - Nuclear reset (rollback + init-prod + reseed)" @echo "" - @echo "TYPICAL FIRST-TIME SETUP:" - @echo " 1. make migrate-up # Apply migrations" - @echo " 2. make init-prod # Create admin user" - @echo " 3. make create-cms-defaults # Create CMS pages" - @echo " 4. make seed-demo # Add demo data" - @echo " 5. make dev # Start developing" + @echo "TYPICAL FIRST-TIME SETUP (Development):" + @echo "──────────────────────────────────────────────────────────" + @echo " 1. make migrate-up # Apply database schema" + @echo " 2. make init-prod # Initialize platform (admin, CMS, logging, pages)" + @echo " 3. make seed-demo # Add demo data (companies, vendors, products)" + @echo " 4. make dev # Start development server" + @echo "" + @echo " OR simply: make db-setup # Does all the above!" @echo "" @echo "PRODUCTION SETUP:" + @echo "──────────────────────────────────────────────────────────" @echo " 1. Set ENV=production or ENVIRONMENT=production" - @echo " 2. make migrate-up" - @echo " 3. make init-prod (with secure credentials in .env)" - @echo " 4. Create vendors via admin panel" \ No newline at end of file + @echo " 2. make migrate-up # Apply database schema" + @echo " 3. make init-prod # Initialize platform (with .env credentials)" + @echo " 4. Create companies via admin panel" + @echo " 5. DO NOT run seed-demo in production!" \ No newline at end of file diff --git a/scripts/seed_demo.py b/scripts/seed_demo.py index c3ef06da..6d0f5d8a 100644 --- a/scripts/seed_demo.py +++ b/scripts/seed_demo.py @@ -47,6 +47,7 @@ from app.core.database import SessionLocal from app.core.environment import get_environment, is_production from middleware.auth import AuthManager from models.database.admin import PlatformAlert +from models.database.company import Company from models.database.customer import Customer, CustomerAddress from models.database.marketplace_import_job import MarketplaceImportJob from models.database.marketplace_product import MarketplaceProduct @@ -63,9 +64,53 @@ SEED_MODE = os.getenv("SEED_MODE", "normal") # normal, minimal, reset # DEMO DATA CONFIGURATION # ============================================================================= -# Demo vendor configurations +# Demo company configurations (NEW: Company-based architecture) +DEMO_COMPANIES = [ + { + "name": "WizaCorp Ltd.", + "description": "Leading technology and electronics distributor", + "owner_email": "john.owner@wizacorp.com", + "owner_password": "password123", + "owner_first_name": "John", + "owner_last_name": "Smith", + "contact_email": "info@wizacorp.com", + "contact_phone": "+352 123 456 789", + "website": "https://www.wizacorp.com", + "business_address": "123 Tech Street, Luxembourg City, L-1234, Luxembourg", + "tax_number": "LU12345678", + }, + { + "name": "Fashion Group S.A.", + "description": "International fashion and lifestyle retailer", + "owner_email": "jane.owner@fashiongroup.com", + "owner_password": "password123", + "owner_first_name": "Jane", + "owner_last_name": "Merchant", + "contact_email": "contact@fashiongroup.com", + "contact_phone": "+352 234 567 890", + "website": "https://www.fashiongroup.com", + "business_address": "456 Fashion Avenue, Luxembourg, L-5678, Luxembourg", + "tax_number": "LU23456789", + }, + { + "name": "BookWorld Publishing", + "description": "Books, education, and media content provider", + "owner_email": "bob.owner@bookworld.com", + "owner_password": "password123", + "owner_first_name": "Bob", + "owner_last_name": "Seller", + "contact_email": "support@bookworld.com", + "contact_phone": "+352 345 678 901", + "website": "https://www.bookworld.com", + "business_address": "789 Library Lane, Esch-sur-Alzette, L-9012, Luxembourg", + "tax_number": "LU34567890", + }, +] + +# Demo vendor configurations (linked to companies by index) DEMO_VENDORS = [ { + "company_index": 0, # WizaCorp "vendor_code": "WIZAMART", "name": "WizaMart", "subdomain": "wizamart", @@ -74,6 +119,7 @@ DEMO_VENDORS = [ "custom_domain": "wizamart.shop", }, { + "company_index": 1, # Fashion Group "vendor_code": "FASHIONHUB", "name": "Fashion Hub", "subdomain": "fashionhub", @@ -82,6 +128,7 @@ DEMO_VENDORS = [ "custom_domain": "fashionhub.store", }, { + "company_index": 2, # BookWorld "vendor_code": "BOOKSTORE", "name": "The Book Store", "subdomain": "bookstore", @@ -91,31 +138,6 @@ DEMO_VENDORS = [ }, ] -# 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": { @@ -241,6 +263,7 @@ def reset_all_data(db: Session): Role, VendorUser, Vendor, + Company, # Delete companies (cascades to vendors) PlatformAlert, ] @@ -259,17 +282,94 @@ def reset_all_data(db: Session): # ============================================================================= -def create_demo_vendors(db: Session, auth_manager: AuthManager) -> list[Vendor]: - """Create demo vendors with users.""" +def create_demo_companies(db: Session, auth_manager: AuthManager) -> list[Company]: + """Create demo companies with owner accounts.""" + + companies = [] + + # Determine how many companies to create based on mode + company_count = 1 if SEED_MODE == "minimal" else len(DEMO_COMPANIES) + companies_to_create = DEMO_COMPANIES[:company_count] + + for company_data in companies_to_create: + # Check if company already exists + existing = db.execute( + select(Company).where(Company.name == company_data["name"]) + ).scalar_one_or_none() + + if existing: + print_warning(f"Company already exists: {company_data['name']}") + companies.append(existing) + continue + + # Check if owner user already exists + owner_user = db.execute( + select(User).where(User.email == company_data["owner_email"]) + ).scalar_one_or_none() + + if not owner_user: + # Create owner user + owner_user = User( + username=company_data["owner_email"].split("@")[0], + email=company_data["owner_email"], + hashed_password=auth_manager.hash_password( + company_data["owner_password"] + ), + role="user", + first_name=company_data["owner_first_name"], + last_name=company_data["owner_last_name"], + is_active=True, + is_email_verified=True, + created_at=datetime.now(UTC), + updated_at=datetime.now(UTC), + ) + db.add(owner_user) + db.flush() + print_success( + f"Created owner user: {owner_user.email} (password: {company_data['owner_password']})" + ) + else: + print_warning(f"Using existing user as owner: {owner_user.email}") + + # Create company + company = Company( + name=company_data["name"], + description=company_data["description"], + owner_user_id=owner_user.id, + contact_email=company_data["contact_email"], + contact_phone=company_data.get("contact_phone"), + website=company_data.get("website"), + business_address=company_data.get("business_address"), + tax_number=company_data.get("tax_number"), + is_active=True, + is_verified=True, # Auto-verified for demo + created_at=datetime.now(UTC), + updated_at=datetime.now(UTC), + ) + db.add(company) + db.flush() + + companies.append(company) + print_success( + f"Created company: {company.name} (Owner: {owner_user.email})" + ) + + db.flush() + return companies + + +def create_demo_vendors( + db: Session, companies: list[Company], auth_manager: AuthManager +) -> list[Vendor]: + """Create demo vendors linked to companies.""" vendors = [] # Determine how many vendors to create based on mode - vendor_count = 1 if SEED_MODE == "minimal" else settings.seed_demo_vendors + vendor_count = 1 if SEED_MODE == "minimal" else len(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): + for vendor_data in vendors_to_create: # Check if vendor already exists existing = db.execute( select(Vendor).where(Vendor.vendor_code == vendor_data["vendor_code"]) @@ -280,28 +380,24 @@ def create_demo_vendors(db: Session, auth_manager: AuthManager) -> list[Vendor]: 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(UTC), - updated_at=datetime.now(UTC), - ) - db.add(vendor_user) - db.flush() + # Get company by index + company_index = vendor_data["company_index"] + if company_index >= len(companies): + print_error( + f"Invalid company_index {company_index} for vendor {vendor_data['name']}" + ) + continue - # Create vendor + company = companies[company_index] + + # Create vendor linked to company vendor = Vendor( + company_id=company.id, # Link to company vendor_code=vendor_data["vendor_code"], name=vendor_data["name"], subdomain=vendor_data["subdomain"], description=vendor_data["description"], + owner_user_id=company.owner_user_id, # Use company owner (for backward compatibility) is_active=True, is_verified=True, created_at=datetime.now(UTC), @@ -310,10 +406,10 @@ def create_demo_vendors(db: Session, auth_manager: AuthManager) -> list[Vendor]: db.add(vendor) db.flush() - # Link user to vendor as owner + # Link company owner to vendor as owner vendor_user_link = VendorUser( vendor_id=vendor.id, - user_id=vendor_user.id, + user_id=company.owner_user_id, user_type="owner", is_active=True, created_at=datetime.now(UTC), @@ -327,11 +423,13 @@ def create_demo_vendors(db: Session, auth_manager: AuthManager) -> list[Vendor]: 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"], + colors={ # āœ… Use JSON format + "primary": theme_colors["primary"], + "secondary": theme_colors["secondary"], + "accent": theme_colors["accent"], + "background": theme_colors["background"], + "text": theme_colors["text"], + }, is_active=True, created_at=datetime.now(UTC), updated_at=datetime.now(UTC), @@ -342,9 +440,9 @@ def create_demo_vendors(db: Session, auth_manager: AuthManager) -> list[Vendor]: if vendor_data.get("custom_domain"): domain = VendorDomain( vendor_id=vendor.id, - domain_name=vendor_data["custom_domain"], + domain=vendor_data["custom_domain"], # āœ… Field is 'domain', not 'domain_name' is_verified=True, # Auto-verified for demo - is_active=True, + is_primary=True, verification_token=None, created_at=datetime.now(UTC), updated_at=datetime.now(UTC), @@ -515,19 +613,23 @@ def seed_demo_data(db: Session, auth_manager: AuthManager): 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 4: Create companies + print_step(4, "Creating demo companies...") + companies = create_demo_companies(db, auth_manager) - # Step 5: Create customers - print_step(5, "Creating demo customers...") + # Step 5: Create vendors + print_step(5, "Creating demo vendors...") + vendors = create_demo_vendors(db, companies, auth_manager) + + # Step 6: Create customers + print_step(6, "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...") + # Step 7: Create products + print_step(7, "Creating demo products...") for vendor in vendors: create_demo_products(db, vendor, count=settings.seed_products_per_vendor) @@ -542,17 +644,30 @@ def print_summary(db: Session): print_header("SEEDING SUMMARY") # Count records + company_count = db.query(Company).count() 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("\nšŸ“Š Database Status:") + print(f" Companies: {company_count}") print(f" Vendors: {vendor_count}") print(f" Users: {user_count}") print(f" Customers: {customer_count}") print(f" Products: {product_count}") + # Show company details + companies = db.query(Company).all() + print("\nšŸ¢ Demo Companies:") + for company in companies: + print(f"\n {company.name}") + print(f" Owner: {company.owner.email if company.owner else 'N/A'}") + print(f" Vendors: {len(company.vendors) if company.vendors else 0}") + print(f" Status: {'āœ“ Active' if company.is_active else 'āœ— Inactive'}") + if company.is_verified: + print(f" Verified: āœ“") + # Show vendor details vendors = db.query(Vendor).all() print("\nšŸŖ Demo Vendors:") @@ -579,21 +694,21 @@ def print_summary(db: Session): print(f" Status: {'āœ“ Active' if vendor.is_active else 'āœ— Inactive'}") - print("\nšŸ” Demo Vendor Credentials:") + print("\nšŸ” Demo Company Owner 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" - ) + for i, company_data in enumerate(DEMO_COMPANIES[:company_count], 1): + company = companies[i - 1] if i <= len(companies) else None + print(f" Company {i}: {company_data['name']}") + print(f" Email: {company_data['owner_email']}") + print(f" Password: {company_data['owner_password']}") + if company and company.vendors: + for vendor in company.vendors: + print( + f" Vendor: http://localhost:8000/vendor/{vendor.vendor_code}/login" + ) + print( + f" or http://{vendor.subdomain}.localhost:8000/vendor/login" + ) print() print("\nšŸ›’ Demo Customer Credentials:")