feat: add Phase 2 migration, make urls command, fix seed script
- Create loyalty_003 migration: company-based architecture (adds company_id to all loyalty tables, creates company_loyalty_settings, renames vendor_id to enrolled_at_vendor_id on cards) - Move platform migration back to alembic/versions (not loyalty-specific) - Add version_locations to alembic.ini for module migration discovery - Add make urls/urls-dev/urls-prod commands (scripts/show_urls.py) - Fix seed_demo.py: import all module models to resolve SQLAlchemy string relationships, fix multiple admin query, set platform_id on vendor content pages - Fix loyalty test fixtures to match Phase 2 model columns Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -51,21 +51,37 @@ from app.core.config import settings
|
||||
from app.core.database import SessionLocal
|
||||
from app.core.environment import get_environment, is_production
|
||||
from middleware.auth import AuthManager
|
||||
from app.modules.cms.models import ContentPage
|
||||
from app.modules.tenancy.models import PlatformAlert
|
||||
from app.modules.tenancy.models import Company
|
||||
# =============================================================================
|
||||
# MODEL IMPORTS
|
||||
# =============================================================================
|
||||
# ALL models must be imported before any ORM query so SQLAlchemy can resolve
|
||||
# cross-module string relationships (e.g. Vendor→VendorEmailTemplate,
|
||||
# Platform→SubscriptionTier, Product→Inventory).
|
||||
|
||||
# Core modules
|
||||
from app.modules.tenancy.models import Company, PlatformAlert, User, Role, Vendor, VendorUser, VendorDomain
|
||||
from app.modules.cms.models import ContentPage, VendorTheme
|
||||
from app.modules.catalog.models import Product
|
||||
from app.modules.customers.models.customer import Customer, CustomerAddress
|
||||
from app.modules.orders.models import Order, OrderItem
|
||||
from app.modules.marketplace.models import (
|
||||
MarketplaceImportJob,
|
||||
MarketplaceProduct,
|
||||
MarketplaceProductTranslation,
|
||||
)
|
||||
from app.modules.orders.models import Order, OrderItem
|
||||
from app.modules.catalog.models import Product
|
||||
from app.modules.tenancy.models import User
|
||||
from app.modules.tenancy.models import Role, Vendor, VendorUser
|
||||
from app.modules.tenancy.models import VendorDomain
|
||||
from app.modules.cms.models import VendorTheme
|
||||
|
||||
# Optional modules — import to register models with SQLAlchemy
|
||||
for _mod in [
|
||||
"app.modules.inventory.models",
|
||||
"app.modules.cart.models",
|
||||
"app.modules.billing.models",
|
||||
"app.modules.messaging.models",
|
||||
"app.modules.loyalty.models",
|
||||
]:
|
||||
try:
|
||||
__import__(_mod)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
SEED_MODE = os.getenv("SEED_MODE", "normal") # normal, minimal, reset
|
||||
FORCE_RESET = os.getenv("FORCE_RESET", "false").lower() in ("true", "1", "yes")
|
||||
@@ -394,7 +410,7 @@ def check_environment():
|
||||
def check_admin_exists(db: Session) -> bool:
|
||||
"""Check if admin user exists."""
|
||||
|
||||
admin = db.execute(select(User).where(User.role == "admin")).scalar_one_or_none()
|
||||
admin = db.execute(select(User).where(User.role == "admin").limit(1)).scalar_one_or_none()
|
||||
|
||||
if not admin:
|
||||
print_error("No admin user found!")
|
||||
@@ -799,6 +815,14 @@ def create_demo_vendor_content_pages(db: Session, vendors: list[Vendor]) -> int:
|
||||
"""
|
||||
created_count = 0
|
||||
|
||||
# Get the OMS platform ID (vendors are registered on OMS)
|
||||
from app.modules.tenancy.models import Platform
|
||||
|
||||
oms_platform = db.execute(
|
||||
select(Platform).where(Platform.code == "oms")
|
||||
).scalar_one_or_none()
|
||||
default_platform_id = oms_platform.id if oms_platform else 1
|
||||
|
||||
for vendor in vendors:
|
||||
vendor_pages = VENDOR_CONTENT_PAGES.get(vendor.vendor_code, [])
|
||||
|
||||
@@ -819,6 +843,7 @@ def create_demo_vendor_content_pages(db: Session, vendors: list[Vendor]) -> int:
|
||||
|
||||
# Create vendor content page override
|
||||
page = ContentPage(
|
||||
platform_id=default_platform_id,
|
||||
vendor_id=vendor.id,
|
||||
slug=page_data["slug"],
|
||||
title=page_data["title"],
|
||||
|
||||
247
scripts/show_urls.py
Normal file
247
scripts/show_urls.py
Normal file
@@ -0,0 +1,247 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Show all platform, admin, vendor, and storefront URLs.
|
||||
|
||||
Queries the database for platforms, vendors, and custom domains,
|
||||
then prints all accessible URLs for both development and production.
|
||||
|
||||
Usage:
|
||||
python scripts/show_urls.py # Show all URLs
|
||||
python scripts/show_urls.py --dev # Development URLs only
|
||||
python scripts/show_urls.py --prod # Production URLs only
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
from sqlalchemy import text
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.database import SessionLocal
|
||||
|
||||
|
||||
DEV_BASE = "http://localhost:9999"
|
||||
SEPARATOR = "─" * 72
|
||||
|
||||
|
||||
def get_platforms(db):
|
||||
"""Get all platforms."""
|
||||
return db.execute(
|
||||
text(
|
||||
"SELECT id, code, name, domain, path_prefix, is_active "
|
||||
"FROM platforms ORDER BY code"
|
||||
)
|
||||
).fetchall()
|
||||
|
||||
|
||||
def get_vendors(db):
|
||||
"""Get all vendors with company info."""
|
||||
return db.execute(
|
||||
text(
|
||||
"SELECT v.id, v.vendor_code, v.name, v.subdomain, v.is_active, "
|
||||
" c.name AS company_name "
|
||||
"FROM vendors v "
|
||||
"LEFT JOIN companies c ON c.id = v.company_id "
|
||||
"ORDER BY c.name, v.name"
|
||||
)
|
||||
).fetchall()
|
||||
|
||||
|
||||
def get_vendor_domains(db):
|
||||
"""Get all custom vendor domains."""
|
||||
return db.execute(
|
||||
text(
|
||||
"SELECT vd.vendor_id, vd.domain, vd.is_primary, vd.is_active, "
|
||||
" vd.is_verified, v.vendor_code "
|
||||
"FROM vendor_domains vd "
|
||||
"JOIN vendors v ON v.id = vd.vendor_id "
|
||||
"ORDER BY vd.vendor_id, vd.is_primary DESC"
|
||||
)
|
||||
).fetchall()
|
||||
|
||||
|
||||
def status_badge(is_active):
|
||||
return "active" if is_active else "INACTIVE"
|
||||
|
||||
|
||||
def print_dev_urls(platforms, vendors, vendor_domains):
|
||||
"""Print all development URLs."""
|
||||
print()
|
||||
print("DEVELOPMENT URLS")
|
||||
print(f"Base: {DEV_BASE}")
|
||||
print(SEPARATOR)
|
||||
|
||||
# Admin
|
||||
print()
|
||||
print(" ADMIN PANEL")
|
||||
print(f" Login: {DEV_BASE}/admin/login")
|
||||
print(f" Dashboard: {DEV_BASE}/admin/")
|
||||
print(f" API: {DEV_BASE}/api/v1/admin/")
|
||||
print(f" API Docs: {DEV_BASE}/docs")
|
||||
|
||||
# Platforms
|
||||
print()
|
||||
print(" PLATFORMS")
|
||||
for p in platforms:
|
||||
tag = f" [{status_badge(p.is_active)}]" if not p.is_active else ""
|
||||
prefix = p.path_prefix or ""
|
||||
if p.code == "main":
|
||||
print(f" {p.name}{tag}")
|
||||
print(f" Home: {DEV_BASE}/")
|
||||
else:
|
||||
print(f" {p.name} ({p.code}){tag}")
|
||||
if prefix:
|
||||
print(f" Home: {DEV_BASE}/platforms/{p.code}/")
|
||||
else:
|
||||
print(f" Home: {DEV_BASE}/platforms/{p.code}/")
|
||||
|
||||
# Vendors
|
||||
print()
|
||||
print(" VENDOR DASHBOARDS")
|
||||
domains_by_vendor = {}
|
||||
for vd in vendor_domains:
|
||||
domains_by_vendor.setdefault(vd.vendor_id, []).append(vd)
|
||||
|
||||
current_company = None
|
||||
for v in vendors:
|
||||
if v.company_name != current_company:
|
||||
current_company = v.company_name
|
||||
print(f" [{current_company or 'No Company'}]")
|
||||
|
||||
tag = f" [{status_badge(v.is_active)}]" if not v.is_active else ""
|
||||
code = v.vendor_code
|
||||
print(f" {v.name} ({code}){tag}")
|
||||
print(f" Dashboard: {DEV_BASE}/vendor/{code}/")
|
||||
print(f" API: {DEV_BASE}/api/v1/vendor/{code}/")
|
||||
|
||||
# Storefronts
|
||||
print()
|
||||
print(" STOREFRONTS")
|
||||
current_company = None
|
||||
for v in vendors:
|
||||
if v.company_name != current_company:
|
||||
current_company = v.company_name
|
||||
print(f" [{current_company or 'No Company'}]")
|
||||
|
||||
tag = f" [{status_badge(v.is_active)}]" if not v.is_active else ""
|
||||
code = v.vendor_code
|
||||
print(f" {v.name} ({code}){tag}")
|
||||
print(f" Shop: {DEV_BASE}/vendors/{code}/storefront/")
|
||||
print(f" API: {DEV_BASE}/api/v1/storefront/{code}/")
|
||||
|
||||
|
||||
def print_prod_urls(platforms, vendors, vendor_domains):
|
||||
"""Print all production URLs."""
|
||||
platform_domain = settings.platform_domain
|
||||
|
||||
print()
|
||||
print("PRODUCTION URLS")
|
||||
print(f"Platform domain: {platform_domain}")
|
||||
print(SEPARATOR)
|
||||
|
||||
# Admin
|
||||
print()
|
||||
print(" ADMIN PANEL")
|
||||
print(f" Login: https://admin.{platform_domain}/admin/login")
|
||||
print(f" Dashboard: https://admin.{platform_domain}/admin/")
|
||||
print(f" API: https://admin.{platform_domain}/api/v1/admin/")
|
||||
|
||||
# Platforms
|
||||
print()
|
||||
print(" PLATFORMS")
|
||||
for p in platforms:
|
||||
tag = f" [{status_badge(p.is_active)}]" if not p.is_active else ""
|
||||
if p.domain:
|
||||
print(f" {p.name} ({p.code}){tag}")
|
||||
print(f" Home: https://{p.domain}/")
|
||||
elif p.code == "main":
|
||||
print(f" {p.name}{tag}")
|
||||
print(f" Home: https://{platform_domain}/")
|
||||
else:
|
||||
print(f" {p.name} ({p.code}){tag}")
|
||||
print(f" Home: https://{p.code}.{platform_domain}/")
|
||||
|
||||
# Group domains by vendor
|
||||
domains_by_vendor = {}
|
||||
for vd in vendor_domains:
|
||||
domains_by_vendor.setdefault(vd.vendor_id, []).append(vd)
|
||||
|
||||
# Vendors
|
||||
print()
|
||||
print(" VENDOR DASHBOARDS")
|
||||
current_company = None
|
||||
for v in vendors:
|
||||
if v.company_name != current_company:
|
||||
current_company = v.company_name
|
||||
print(f" [{current_company or 'No Company'}]")
|
||||
|
||||
tag = f" [{status_badge(v.is_active)}]" if not v.is_active else ""
|
||||
print(f" {v.name} ({v.vendor_code}){tag}")
|
||||
print(f" Dashboard: https://{v.subdomain}.{platform_domain}/vendor/{v.vendor_code}/")
|
||||
|
||||
# Storefronts
|
||||
print()
|
||||
print(" STOREFRONTS")
|
||||
current_company = None
|
||||
for v in vendors:
|
||||
if v.company_name != current_company:
|
||||
current_company = v.company_name
|
||||
print(f" [{current_company or 'No Company'}]")
|
||||
|
||||
tag = f" [{status_badge(v.is_active)}]" if not v.is_active else ""
|
||||
print(f" {v.name} ({v.vendor_code}){tag}")
|
||||
|
||||
# Subdomain URL
|
||||
print(f" Subdomain: https://{v.subdomain}.{platform_domain}/")
|
||||
|
||||
# Custom domains
|
||||
vd_list = domains_by_vendor.get(v.id, [])
|
||||
for vd in vd_list:
|
||||
d_flags = []
|
||||
if vd.is_primary:
|
||||
d_flags.append("primary")
|
||||
if not vd.is_active:
|
||||
d_flags.append("INACTIVE")
|
||||
if not vd.is_verified:
|
||||
d_flags.append("unverified")
|
||||
suffix = f" ({', '.join(d_flags)})" if d_flags else ""
|
||||
print(f" Custom: https://{vd.domain}/{suffix}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Show all platform URLs")
|
||||
parser.add_argument("--dev", action="store_true", help="Development URLs only")
|
||||
parser.add_argument("--prod", action="store_true", help="Production URLs only")
|
||||
args = parser.parse_args()
|
||||
|
||||
show_dev = args.dev or (not args.dev and not args.prod)
|
||||
show_prod = args.prod or (not args.dev and not args.prod)
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
platforms = get_platforms(db)
|
||||
vendors = get_vendors(db)
|
||||
vendor_domains = get_vendor_domains(db)
|
||||
except Exception as e:
|
||||
print(f"Error querying database: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
print()
|
||||
print("=" * 72)
|
||||
print(" WIZAMART PLATFORM - ALL URLS")
|
||||
print(f" {len(platforms)} platform(s), {len(vendors)} vendor(s), {len(vendor_domains)} custom domain(s)")
|
||||
print("=" * 72)
|
||||
|
||||
if show_dev:
|
||||
print_dev_urls(platforms, vendors, vendor_domains)
|
||||
|
||||
if show_prod:
|
||||
print_prod_urls(platforms, vendors, vendor_domains)
|
||||
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user