feat: trim platform modules, rename platforms, fix seed output
- Rename platforms: Orion OMS → OMS, Orion → Wizard, Loyalty+ → Loyalty - Per-platform module assignment: core modules always enabled, optional modules selectively enabled per platform instead of enabling all 18 - Rename demo store Orion → WizaTech to avoid confusion with app name - Fix false "already exist" warnings for customers/products in seed (broken post-flush id detection replaced with simple counter) - Make dev port use API_PORT from .env instead of hardcoded 9999 - Add platforms section with dev URLs to init-prod summary - Add merchant panel and customer login URLs to seed next steps - Merge alembic heads (z_store_domain_platform_id + tenancy_001) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2
Makefile
2
Makefile
@@ -44,7 +44,7 @@ setup: install-all migrate-up init-prod
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
dev:
|
dev:
|
||||||
$(PYTHON) -m uvicorn main:app --reload --host 0.0.0.0 --port 9999
|
$(PYTHON) -m uvicorn main:app --reload --host 0.0.0.0 --port $(or $(API_PORT),8000)
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# DATABASE MIGRATIONS
|
# DATABASE MIGRATIONS
|
||||||
|
|||||||
26
alembic/versions/a44f4956cfb1_merge_heads.py
Normal file
26
alembic/versions/a44f4956cfb1_merge_heads.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
"""merge heads
|
||||||
|
|
||||||
|
Revision ID: a44f4956cfb1
|
||||||
|
Revises: z_store_domain_platform_id, tenancy_001
|
||||||
|
Create Date: 2026-02-17 16:10:36.287976
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = 'a44f4956cfb1'
|
||||||
|
down_revision: Union[str, None] = ('z_store_domain_platform_id', 'tenancy_001')
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
pass
|
||||||
@@ -176,7 +176,7 @@ def create_default_platforms(db: Session) -> list[Platform]:
|
|||||||
platform_defs = [
|
platform_defs = [
|
||||||
{
|
{
|
||||||
"code": "oms",
|
"code": "oms",
|
||||||
"name": "Orion OMS",
|
"name": "OMS",
|
||||||
"description": "Order Management System for multi-store e-commerce",
|
"description": "Order Management System for multi-store e-commerce",
|
||||||
"domain": "omsflow.lu",
|
"domain": "omsflow.lu",
|
||||||
"path_prefix": "oms",
|
"path_prefix": "oms",
|
||||||
@@ -187,7 +187,7 @@ def create_default_platforms(db: Session) -> list[Platform]:
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "main",
|
"code": "main",
|
||||||
"name": "Orion",
|
"name": "Wizard",
|
||||||
"description": "Main marketing site showcasing all Orion platforms",
|
"description": "Main marketing site showcasing all Orion platforms",
|
||||||
"domain": "wizard.lu",
|
"domain": "wizard.lu",
|
||||||
"path_prefix": None,
|
"path_prefix": None,
|
||||||
@@ -198,7 +198,7 @@ def create_default_platforms(db: Session) -> list[Platform]:
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "loyalty",
|
"code": "loyalty",
|
||||||
"name": "Loyalty+",
|
"name": "Loyalty",
|
||||||
"description": "Customer loyalty program platform for Luxembourg businesses",
|
"description": "Customer loyalty program platform for Luxembourg businesses",
|
||||||
"domain": "rewardflow.lu",
|
"domain": "rewardflow.lu",
|
||||||
"path_prefix": "loyalty",
|
"path_prefix": "loyalty",
|
||||||
@@ -488,17 +488,26 @@ def create_subscription_tiers(db: Session, platform: Platform) -> int:
|
|||||||
def create_platform_modules(db: Session, platforms: list[Platform]) -> int:
|
def create_platform_modules(db: Session, platforms: list[Platform]) -> int:
|
||||||
"""Create PlatformModule records for all platforms.
|
"""Create PlatformModule records for all platforms.
|
||||||
|
|
||||||
Enables all discovered modules for each platform so the app works
|
Core modules are enabled for every platform. Optional modules are
|
||||||
out of the box. Admins can disable optional modules later via the API.
|
selectively enabled per platform. All other modules are created but
|
||||||
|
disabled (available to toggle on later via the admin API).
|
||||||
"""
|
"""
|
||||||
from app.modules.registry import MODULES
|
from app.modules.registry import MODULES, is_core_module
|
||||||
|
|
||||||
|
# Optional modules enabled per platform (core modules always enabled)
|
||||||
|
PLATFORM_MODULES = {
|
||||||
|
"oms": ["inventory", "catalog", "orders", "marketplace", "analytics", "cart", "checkout"],
|
||||||
|
"main": ["analytics", "monitoring", "dev-tools"],
|
||||||
|
"loyalty": ["loyalty"],
|
||||||
|
}
|
||||||
|
|
||||||
now = datetime.now(UTC)
|
now = datetime.now(UTC)
|
||||||
records_created = 0
|
records_created = 0
|
||||||
|
|
||||||
for platform in platforms:
|
for platform in platforms:
|
||||||
|
enabled_extras = set(PLATFORM_MODULES.get(platform.code, []))
|
||||||
|
|
||||||
for code in MODULES:
|
for code in MODULES:
|
||||||
# Check if record already exists
|
|
||||||
existing = db.execute(
|
existing = db.execute(
|
||||||
select(PlatformModule).where(
|
select(PlatformModule).where(
|
||||||
PlatformModule.platform_id == platform.id,
|
PlatformModule.platform_id == platform.id,
|
||||||
@@ -509,11 +518,12 @@ def create_platform_modules(db: Session, platforms: list[Platform]) -> int:
|
|||||||
if existing:
|
if existing:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
enabled = is_core_module(code) or code in enabled_extras
|
||||||
pm = PlatformModule(
|
pm = PlatformModule(
|
||||||
platform_id=platform.id,
|
platform_id=platform.id,
|
||||||
module_code=code,
|
module_code=code,
|
||||||
is_enabled=True,
|
is_enabled=enabled,
|
||||||
enabled_at=now,
|
enabled_at=now if enabled else None,
|
||||||
config={},
|
config={},
|
||||||
)
|
)
|
||||||
db.add(pm) # noqa: PERF006
|
db.add(pm) # noqa: PERF006
|
||||||
@@ -649,16 +659,38 @@ def print_summary(db: Session):
|
|||||||
print(f" Admin settings: {setting_count}")
|
print(f" Admin settings: {setting_count}")
|
||||||
print(f" Sub. tiers: {tier_count}")
|
print(f" Sub. tiers: {tier_count}")
|
||||||
|
|
||||||
|
# Show platforms
|
||||||
|
platforms = db.query(Platform).order_by(Platform.code).all()
|
||||||
|
enabled_counts = {}
|
||||||
|
for pm in db.query(PlatformModule).filter(PlatformModule.is_enabled.is_(True)).all():
|
||||||
|
enabled_counts[pm.platform_id] = enabled_counts.get(pm.platform_id, 0) + 1
|
||||||
|
|
||||||
|
port = settings.api_port
|
||||||
|
print("\n" + "─" * 70)
|
||||||
|
print("🌐 PLATFORMS")
|
||||||
|
print("─" * 70)
|
||||||
|
for p in platforms:
|
||||||
|
n_enabled = enabled_counts.get(p.id, 0)
|
||||||
|
if p.code == "main":
|
||||||
|
dev_url = f"http://localhost:{port}/"
|
||||||
|
else:
|
||||||
|
dev_url = f"http://localhost:{port}/platforms/{p.code}/"
|
||||||
|
print(f" {p.name} ({p.code})")
|
||||||
|
print(f" Domain: {p.domain}")
|
||||||
|
print(f" Dev URL: {dev_url}")
|
||||||
|
print(f" Modules: {n_enabled} enabled")
|
||||||
|
|
||||||
print("\n" + "─" * 70)
|
print("\n" + "─" * 70)
|
||||||
print("🔐 ADMIN CREDENTIALS")
|
print("🔐 ADMIN CREDENTIALS")
|
||||||
print("─" * 70)
|
print("─" * 70)
|
||||||
|
admin_url = f"http://localhost:{port}/admin/login"
|
||||||
print(" Super Admin (all platforms):")
|
print(" Super Admin (all platforms):")
|
||||||
print(" URL: /admin/login")
|
print(f" URL: {admin_url}")
|
||||||
print(f" Username: {settings.admin_username}")
|
print(f" Username: {settings.admin_username}")
|
||||||
print(f" Password: {settings.admin_password}") # noqa: SEC021
|
print(f" Password: {settings.admin_password}") # noqa: SEC021
|
||||||
print()
|
print()
|
||||||
print(" Loyalty Platform Admin (loyalty only):")
|
print(" Loyalty Platform Admin (loyalty only):")
|
||||||
print(" URL: /admin/login")
|
print(f" URL: {admin_url}")
|
||||||
print(" Username: loyalty_admin")
|
print(" Username: loyalty_admin")
|
||||||
print(" Password: admin123")
|
print(" Password: admin123")
|
||||||
print("─" * 70)
|
print("─" * 70)
|
||||||
@@ -677,17 +709,16 @@ def print_summary(db: Session):
|
|||||||
print(" Change them in production via .env file")
|
print(" Change them in production via .env file")
|
||||||
|
|
||||||
print("\n🚀 NEXT STEPS:")
|
print("\n🚀 NEXT STEPS:")
|
||||||
print(" 1. Login to admin panel")
|
|
||||||
if is_production():
|
if is_production():
|
||||||
|
print(f" 1. Login to admin panel: {admin_url}")
|
||||||
print(" 2. CHANGE DEFAULT PASSWORD IMMEDIATELY!") # noqa: SEC021
|
print(" 2. CHANGE DEFAULT PASSWORD IMMEDIATELY!") # noqa: SEC021
|
||||||
print(" 3. Configure admin settings")
|
print(" 3. Configure admin settings")
|
||||||
print(" 4. Create first store")
|
print(" 4. Create first store")
|
||||||
else:
|
else:
|
||||||
print(" 2. Create demo data: make seed-demo")
|
print(" 1. Start development: make dev")
|
||||||
print(" 3. Start development: make dev")
|
print(f" 2. Admin panel: {admin_url}")
|
||||||
|
print(f" 3. Merchant panel: http://localhost:{port}/merchants/login")
|
||||||
print("\n📝 FOR DEMO DATA (Development only):")
|
print(" 4. Create demo data: make seed-demo")
|
||||||
print(" make seed-demo")
|
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
@@ -144,12 +144,12 @@ DEMO_COMPANIES = [
|
|||||||
DEMO_STORES = [
|
DEMO_STORES = [
|
||||||
{
|
{
|
||||||
"merchant_index": 0, # WizaCorp
|
"merchant_index": 0, # WizaCorp
|
||||||
"store_code": "ORION",
|
"store_code": "WIZATECH",
|
||||||
"name": "Orion",
|
"name": "WizaTech",
|
||||||
"subdomain": "orion",
|
"subdomain": "wizatech",
|
||||||
"description": "Premium electronics and gadgets marketplace",
|
"description": "Premium electronics and gadgets marketplace",
|
||||||
"theme_preset": "modern",
|
"theme_preset": "modern",
|
||||||
"custom_domain": "orion.shop",
|
"custom_domain": "wizatech.shop",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"merchant_index": 0, # WizaCorp
|
"merchant_index": 0, # WizaCorp
|
||||||
@@ -216,7 +216,7 @@ DEMO_TEAM_MEMBERS = [
|
|||||||
"password": "password123", # noqa: SEC001
|
"password": "password123", # noqa: SEC001
|
||||||
"first_name": "Alice",
|
"first_name": "Alice",
|
||||||
"last_name": "Manager",
|
"last_name": "Manager",
|
||||||
"store_codes": ["ORION", "WIZAGADGETS"], # manages two stores
|
"store_codes": ["WIZATECH", "WIZAGADGETS"], # manages two stores
|
||||||
"user_type": "member",
|
"user_type": "member",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -287,20 +287,20 @@ THEME_PRESETS = {
|
|||||||
# Store content page overrides (demonstrates CMS store override feature)
|
# Store content page overrides (demonstrates CMS store override feature)
|
||||||
# Each store can override platform default pages with custom content
|
# Each store can override platform default pages with custom content
|
||||||
STORE_CONTENT_PAGES = {
|
STORE_CONTENT_PAGES = {
|
||||||
"ORION": [
|
"WIZATECH": [
|
||||||
{
|
{
|
||||||
"slug": "about",
|
"slug": "about",
|
||||||
"title": "About Orion",
|
"title": "About WizaTech",
|
||||||
"content": """
|
"content": """
|
||||||
<div class="prose-content">
|
<div class="prose-content">
|
||||||
<h2>Welcome to Orion</h2>
|
<h2>Welcome to WizaTech</h2>
|
||||||
<p>Your premier destination for cutting-edge electronics and innovative gadgets.</p>
|
<p>Your premier destination for cutting-edge electronics and innovative gadgets.</p>
|
||||||
|
|
||||||
<h3>Our Story</h3>
|
<h3>Our Story</h3>
|
||||||
<p>Founded by tech enthusiasts, Orion has been bringing the latest technology to customers since 2020.
|
<p>Founded by tech enthusiasts, WizaTech has been bringing the latest technology to customers since 2020.
|
||||||
We carefully curate our selection to ensure you get only the best products at competitive prices.</p>
|
We carefully curate our selection to ensure you get only the best products at competitive prices.</p>
|
||||||
|
|
||||||
<h3>Why Choose Orion?</h3>
|
<h3>Why Choose WizaTech?</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Expert Selection:</strong> Our team tests and reviews every product</li>
|
<li><strong>Expert Selection:</strong> Our team tests and reviews every product</li>
|
||||||
<li><strong>Best Prices:</strong> We negotiate directly with manufacturers</li>
|
<li><strong>Best Prices:</strong> We negotiate directly with manufacturers</li>
|
||||||
@@ -312,20 +312,20 @@ STORE_CONTENT_PAGES = {
|
|||||||
<p>123 Tech Street, Luxembourg City<br>Open Monday-Saturday, 9am-7pm</p>
|
<p>123 Tech Street, Luxembourg City<br>Open Monday-Saturday, 9am-7pm</p>
|
||||||
</div>
|
</div>
|
||||||
""",
|
""",
|
||||||
"meta_description": "Orion - Your trusted source for premium electronics and gadgets in Luxembourg",
|
"meta_description": "WizaTech - Your trusted source for premium electronics and gadgets in Luxembourg",
|
||||||
"show_in_header": True,
|
"show_in_header": True,
|
||||||
"show_in_footer": True,
|
"show_in_footer": True,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"slug": "contact",
|
"slug": "contact",
|
||||||
"title": "Contact Orion",
|
"title": "Contact WizaTech",
|
||||||
"content": """
|
"content": """
|
||||||
<div class="prose-content">
|
<div class="prose-content">
|
||||||
<h2>Get in Touch with Orion</h2>
|
<h2>Get in Touch with WizaTech</h2>
|
||||||
|
|
||||||
<h3>Customer Support</h3>
|
<h3>Customer Support</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Email:</strong> support@orion.lu</li>
|
<li><strong>Email:</strong> support@wizatech.lu</li>
|
||||||
<li><strong>Phone:</strong> +352 123 456 789</li>
|
<li><strong>Phone:</strong> +352 123 456 789</li>
|
||||||
<li><strong>WhatsApp:</strong> +352 123 456 789</li>
|
<li><strong>WhatsApp:</strong> +352 123 456 789</li>
|
||||||
<li><strong>Hours:</strong> Monday-Friday, 9am-6pm CET</li>
|
<li><strong>Hours:</strong> Monday-Friday, 9am-6pm CET</li>
|
||||||
@@ -334,7 +334,7 @@ STORE_CONTENT_PAGES = {
|
|||||||
<h3>Technical Support</h3>
|
<h3>Technical Support</h3>
|
||||||
<p>Need help with your gadgets? Our tech experts are here to help!</p>
|
<p>Need help with your gadgets? Our tech experts are here to help!</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Email:</strong> tech@orion.lu</li>
|
<li><strong>Email:</strong> tech@wizatech.lu</li>
|
||||||
<li><strong>Live Chat:</strong> Available on our website</li>
|
<li><strong>Live Chat:</strong> Available on our website</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
@@ -342,7 +342,7 @@ STORE_CONTENT_PAGES = {
|
|||||||
<p>123 Tech Street<br>Luxembourg City, L-1234<br>Luxembourg</p>
|
<p>123 Tech Street<br>Luxembourg City, L-1234<br>Luxembourg</p>
|
||||||
</div>
|
</div>
|
||||||
""",
|
""",
|
||||||
"meta_description": "Contact Orion customer support for electronics and gadget inquiries",
|
"meta_description": "Contact WizaTech customer support for electronics and gadget inquiries",
|
||||||
"show_in_header": True,
|
"show_in_header": True,
|
||||||
"show_in_footer": True,
|
"show_in_footer": True,
|
||||||
},
|
},
|
||||||
@@ -837,6 +837,7 @@ def create_demo_customers(
|
|||||||
"""Create demo customers for a store."""
|
"""Create demo customers for a store."""
|
||||||
|
|
||||||
customers = []
|
customers = []
|
||||||
|
new_count = 0
|
||||||
# Use a simple demo password for all customers
|
# Use a simple demo password for all customers
|
||||||
demo_password = "customer123" # noqa: SEC001
|
demo_password = "customer123" # noqa: SEC001
|
||||||
|
|
||||||
@@ -869,10 +870,10 @@ def create_demo_customers(
|
|||||||
)
|
)
|
||||||
db.add(customer) # noqa: PERF006
|
db.add(customer) # noqa: PERF006
|
||||||
customers.append(customer)
|
customers.append(customer)
|
||||||
|
new_count += 1
|
||||||
|
|
||||||
db.flush()
|
db.flush()
|
||||||
|
|
||||||
new_count = len([c for c in customers if c.id is None or db.is_modified(c)])
|
|
||||||
if new_count > 0:
|
if new_count > 0:
|
||||||
print_success(f"Created {new_count} customers for {store.name}")
|
print_success(f"Created {new_count} customers for {store.name}")
|
||||||
else:
|
else:
|
||||||
@@ -885,6 +886,7 @@ def create_demo_products(db: Session, store: Store, count: int) -> list[Product]
|
|||||||
"""Create demo products for a store."""
|
"""Create demo products for a store."""
|
||||||
|
|
||||||
products = []
|
products = []
|
||||||
|
new_count = 0
|
||||||
|
|
||||||
for i in range(1, count + 1):
|
for i in range(1, count + 1):
|
||||||
marketplace_product_id = f"{store.store_code}-MP-{i:04d}"
|
marketplace_product_id = f"{store.store_code}-MP-{i:04d}"
|
||||||
@@ -922,7 +924,7 @@ def create_demo_products(db: Session, store: Store, count: int) -> list[Product]
|
|||||||
availability="in stock",
|
availability="in stock",
|
||||||
condition="new",
|
condition="new",
|
||||||
google_product_category="Electronics > Computers > Laptops",
|
google_product_category="Electronics > Computers > Laptops",
|
||||||
marketplace="Orion",
|
marketplace="OMS",
|
||||||
store_name=store.name,
|
store_name=store.name,
|
||||||
currency="EUR",
|
currency="EUR",
|
||||||
created_at=datetime.now(UTC),
|
created_at=datetime.now(UTC),
|
||||||
@@ -964,10 +966,10 @@ def create_demo_products(db: Session, store: Store, count: int) -> list[Product]
|
|||||||
)
|
)
|
||||||
db.add(product) # noqa: PERF006
|
db.add(product) # noqa: PERF006
|
||||||
products.append(product)
|
products.append(product)
|
||||||
|
new_count += 1
|
||||||
|
|
||||||
db.flush()
|
db.flush()
|
||||||
|
|
||||||
new_count = len([p for p in products if p.id is None or db.is_modified(p)])
|
|
||||||
if new_count > 0:
|
if new_count > 0:
|
||||||
print_success(f"Created {new_count} products for {store.name}")
|
print_success(f"Created {new_count} products for {store.name}")
|
||||||
else:
|
else:
|
||||||
@@ -1205,7 +1207,7 @@ def print_summary(db: Session):
|
|||||||
print(" All customers:")
|
print(" All customers:")
|
||||||
print(" Email: customer1@{subdomain}.example.com")
|
print(" Email: customer1@{subdomain}.example.com")
|
||||||
print(" Password: customer123") # noqa: SEC021
|
print(" Password: customer123") # noqa: SEC021
|
||||||
print(" (Replace {subdomain} with store subdomain, e.g., orion)")
|
print(" (Replace {subdomain} with store subdomain, e.g., wizatech)")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
print("\n🏪 Shop Access (Development):")
|
print("\n🏪 Shop Access (Development):")
|
||||||
@@ -1220,15 +1222,14 @@ def print_summary(db: Session):
|
|||||||
|
|
||||||
print("⚠️ ALL DEMO CREDENTIALS ARE INSECURE - For development only!")
|
print("⚠️ ALL DEMO CREDENTIALS ARE INSECURE - For development only!")
|
||||||
|
|
||||||
|
port = settings.api_port
|
||||||
print("\n🚀 NEXT STEPS:")
|
print("\n🚀 NEXT STEPS:")
|
||||||
print(" 1. Start development: make dev")
|
print(" 1. Start development: make dev")
|
||||||
print(" 2. Login as store:")
|
print(f" 2. Admin panel: http://localhost:{port}/admin/login")
|
||||||
print(" • Path-based: http://localhost:8000/store/ORION/login")
|
print(f" 3. Merchant panel: http://localhost:{port}/merchants/login")
|
||||||
print(" • Subdomain: http://orion.localhost:8000/store/login") # noqa: SEC034
|
print(f" 4. Store panel: http://localhost:{port}/store/WIZATECH/login")
|
||||||
print(" 3. Visit store shop: http://localhost:8000/stores/ORION/shop/")
|
print(f" 5. Storefront: http://localhost:{port}/stores/WIZATECH/shop/")
|
||||||
print(" 4. Admin panel: http://localhost:8000/admin/login")
|
print(f" 6. Customer login: http://localhost:{port}/stores/WIZATECH/shop/account")
|
||||||
print(f" Username: {settings.admin_username}")
|
|
||||||
print(f" Password: {settings.admin_password}") # noqa: SEC021
|
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user