refactor: update seed script and Makefile for company architecture

Seed Script Updates:
- Add create_demo_companies() function to seed 3 demo companies with owners
- Update create_demo_vendors() to link vendors to companies (not create owners)
- Fix VendorTheme to use JSON colors format (not individual columns)
- Fix VendorDomain to use 'domain' field (not 'domain_name')
- Update seed summary to show company information
- Update credentials output to show company owners instead of vendor owners

Makefile Refactoring:
- Separate production initialization from demo data seeding
- Update init-prod to run 4 steps:
  1. Create admin user + alerts (init_production.py)
  2. Initialize log settings (init_log_settings.py)
  3. Create CMS defaults (create_default_content_pages.py)
  4. Create platform pages (create_platform_pages.py)
- Update db-setup workflow: migrate-up + init-prod + seed-demo
- Update db-reset workflow: migrate-down + migrate-up + init-prod + seed-demo-reset
- Add utility commands: create-cms-defaults, create-platform-pages, init-logging
- Enhance help documentation with clear production vs demo distinction

Architecture:
- init-prod: Production-safe platform initialization (run in prod + dev)
- seed-demo: Demo data only (NEVER run in production)
- Clear separation of concerns for production deployment
This commit is contained in:
2025-12-01 21:50:36 +01:00
parent 801510ecc6
commit c9c280a8c7
2 changed files with 261 additions and 107 deletions

View File

