feat: implement /platforms/ URL prefix routing strategy
- Update middleware to use /platforms/{code}/ prefix for dev routing
- Change DEFAULT_PLATFORM_CODE from 'oms' to 'main'
- Add 'main' platform for main marketing site (wizamart.lu)
- Remove hardcoded /oms and /loyalty routes from main.py
- Update platform_pages.py homepage to handle vendor landing pages
URL structure:
- localhost:9999/ → Main marketing site ('main' platform)
- localhost:9999/platforms/oms/ → OMS platform
- localhost:9999/platforms/loyalty/ → Loyalty platform
- oms.lu/ → OMS platform (production)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
402
main.py
402
main.py
@@ -373,320 +373,29 @@ async def vendor_root_path(
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# PLATFORM PUBLIC PAGES (Platform Homepage, About, FAQ, etc.)
|
||||
# PLATFORM ROUTING (via PlatformContextMiddleware)
|
||||
#
|
||||
# The PlatformContextMiddleware handles all platform routing by rewriting paths:
|
||||
# - /platforms/oms/ → rewrites to / (served by platform_pages router)
|
||||
# - /platforms/oms/pricing → rewrites to /pricing (served by platform_pages router)
|
||||
# - /platforms/loyalty/ → rewrites to / (served by platform_pages router)
|
||||
#
|
||||
# The middleware also sets request.state.platform so handlers know which
|
||||
# platform's content to serve. All platform page routes are defined in
|
||||
# app/routes/platform_pages.py which is included above.
|
||||
#
|
||||
# URL Structure (Development - localhost:9999):
|
||||
# - / → Main marketing site ('main' platform)
|
||||
# - /about → Main marketing site about page
|
||||
# - /platforms/oms/ → OMS platform homepage
|
||||
# - /platforms/oms/pricing → OMS platform pricing page
|
||||
# - /platforms/loyalty/ → Loyalty platform homepage
|
||||
#
|
||||
# URL Structure (Production - domain-based):
|
||||
# - wizamart.lu/ → Main marketing site
|
||||
# - oms.lu/ → OMS platform homepage
|
||||
# - loyalty.lu/ → Loyalty platform homepage
|
||||
# ============================================================================
|
||||
logger.info("Registering platform public page routes:")
|
||||
logger.info(" - / (platform homepage)")
|
||||
logger.info(" - /{platform_code}/ (platform-prefixed homepage for dev mode)")
|
||||
logger.info(" - /{platform_code}/{slug} (platform-prefixed content pages)")
|
||||
logger.info(" - /{slug} (platform content pages: /about, /faq, /terms, /contact)")
|
||||
|
||||
|
||||
@app.get("/", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def platform_homepage(request: Request, db: Session = Depends(get_db)):
|
||||
"""
|
||||
Platform homepage at localhost:8000 or platform.com
|
||||
|
||||
Uses multi-platform CMS with three-tier resolution:
|
||||
1. Platform marketing pages (is_platform_page=True)
|
||||
2. Vendor default pages (fallback)
|
||||
3. Vendor override pages
|
||||
|
||||
Falls back to default static template if not found.
|
||||
"""
|
||||
from app.services.content_page_service import content_page_service
|
||||
|
||||
logger.debug("[PLATFORM] Homepage requested")
|
||||
|
||||
# Get platform from middleware (multi-platform support)
|
||||
platform = getattr(request.state, "platform", None)
|
||||
|
||||
if platform:
|
||||
# Try to load platform homepage from CMS (platform marketing page)
|
||||
homepage = content_page_service.get_platform_page(
|
||||
db,
|
||||
platform_id=platform.id,
|
||||
slug="home",
|
||||
include_unpublished=False,
|
||||
)
|
||||
|
||||
# Also try platform_homepage slug for backwards compatibility
|
||||
if not homepage:
|
||||
homepage = content_page_service.get_platform_page(
|
||||
db,
|
||||
platform_id=platform.id,
|
||||
slug="platform_homepage",
|
||||
include_unpublished=False,
|
||||
)
|
||||
|
||||
# Load header and footer navigation (platform marketing pages)
|
||||
header_pages = content_page_service.list_platform_pages(
|
||||
db, platform_id=platform.id, header_only=True, include_unpublished=False
|
||||
)
|
||||
|
||||
footer_pages = content_page_service.list_platform_pages(
|
||||
db, platform_id=platform.id, footer_only=True, include_unpublished=False
|
||||
)
|
||||
else:
|
||||
# Fallback for when no platform context (shouldn't happen normally)
|
||||
homepage = None
|
||||
header_pages = []
|
||||
footer_pages = []
|
||||
|
||||
# Get language from request state and build i18n context
|
||||
language = getattr(request.state, "language", "fr")
|
||||
i18n_globals = get_jinja2_globals(language)
|
||||
|
||||
if homepage:
|
||||
# Use template selection from CMS
|
||||
template_name = homepage.template or "default"
|
||||
template_path = f"platform/homepage-{template_name}.html"
|
||||
|
||||
logger.info(f"[PLATFORM] Rendering CMS homepage with template: {template_path}")
|
||||
|
||||
context = {
|
||||
"request": request,
|
||||
"page": homepage,
|
||||
"header_pages": header_pages,
|
||||
"footer_pages": footer_pages,
|
||||
}
|
||||
context.update(i18n_globals)
|
||||
|
||||
return templates.TemplateResponse(template_path, context)
|
||||
|
||||
# Fallback to default static template
|
||||
logger.info("[PLATFORM] No CMS homepage found, using default template")
|
||||
|
||||
context = {
|
||||
"request": request,
|
||||
"header_pages": header_pages,
|
||||
"footer_pages": footer_pages,
|
||||
}
|
||||
context.update(i18n_globals)
|
||||
|
||||
return templates.TemplateResponse("platform/homepage-default.html", context)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# PLATFORM-PREFIXED ROUTES (Development mode: /oms/, /loyalty/, etc.)
|
||||
# These routes handle path-based platform routing in development.
|
||||
# In production, domain-based routing means these won't be needed.
|
||||
# ============================================================================
|
||||
|
||||
|
||||
async def _serve_platform_homepage(request: Request, platform_code: str, db: Session):
|
||||
"""Helper function to serve platform homepage."""
|
||||
from app.services.content_page_service import content_page_service
|
||||
from models.database.platform import Platform
|
||||
|
||||
# Get platform from middleware or query directly
|
||||
platform = getattr(request.state, "platform", None)
|
||||
if not platform:
|
||||
platform = db.query(Platform).filter(
|
||||
Platform.code == platform_code.lower(),
|
||||
Platform.is_active == True
|
||||
).first()
|
||||
|
||||
if not platform:
|
||||
raise HTTPException(status_code=404, detail=f"Platform not found: {platform_code}")
|
||||
|
||||
# Load platform homepage from CMS
|
||||
homepage = content_page_service.get_platform_page(
|
||||
db, platform_id=platform.id, slug="home", include_unpublished=False
|
||||
)
|
||||
if not homepage:
|
||||
homepage = content_page_service.get_platform_page(
|
||||
db, platform_id=platform.id, slug="platform_homepage", include_unpublished=False
|
||||
)
|
||||
|
||||
# Load navigation
|
||||
header_pages = content_page_service.list_platform_pages(
|
||||
db, platform_id=platform.id, header_only=True, include_unpublished=False
|
||||
)
|
||||
footer_pages = content_page_service.list_platform_pages(
|
||||
db, platform_id=platform.id, footer_only=True, include_unpublished=False
|
||||
)
|
||||
|
||||
# Build context
|
||||
language = getattr(request.state, "language", "fr")
|
||||
i18n_globals = get_jinja2_globals(language)
|
||||
|
||||
if homepage:
|
||||
template_name = homepage.template or "default"
|
||||
template_path = f"platform/homepage-{template_name}.html"
|
||||
logger.info(f"[PLATFORM] Rendering {platform.code} homepage: {template_path}")
|
||||
|
||||
context = {
|
||||
"request": request,
|
||||
"page": homepage,
|
||||
"platform": platform,
|
||||
"header_pages": header_pages,
|
||||
"footer_pages": footer_pages,
|
||||
}
|
||||
context.update(i18n_globals)
|
||||
return templates.TemplateResponse(template_path, context)
|
||||
|
||||
# Fallback
|
||||
logger.info(f"[PLATFORM] No CMS homepage for {platform.code}, using default")
|
||||
context = {
|
||||
"request": request,
|
||||
"platform": platform,
|
||||
"header_pages": header_pages,
|
||||
"footer_pages": footer_pages,
|
||||
}
|
||||
context.update(i18n_globals)
|
||||
return templates.TemplateResponse("platform/homepage-default.html", context)
|
||||
|
||||
|
||||
async def _serve_platform_content_page(request: Request, platform_code: str, slug: str, db: Session):
|
||||
"""Helper function to serve platform content page."""
|
||||
from app.services.content_page_service import content_page_service
|
||||
from models.database.platform import Platform
|
||||
|
||||
# Get platform
|
||||
platform = getattr(request.state, "platform", None)
|
||||
if not platform:
|
||||
platform = db.query(Platform).filter(
|
||||
Platform.code == platform_code.lower(),
|
||||
Platform.is_active == True
|
||||
).first()
|
||||
|
||||
if not platform:
|
||||
raise HTTPException(status_code=404, detail=f"Platform not found: {platform_code}")
|
||||
|
||||
# Load content page
|
||||
page = content_page_service.get_platform_page(
|
||||
db, platform_id=platform.id, slug=slug, include_unpublished=False
|
||||
)
|
||||
if not page:
|
||||
logger.warning(f"[PLATFORM] Page not found: /{platform_code}/{slug}")
|
||||
raise HTTPException(status_code=404, detail=f"Page not found: {slug}")
|
||||
|
||||
# Load navigation
|
||||
header_pages = content_page_service.list_platform_pages(
|
||||
db, platform_id=platform.id, header_only=True, include_unpublished=False
|
||||
)
|
||||
footer_pages = content_page_service.list_platform_pages(
|
||||
db, platform_id=platform.id, footer_only=True, include_unpublished=False
|
||||
)
|
||||
|
||||
logger.info(f"[PLATFORM] Rendering {platform.code} page: {page.title} (/{slug})")
|
||||
|
||||
language = getattr(request.state, "language", "fr")
|
||||
i18n_globals = get_jinja2_globals(language)
|
||||
|
||||
context = {
|
||||
"request": request,
|
||||
"page": page,
|
||||
"platform": platform,
|
||||
"header_pages": header_pages,
|
||||
"footer_pages": footer_pages,
|
||||
}
|
||||
context.update(i18n_globals)
|
||||
return templates.TemplateResponse("platform/content-page.html", context)
|
||||
|
||||
|
||||
# Explicit routes for OMS platform
|
||||
@app.get("/oms", response_class=RedirectResponse, include_in_schema=False)
|
||||
async def oms_redirect():
|
||||
"""Redirect /oms to /oms/"""
|
||||
return RedirectResponse(url="/oms/", status_code=307)
|
||||
|
||||
|
||||
@app.get("/oms/", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def oms_homepage(request: Request, db: Session = Depends(get_db)):
|
||||
"""OMS platform homepage"""
|
||||
return await _serve_platform_homepage(request, "oms", db)
|
||||
|
||||
|
||||
@app.get("/oms/{slug}", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def oms_content_page(request: Request, slug: str, db: Session = Depends(get_db)):
|
||||
"""OMS platform content pages"""
|
||||
return await _serve_platform_content_page(request, "oms", slug, db)
|
||||
|
||||
|
||||
# Explicit routes for Loyalty platform
|
||||
@app.get("/loyalty", response_class=RedirectResponse, include_in_schema=False)
|
||||
async def loyalty_redirect():
|
||||
"""Redirect /loyalty to /loyalty/"""
|
||||
return RedirectResponse(url="/loyalty/", status_code=307)
|
||||
|
||||
|
||||
@app.get("/loyalty/", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def loyalty_homepage(request: Request, db: Session = Depends(get_db)):
|
||||
"""Loyalty platform homepage"""
|
||||
return await _serve_platform_homepage(request, "loyalty", db)
|
||||
|
||||
|
||||
@app.get("/loyalty/{slug}", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def loyalty_content_page(request: Request, slug: str, db: Session = Depends(get_db)):
|
||||
"""Loyalty platform content pages"""
|
||||
return await _serve_platform_content_page(request, "loyalty", slug, db)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# GENERIC PLATFORM CONTENT PAGE (must be LAST - catches all /{slug})
|
||||
# ============================================================================
|
||||
|
||||
@app.get("/{slug}", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def platform_content_page(
|
||||
request: Request, slug: str, db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Platform content pages: /about, /faq, /terms, /contact, etc.
|
||||
|
||||
Uses multi-platform CMS with three-tier resolution.
|
||||
Returns 404 if page not found.
|
||||
|
||||
This route MUST be defined LAST to avoid conflicts with other routes.
|
||||
"""
|
||||
from app.services.content_page_service import content_page_service
|
||||
|
||||
logger.debug(f"[PLATFORM] Content page requested: /{slug}")
|
||||
|
||||
# Get platform from middleware (multi-platform support)
|
||||
platform = getattr(request.state, "platform", None)
|
||||
|
||||
if not platform:
|
||||
logger.warning(f"[PLATFORM] No platform context for content page: {slug}")
|
||||
raise HTTPException(status_code=404, detail=f"Page not found: {slug}")
|
||||
|
||||
# Load platform marketing page from CMS
|
||||
page = content_page_service.get_platform_page(
|
||||
db,
|
||||
platform_id=platform.id,
|
||||
slug=slug,
|
||||
include_unpublished=False,
|
||||
)
|
||||
|
||||
if not page:
|
||||
logger.warning(f"[PLATFORM] Content page not found: {slug}")
|
||||
raise HTTPException(status_code=404, detail=f"Page not found: {slug}")
|
||||
|
||||
# Load header and footer navigation (platform marketing pages)
|
||||
header_pages = content_page_service.list_platform_pages(
|
||||
db, platform_id=platform.id, header_only=True, include_unpublished=False
|
||||
)
|
||||
|
||||
footer_pages = content_page_service.list_platform_pages(
|
||||
db, platform_id=platform.id, footer_only=True, include_unpublished=False
|
||||
)
|
||||
|
||||
logger.info(f"[PLATFORM] Rendering content page: {page.title} (/{slug})")
|
||||
|
||||
# Get language from request state and build i18n context
|
||||
language = getattr(request.state, "language", "fr")
|
||||
i18n_globals = get_jinja2_globals(language)
|
||||
|
||||
context = {
|
||||
"request": request,
|
||||
"page": page,
|
||||
"header_pages": header_pages,
|
||||
"footer_pages": footer_pages,
|
||||
}
|
||||
context.update(i18n_globals)
|
||||
|
||||
return templates.TemplateResponse("platform/content-page.html", context)
|
||||
|
||||
|
||||
logger.info("=" * 80)
|
||||
@@ -701,73 +410,6 @@ for route in app.routes:
|
||||
logger.info(f" {methods:<10} {route.path:<60}")
|
||||
logger.info("=" * 80)
|
||||
|
||||
# ============================================================================
|
||||
# API ROUTES (JSON Responses)
|
||||
# ============================================================================
|
||||
|
||||
|
||||
# Public Routes (no authentication required)
|
||||
@app.get("/", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def root(request: Request, db: Session = Depends(get_db)):
|
||||
"""
|
||||
Smart root handler:
|
||||
- If vendor detected (domain/subdomain): Show vendor landing page or redirect to shop
|
||||
- If no vendor (platform root): Redirect to documentation
|
||||
"""
|
||||
vendor = getattr(request.state, "vendor", None)
|
||||
platform = getattr(request.state, "platform", None)
|
||||
|
||||
if vendor:
|
||||
# Vendor context detected - serve landing page
|
||||
from app.services.content_page_service import content_page_service
|
||||
|
||||
# Get platform_id (use platform from context or default to 1 for OMS)
|
||||
platform_id = platform.id if platform else 1
|
||||
|
||||
# Try to find landing page (slug='landing' or 'home') with three-tier resolution
|
||||
landing_page = content_page_service.get_page_for_vendor(
|
||||
db, platform_id=platform_id, slug="landing", vendor_id=vendor.id, include_unpublished=False
|
||||
)
|
||||
|
||||
if not landing_page:
|
||||
# Try 'home' slug as fallback
|
||||
landing_page = content_page_service.get_page_for_vendor(
|
||||
db, platform_id=platform_id, slug="home", vendor_id=vendor.id, include_unpublished=False
|
||||
)
|
||||
|
||||
if landing_page:
|
||||
# Render landing page with selected template
|
||||
from app.routes.shop_pages import get_shop_context
|
||||
|
||||
template_name = landing_page.template or "default"
|
||||
template_path = f"vendor/landing-{template_name}.html"
|
||||
|
||||
return templates.TemplateResponse(
|
||||
template_path, get_shop_context(request, db=db, page=landing_page)
|
||||
)
|
||||
# No landing page - redirect to shop
|
||||
vendor_context = getattr(request.state, "vendor_context", None)
|
||||
access_method = (
|
||||
vendor_context.get("detection_method", "unknown")
|
||||
if vendor_context
|
||||
else "unknown"
|
||||
)
|
||||
|
||||
if access_method == "path":
|
||||
full_prefix = (
|
||||
vendor_context.get("full_prefix", "/vendor/")
|
||||
if vendor_context
|
||||
else "/vendor/"
|
||||
)
|
||||
return RedirectResponse(
|
||||
url=f"{full_prefix}{vendor.subdomain}/shop/", status_code=302
|
||||
)
|
||||
# Domain/subdomain
|
||||
return RedirectResponse(url="/shop/", status_code=302)
|
||||
# No vendor - platform root
|
||||
return RedirectResponse(url="/documentation")
|
||||
|
||||
|
||||
@app.get("/documentation", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def documentation():
|
||||
"""Redirect to documentation"""
|
||||
|
||||
Reference in New Issue
Block a user