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

@@ -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:")