@@ -90,10 +90,24 @@ migrate-status:
# DATABASE INITIALIZATION & SEEDING # DATABASE INITIALIZATION & SEEDING
# ============================================================================= # =============================================================================
# Production initialization - Run on first setup (production OR development)
init-prod: init-prod:
@echo "🔧 Initializing production database..." @echo "🔧 Initializing production database..."
@echo ""
@echo "Step 1/4: Creating admin user and platform alerts..."
$(PYTHON) scripts/init_production.py $(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 "✅ Production initialization completed"
@echo "✨ Platform is ready for production OR development"
# Demo data seeding - Cross-platform using Python to set environment # Demo data seeding - Cross-platform using Python to set environment
seed-demo: seed-demo:
@@ -122,23 +136,35 @@ else
SEED_MODE=reset $(PYTHON) scripts/seed_demo.py SEED_MODE=reset $(PYTHON) scripts/seed_demo.py
endif 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 "✅ 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!" @echo "✅ Database completely reset!"
backup-db: backup-db:
@echo "Creating database backup..." @echo "Creating database backup..."
@$(PYTHON) scripts/backup_database.py @$(PYTHON) scripts/backup_database.py
# CMS default content pages # Utility commands (usually not needed - init-prod handles these)
create-cms-defaults: create-cms-defaults:
@echo "📄 Creating default CMS content pages..." @echo "📄 Creating default CMS content pages..."
$(PYTHON) scripts/create_default_content_pages.py $(PYTHON) scripts/create_default_content_pages.py
@echo "✅ CMS defaults created" @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 # TESTING
# ============================================================================= # =============================================================================
@@ -320,12 +346,11 @@ help:
@echo " migrate-up - Apply pending migrations" @echo " migrate-up - Apply pending migrations"
@echo " migrate-down - Rollback last migration" @echo " migrate-down - Rollback last migration"
@echo " migrate-status - Show migration status" @echo " migrate-status - Show migration status"
@echo " init-prod - Initialize production essentials" @echo " init-prod - Initialize platform (admin, logging, CMS, pages)"
@echo " seed-demo - Seed demo data (3 vendors)" @echo " seed-demo - Seed demo data (3 companies + vendors)"
@echo " seed-demo-minimal - Seed minimal demo (1 vendor)" @echo " seed-demo-minimal - Seed minimal demo (1 company + vendor)"
@echo " seed-demo-reset - DELETE ALL and reseed" @echo " seed-demo-reset - DELETE ALL demo data and reseed"
@echo " create-cms-defaults - Create default CMS content pages" @echo " db-setup - Full dev setup (migrate + init-prod + seed-demo)"
@echo " db-setup - Full dev setup (migrate + init + cms + seed)"
@echo " backup-db - Backup database" @echo " backup-db - Backup database"
@echo "" @echo ""
@echo "=== TESTING ===" @echo "=== TESTING ==="
@@ -372,30 +397,44 @@ help-db:
@echo " migrate-down - Rollback last migration" @echo " migrate-down - Rollback last migration"
@echo " migrate-status - Show current status and history" @echo " migrate-status - Show current status and history"
@echo "" @echo ""
@echo "INITIALIZATION:" @echo "PLATFORM INITIALIZATION (Production + Development):"
@echo " init-prod - Create admin user + settings (SAFE for production)" @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 ""
@echo "DEMO DATA (Development only):" @echo "DEMO DATA (Development Only - NEVER in production):"
@echo " seed-demo - Create 3 demo vendors with data" @echo "──────────────────────────────────────────────────────────"
@echo " seed-demo-minimal - Create 1 demo vendor only" @echo " seed-demo - Create 3 demo companies + vendors + data"
@echo " seed-demo-reset - DELETE ALL data and reseed (DANGEROUS!)" @echo " seed-demo-minimal - Create 1 demo company + vendor only"
@echo " seed-demo-reset - DELETE ALL demo data and reseed (DANGEROUS!)"
@echo "" @echo ""
@echo "CMS CONTENT:" @echo "UTILITY COMMANDS (Advanced - usually not needed):"
@echo " create-cms-defaults - Create default content pages (about, faq, etc.)" @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 ""
@echo "WORKFLOWS:" @echo "QUICK WORKFLOWS:"
@echo " db-setup - Complete dev setup (migrate + init + cms + seed)" @echo "──────────────────────────────────────────────────────────"
@echo " db-reset - Nuclear option: rollback + reset + reseed" @echo " db-setup - Full dev setup (migrate + init-prod + seed-demo)"
@echo " db-reset - Nuclear reset (rollback + init-prod + reseed)"
@echo "" @echo ""
@echo "TYPICAL FIRST-TIME SETUP:" @echo "TYPICAL FIRST-TIME SETUP (Development):"
@echo " 1. make migrate-up # Apply migrations" @echo "──────────────────────────────────────────────────────────"
@echo " 2. make init-prod # Create admin user" @echo " 1. make migrate-up # Apply database schema"
@echo " 3. make create-cms-defaults # Create CMS pages" @echo " 2. make init-prod # Initialize platform (admin, CMS, logging, pages)"
@echo " 4. make seed-demo # Add demo data" @echo " 3. make seed-demo # Add demo data (companies, vendors, products)"
@echo " 5. make dev # Start developing" @echo " 4. make dev # Start development server"
@echo ""
@echo " OR simply: make db-setup # Does all the above!"
@echo "" @echo ""
@echo "PRODUCTION SETUP:" @echo "PRODUCTION SETUP:"
@echo "──────────────────────────────────────────────────────────"
@echo " 1. Set ENV=production or ENVIRONMENT=production" @echo " 1. Set ENV=production or ENVIRONMENT=production"
@echo " 2. make migrate-up" @echo " 2. make migrate-up # Apply database schema"
@echo " 3. make init-prod (with secure credentials in .env)" @echo " 3. make init-prod # Initialize platform (with .env credentials)"
@echo " 4. Create vendors via admin panel" @echo " 4. Create companies via admin panel"
@echo " 5. DO NOT run seed-demo in production!"

View File

@@ -47,6 +47,7 @@ from app.core.database import SessionLocal
from app.core.environment import get_environment, is_production from app.core.environment import get_environment, is_production
from middleware.auth import AuthManager from middleware.auth import AuthManager
from models.database.admin import PlatformAlert from models.database.admin import PlatformAlert
from models.database.company import Company
from models.database.customer import Customer, CustomerAddress from models.database.customer import Customer, CustomerAddress
from models.database.marketplace_import_job import MarketplaceImportJob from models.database.marketplace_import_job import MarketplaceImportJob
from models.database.marketplace_product import MarketplaceProduct 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 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 = [ DEMO_VENDORS = [
{ {
"company_index": 0, # WizaCorp
"vendor_code": "WIZAMART", "vendor_code": "WIZAMART",
"name": "WizaMart", "name": "WizaMart",
"subdomain": "wizamart", "subdomain": "wizamart",
@@ -74,6 +119,7 @@ DEMO_VENDORS = [
"custom_domain": "wizamart.shop", "custom_domain": "wizamart.shop",
}, },
{ {
"company_index": 1, # Fashion Group
"vendor_code": "FASHIONHUB", "vendor_code": "FASHIONHUB",
"name": "Fashion Hub", "name": "Fashion Hub",
"subdomain": "fashionhub", "subdomain": "fashionhub",
@@ -82,6 +128,7 @@ DEMO_VENDORS = [
"custom_domain": "fashionhub.store", "custom_domain": "fashionhub.store",
}, },
{ {
"company_index": 2, # BookWorld
"vendor_code": "BOOKSTORE", "vendor_code": "BOOKSTORE",
"name": "The Book Store", "name": "The Book Store",
"subdomain": "bookstore", "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
THEME_PRESETS = { THEME_PRESETS = {
"modern": { "modern": {
@@ -241,6 +263,7 @@ def reset_all_data(db: Session):
Role, Role,
VendorUser, VendorUser,
Vendor, Vendor,
Company, # Delete companies (cascades to vendors)
PlatformAlert, PlatformAlert,
] ]
@@ -259,17 +282,94 @@ def reset_all_data(db: Session):
# ============================================================================= # =============================================================================
def create_demo_vendors(db: Session, auth_manager: AuthManager) -> list[Vendor]: def create_demo_companies(db: Session, auth_manager: AuthManager) -> list[Company]:
"""Create demo vendors with users.""" """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 = [] vendors = []
# Determine how many vendors to create based on mode # 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] 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 # Check if vendor already exists
existing = db.execute( existing = db.execute(
select(Vendor).where(Vendor.vendor_code == vendor_data["vendor_code"]) 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) vendors.append(existing)
continue continue
# Create vendor user # Get company by index
vendor_user = User( company_index = vendor_data["company_index"]
username=user_data["username"], if company_index >= len(companies):
email=user_data["email"], print_error(
hashed_password=auth_manager.hash_password(user_data["password"]), f"Invalid company_index {company_index} for vendor {vendor_data['name']}"
role="vendor", )
first_name=user_data["first_name"], continue
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()
# Create vendor company = companies[company_index]
# Create vendor linked to company
vendor = Vendor( vendor = Vendor(
company_id=company.id, # Link to company
vendor_code=vendor_data["vendor_code"], vendor_code=vendor_data["vendor_code"],
name=vendor_data["name"], name=vendor_data["name"],
subdomain=vendor_data["subdomain"], subdomain=vendor_data["subdomain"],
description=vendor_data["description"], description=vendor_data["description"],
owner_user_id=company.owner_user_id, # Use company owner (for backward compatibility)
is_active=True, is_active=True,
is_verified=True, is_verified=True,
created_at=datetime.now(UTC), created_at=datetime.now(UTC),
@@ -310,10 +406,10 @@ def create_demo_vendors(db: Session, auth_manager: AuthManager) -> list[Vendor]:
db.add(vendor) db.add(vendor)
db.flush() db.flush()
# Link user to vendor as owner # Link company owner to vendor as owner
vendor_user_link = VendorUser( vendor_user_link = VendorUser(
vendor_id=vendor.id, vendor_id=vendor.id,
user_id=vendor_user.id, user_id=company.owner_user_id,
user_type="owner", user_type="owner",
is_active=True, is_active=True,
created_at=datetime.now(UTC), created_at=datetime.now(UTC),
@@ -327,11 +423,13 @@ def create_demo_vendors(db: Session, auth_manager: AuthManager) -> list[Vendor]:
theme = VendorTheme( theme = VendorTheme(
vendor_id=vendor.id, vendor_id=vendor.id,
theme_name=vendor_data["theme_preset"], theme_name=vendor_data["theme_preset"],
primary_color=theme_colors["primary"], colors={ # ✅ Use JSON format
secondary_color=theme_colors["secondary"], "primary": theme_colors["primary"],
accent_color=theme_colors["accent"], "secondary": theme_colors["secondary"],
background_color=theme_colors["background"], "accent": theme_colors["accent"],
text_color=theme_colors["text"], "background": theme_colors["background"],
"text": theme_colors["text"],
},
is_active=True, is_active=True,
created_at=datetime.now(UTC), created_at=datetime.now(UTC),
updated_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"): if vendor_data.get("custom_domain"):
domain = VendorDomain( domain = VendorDomain(
vendor_id=vendor.id, 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_verified=True, # Auto-verified for demo
is_active=True, is_primary=True,
verification_token=None, verification_token=None,
created_at=datetime.now(UTC), created_at=datetime.now(UTC),
updated_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...") print_step(3, "Resetting data...")
reset_all_data(db) reset_all_data(db)
# Step 4: Create vendors # Step 4: Create companies
print_step(4, "Creating demo vendors...") print_step(4, "Creating demo companies...")
vendors = create_demo_vendors(db, auth_manager) companies = create_demo_companies(db, auth_manager)
# Step 5: Create customers # Step 5: Create vendors
print_step(5, "Creating demo customers...") 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: for vendor in vendors:
create_demo_customers( create_demo_customers(
db, vendor, auth_manager, count=settings.seed_customers_per_vendor db, vendor, auth_manager, count=settings.seed_customers_per_vendor
) )
# Step 6: Create products # Step 7: Create products
print_step(6, "Creating demo products...") print_step(7, "Creating demo products...")
for vendor in vendors: for vendor in vendors:
create_demo_products(db, vendor, count=settings.seed_products_per_vendor) create_demo_products(db, vendor, count=settings.seed_products_per_vendor)
@@ -542,17 +644,30 @@ def print_summary(db: Session):
print_header("SEEDING SUMMARY") print_header("SEEDING SUMMARY")
# Count records # Count records
company_count = db.query(Company).count()
vendor_count = db.query(Vendor).count() vendor_count = db.query(Vendor).count()
user_count = db.query(User).count() user_count = db.query(User).count()
customer_count = db.query(Customer).count() customer_count = db.query(Customer).count()
product_count = db.query(Product).count() product_count = db.query(Product).count()
print("\n📊 Database Status:") print("\n📊 Database Status:")
print(f" Companies: {company_count}")
print(f" Vendors: {vendor_count}") print(f" Vendors: {vendor_count}")
print(f" Users: {user_count}") print(f" Users: {user_count}")
print(f" Customers: {customer_count}") print(f" Customers: {customer_count}")
print(f" Products: {product_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 # Show vendor details
vendors = db.query(Vendor).all() vendors = db.query(Vendor).all()
print("\n🏪 Demo Vendors:") print("\n🏪 Demo Vendors:")
@@ -579,21 +694,21 @@ def print_summary(db: Session):
print(f" Status: {'✓ Active' if vendor.is_active else '✗ Inactive'}") print(f" Status: {'✓ Active' if vendor.is_active else '✗ Inactive'}")
print("\n🔐 Demo Vendor Credentials:") print("\n🔐 Demo Company Owner Credentials:")
print("" * 70) print("" * 70)
for i, vendor_data in enumerate(DEMO_VENDOR_USERS[:vendor_count], 1): for i, company_data in enumerate(DEMO_COMPANIES[:company_count], 1):
vendor = vendors[i - 1] if i <= len(vendors) else None company = companies[i - 1] if i <= len(companies) else None
print(f" Vendor {i}:") print(f" Company {i}: {company_data['name']}")
print(f" Username: {vendor_data['username']}") print(f" Email: {company_data['owner_email']}")
print(f" Email: {vendor_data['email']}") print(f" Password: {company_data['owner_password']}")
print(f" Password: {vendor_data['password']}") if company and company.vendors:
if vendor: for vendor in company.vendors:
print( print(
f" Login: http://localhost:8000/vendor/{vendor.vendor_code}/login" f" Vendor: http://localhost:8000/vendor/{vendor.vendor_code}/login"
) )
print( print(
f" or http://{vendor.subdomain}.localhost:8000/vendor/login" f" or http://{vendor.subdomain}.localhost:8000/vendor/login"
) )
print() print()
print("\n🛒 Demo Customer Credentials:") print("\n🛒 Demo Customer Credentials:")