Overhaul storefront URL routing to be platform-aware:
- Dev: /platforms/{code}/storefront/{store_code}/
- Prod: subdomain.platform.lu/ (internally rewritten to /storefront/)
- Add subdomain detection in PlatformContextMiddleware
- Add /storefront/ path rewrite for prod mode (subdomain/custom domain)
- Remove all silent platform fallbacks (platform_id=1)
- Add require_platform dependency for clean endpoint validation
- Update route registration, templates, module definitions, base_url calc
- Update StoreContextMiddleware for /storefront/ path detection
- Remove /stores/ from FrontendDetector STOREFRONT_PATH_PREFIXES
Billing service improvements:
- Add store_platform_sync_service to keep store_platforms in sync
- Make tier lookups platform-aware across billing services
- Add tiers for all platforms in seed data
- Add demo subscriptions to seed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
416 lines
13 KiB
Python
416 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Show all platform, admin, store, and storefront URLs.
|
|
|
|
Queries the database for platforms, stores, 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
|
|
python scripts/show_urls.py --check # Check dev URLs with curl
|
|
"""
|
|
|
|
import argparse
|
|
import subprocess
|
|
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_stores(db):
|
|
"""Get all stores with merchant info."""
|
|
return db.execute(
|
|
text(
|
|
"SELECT v.id, v.store_code, v.name, v.subdomain, v.is_active, "
|
|
" c.name AS merchant_name "
|
|
"FROM stores v "
|
|
"LEFT JOIN merchants c ON c.id = v.merchant_id "
|
|
"ORDER BY c.name, v.name"
|
|
)
|
|
).fetchall()
|
|
|
|
|
|
def get_store_domains(db):
|
|
"""Get all custom store domains."""
|
|
return db.execute(
|
|
text(
|
|
"SELECT vd.store_id, vd.domain, vd.is_primary, vd.is_active, "
|
|
" vd.is_verified, v.store_code "
|
|
"FROM store_domains vd "
|
|
"JOIN stores v ON v.id = vd.store_id "
|
|
"ORDER BY vd.store_id, vd.is_primary DESC"
|
|
)
|
|
).fetchall()
|
|
|
|
|
|
def get_store_platform_map(db):
|
|
"""
|
|
Get store-to-platform mapping.
|
|
|
|
Returns dict: store_id -> list of platform codes.
|
|
Uses store_platforms junction table.
|
|
"""
|
|
rows = db.execute(
|
|
text(
|
|
"SELECT sp.store_id, p.code AS platform_code "
|
|
"FROM store_platforms sp "
|
|
"JOIN platforms p ON p.id = sp.platform_id "
|
|
"WHERE sp.is_active = true "
|
|
"ORDER BY sp.store_id, sp.is_primary DESC"
|
|
)
|
|
).fetchall()
|
|
|
|
mapping = {}
|
|
for r in rows:
|
|
mapping.setdefault(r.store_id, []).append(r.platform_code)
|
|
return mapping
|
|
|
|
|
|
def status_badge(is_active):
|
|
return "active" if is_active else "INACTIVE"
|
|
|
|
|
|
def _store_dev_dashboard_url(store_code, platform_code=None):
|
|
"""Build store dashboard URL for dev mode."""
|
|
if platform_code and platform_code != "main":
|
|
return f"{DEV_BASE}/platforms/{platform_code}/store/{store_code}/"
|
|
return f"{DEV_BASE}/store/{store_code}/"
|
|
|
|
|
|
def _store_dev_login_url(store_code, platform_code=None):
|
|
"""Build store login URL for dev mode."""
|
|
if platform_code and platform_code != "main":
|
|
return f"{DEV_BASE}/platforms/{platform_code}/store/{store_code}/login"
|
|
return f"{DEV_BASE}/store/{store_code}/login"
|
|
|
|
|
|
def collect_dev_urls(platforms, stores, store_domains, store_platform_map):
|
|
"""
|
|
Collect all dev URLs with labels and expected status codes.
|
|
|
|
Returns list of (label, url, expected_codes) tuples.
|
|
"""
|
|
urls = []
|
|
|
|
# Admin
|
|
urls.append(("Admin Login", f"{DEV_BASE}/admin/login", [200]))
|
|
urls.append(("Admin Dashboard", f"{DEV_BASE}/admin/", [200, 302]))
|
|
urls.append(("API Docs", f"{DEV_BASE}/docs", [200]))
|
|
urls.append(("Health", f"{DEV_BASE}/health", [200]))
|
|
|
|
# Platforms
|
|
for p in platforms:
|
|
if not p.is_active:
|
|
continue
|
|
if p.code == "main":
|
|
urls.append((f"Platform: {p.name}", f"{DEV_BASE}/", [200]))
|
|
else:
|
|
urls.append((f"Platform: {p.name}", f"{DEV_BASE}/platforms/{p.code}/", [200]))
|
|
|
|
# Store dashboards (redirect to login when unauthenticated)
|
|
for v in stores:
|
|
if not v.is_active:
|
|
continue
|
|
platform_codes = store_platform_map.get(v.id, [])
|
|
if platform_codes:
|
|
for pc in platform_codes:
|
|
url = _store_dev_login_url(v.store_code, pc)
|
|
urls.append((f"Store Login: {v.name} ({pc})", url, [200]))
|
|
else:
|
|
url = _store_dev_login_url(v.store_code)
|
|
urls.append((f"Store Login: {v.name}", url, [200]))
|
|
|
|
# Storefronts (platform-aware)
|
|
for v in stores:
|
|
if not v.is_active:
|
|
continue
|
|
platform_codes = store_platform_map.get(v.id, [])
|
|
for pc in platform_codes:
|
|
urls.append((
|
|
f"Storefront [{pc}]: {v.name}",
|
|
f"{DEV_BASE}/platforms/{pc}/storefront/{v.store_code}/",
|
|
[200, 302],
|
|
))
|
|
|
|
# Store info API (public, no auth needed)
|
|
for v in stores:
|
|
if not v.is_active:
|
|
continue
|
|
urls.append((
|
|
f"Store API: {v.name}",
|
|
f"{DEV_BASE}/api/v1/store/info/{v.store_code}",
|
|
[200],
|
|
))
|
|
|
|
return urls
|
|
|
|
|
|
def print_dev_urls(platforms, stores, store_domains, store_platform_map):
|
|
"""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 ""
|
|
if p.code == "main":
|
|
print(f" {p.name}{tag}")
|
|
print(f" Home: {DEV_BASE}/")
|
|
else:
|
|
print(f" {p.name} ({p.code}){tag}")
|
|
print(f" Home: {DEV_BASE}/platforms/{p.code}/")
|
|
|
|
# Stores
|
|
print()
|
|
print(" STORE DASHBOARDS")
|
|
current_merchant = None
|
|
for v in stores:
|
|
if v.merchant_name != current_merchant:
|
|
current_merchant = v.merchant_name
|
|
print(f" [{current_merchant or 'No Merchant'}]")
|
|
|
|
tag = f" [{status_badge(v.is_active)}]" if not v.is_active else ""
|
|
code = v.store_code
|
|
|
|
# Get platform(s) for this store
|
|
platform_codes = store_platform_map.get(v.id, [])
|
|
|
|
print(f" {v.name} ({code}){tag}")
|
|
if platform_codes:
|
|
for pc in platform_codes:
|
|
print(f" Login: {_store_dev_login_url(code, pc)}")
|
|
print(f" Dashboard: {_store_dev_dashboard_url(code, pc)}")
|
|
else:
|
|
print(f" Login: {_store_dev_login_url(code)}")
|
|
print(f" Dashboard: {_store_dev_dashboard_url(code)}")
|
|
print(f" (!) No platform assigned - use /platforms/{{code}}/store/{code}/ for platform context")
|
|
|
|
# Storefronts
|
|
print()
|
|
print(" STOREFRONTS")
|
|
current_merchant = None
|
|
for v in stores:
|
|
if v.merchant_name != current_merchant:
|
|
current_merchant = v.merchant_name
|
|
print(f" [{current_merchant or 'No Merchant'}]")
|
|
|
|
tag = f" [{status_badge(v.is_active)}]" if not v.is_active else ""
|
|
code = v.store_code
|
|
print(f" {v.name} ({code}){tag}")
|
|
pcs = store_platform_map.get(v.id, [])
|
|
for pc in pcs:
|
|
print(f" Shop [{pc}]: {DEV_BASE}/platforms/{pc}/storefront/{code}/")
|
|
print(f" API: {DEV_BASE}/api/v1/storefront/{code}/")
|
|
|
|
|
|
def print_prod_urls(platforms, stores, store_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 store
|
|
domains_by_store = {}
|
|
for vd in store_domains:
|
|
domains_by_store.setdefault(vd.store_id, []).append(vd)
|
|
|
|
# Stores
|
|
print()
|
|
print(" STORE DASHBOARDS")
|
|
current_merchant = None
|
|
for v in stores:
|
|
if v.merchant_name != current_merchant:
|
|
current_merchant = v.merchant_name
|
|
print(f" [{current_merchant or 'No Merchant'}]")
|
|
|
|
tag = f" [{status_badge(v.is_active)}]" if not v.is_active else ""
|
|
print(f" {v.name} ({v.store_code}){tag}")
|
|
print(f" Dashboard: https://{v.subdomain}.{platform_domain}/store/{v.store_code}/")
|
|
|
|
# Storefronts
|
|
print()
|
|
print(" STOREFRONTS")
|
|
current_merchant = None
|
|
for v in stores:
|
|
if v.merchant_name != current_merchant:
|
|
current_merchant = v.merchant_name
|
|
print(f" [{current_merchant or 'No Merchant'}]")
|
|
|
|
tag = f" [{status_badge(v.is_active)}]" if not v.is_active else ""
|
|
print(f" {v.name} ({v.store_code}){tag}")
|
|
|
|
# Subdomain URL
|
|
print(f" Subdomain: https://{v.subdomain}.{platform_domain}/")
|
|
|
|
# Custom domains
|
|
vd_list = domains_by_store.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 check_dev_urls(platforms, stores, store_domains, store_platform_map):
|
|
"""Curl all dev URLs and report reachability."""
|
|
urls = collect_dev_urls(platforms, stores, store_domains, store_platform_map)
|
|
|
|
print()
|
|
print("URL HEALTH CHECK")
|
|
print(f"Base: {DEV_BASE}")
|
|
print(SEPARATOR)
|
|
print()
|
|
|
|
passed = 0
|
|
failed = 0
|
|
errors = []
|
|
|
|
for label, url, expected_codes in urls:
|
|
try:
|
|
result = subprocess.run(
|
|
["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "-L", "--max-time", "5", url],
|
|
capture_output=True, text=True, timeout=10,
|
|
)
|
|
status = int(result.stdout.strip())
|
|
|
|
if status in expected_codes:
|
|
print(f" PASS {status} {label}")
|
|
print(f" {url}")
|
|
passed += 1
|
|
else:
|
|
expected_str = "/".join(str(c) for c in expected_codes)
|
|
print(f" FAIL {status} {label} (expected {expected_str})")
|
|
print(f" {url}")
|
|
failed += 1
|
|
errors.append((label, url, status, expected_codes))
|
|
except (subprocess.TimeoutExpired, FileNotFoundError, ValueError) as e:
|
|
print(f" ERR --- {label} ({e})")
|
|
print(f" {url}")
|
|
failed += 1
|
|
errors.append((label, url, 0, expected_codes))
|
|
|
|
# Summary
|
|
print()
|
|
print(SEPARATOR)
|
|
total = passed + failed
|
|
print(f" Results: {passed}/{total} passed", end="")
|
|
if failed:
|
|
print(f", {failed} failed")
|
|
else:
|
|
print()
|
|
|
|
if errors:
|
|
print()
|
|
print(" Failed URLs:")
|
|
for label, url, status, expected in errors:
|
|
expected_str = "/".join(str(c) for c in expected)
|
|
print(f" [{status or 'ERR'}] {url} (expected {expected_str})")
|
|
|
|
print()
|
|
return failed == 0
|
|
|
|
|
|
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")
|
|
parser.add_argument("--check", action="store_true", help="Check dev URLs with curl")
|
|
args = parser.parse_args()
|
|
|
|
show_dev = args.dev or (not args.dev and not args.prod and not args.check)
|
|
show_prod = args.prod or (not args.dev and not args.prod and not args.check)
|
|
|
|
db = SessionLocal()
|
|
try:
|
|
platforms = get_platforms(db)
|
|
stores = get_stores(db)
|
|
store_domains = get_store_domains(db)
|
|
store_platform_map = get_store_platform_map(db)
|
|
except Exception as e:
|
|
print(f"Error querying database: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
finally:
|
|
db.close()
|
|
|
|
print()
|
|
print("=" * 72)
|
|
print(" ORION PLATFORM - ALL URLS")
|
|
print(f" {len(platforms)} platform(s), {len(stores)} store(s), {len(store_domains)} custom domain(s)")
|
|
print("=" * 72)
|
|
|
|
if args.check:
|
|
success = check_dev_urls(platforms, stores, store_domains, store_platform_map)
|
|
sys.exit(0 if success else 1)
|
|
|
|
if show_dev:
|
|
print_dev_urls(platforms, stores, store_domains, store_platform_map)
|
|
|
|
if show_prod:
|
|
print_prod_urls(platforms, stores, store_domains)
|
|
|
|
print()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|