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
# =============================================================================
# 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"
@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!"

View File

@@ -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),
# 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']}"
)
db.add(vendor_user)
db.flush()
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,17 +694,17 @@ 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:
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" Login: http://localhost:8000/vendor/{vendor.vendor_code}/login"
f" Vendor: http://localhost:8000/vendor/{vendor.vendor_code}/login"
)
print(
f" or http://{vendor.subdomain}.localhost:8000/vendor/login"