Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart with Orion/orion/ORION across 184 files. This includes database identifiers, email addresses, domain references, R2 bucket names, DNS prefixes, encryption salt, Celery app name, config defaults, Docker configs, CI configs, documentation, seed data, and templates. Renames homepage-wizamart.html template to homepage-orion.html. Fixes duplicate file_pattern key in api.yaml architecture rule. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
250 lines
8.4 KiB
Python
250 lines
8.4 KiB
Python
# app/modules/cms/routes/pages/platform.py
|
|
"""
|
|
CMS Platform Page Routes (HTML rendering).
|
|
|
|
Platform (unauthenticated) pages for platform content:
|
|
- Homepage
|
|
- Generic content pages (/{slug} catch-all)
|
|
"""
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.database import get_db
|
|
from app.modules.cms.services import content_page_service
|
|
from app.modules.core.utils.page_context import get_platform_context
|
|
from app.templates_config import templates
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter()
|
|
|
|
# Route configuration - high priority so catch-all is registered last
|
|
ROUTE_CONFIG = {
|
|
"priority": 100,
|
|
}
|
|
|
|
|
|
def _get_tiers_data(db: Session) -> list[dict]:
|
|
"""Build tier data for display in templates from database."""
|
|
from app.modules.billing.models import SubscriptionTier, TierCode
|
|
|
|
tiers_db = (
|
|
db.query(SubscriptionTier)
|
|
.filter(
|
|
SubscriptionTier.is_active == True,
|
|
SubscriptionTier.is_public == True,
|
|
)
|
|
.order_by(SubscriptionTier.display_order)
|
|
.all()
|
|
)
|
|
|
|
tiers = []
|
|
for tier in tiers_db:
|
|
feature_codes = sorted(tier.get_feature_codes())
|
|
tiers.append({
|
|
"code": tier.code,
|
|
"name": tier.name,
|
|
"price_monthly": tier.price_monthly_cents / 100,
|
|
"price_annual": (tier.price_annual_cents / 100) if tier.price_annual_cents else None,
|
|
"feature_codes": feature_codes,
|
|
"products_limit": tier.get_limit_for_feature("products_limit"),
|
|
"orders_per_month": tier.get_limit_for_feature("orders_per_month"),
|
|
"team_members": tier.get_limit_for_feature("team_members"),
|
|
"is_popular": tier.code == TierCode.PROFESSIONAL.value,
|
|
"is_enterprise": tier.code == TierCode.ENTERPRISE.value,
|
|
})
|
|
return tiers
|
|
|
|
|
|
# ============================================================================
|
|
# HOMEPAGE
|
|
# ============================================================================
|
|
|
|
|
|
@router.get("/", response_class=HTMLResponse, name="platform_homepage")
|
|
async def homepage(
|
|
request: Request,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
Homepage handler.
|
|
|
|
Handles two scenarios:
|
|
1. Store on custom domain (store.com) -> Show store landing page or redirect to shop
|
|
2. Platform marketing site -> Show platform homepage from CMS or default template
|
|
|
|
URL routing:
|
|
- localhost:9999/ -> Main marketing site ('main' platform)
|
|
- localhost:9999/platforms/oms/ -> OMS platform (middleware rewrites to /)
|
|
- oms.lu/ -> OMS platform (domain-based)
|
|
- shop.mymerchant.com/ -> Store landing page (custom domain)
|
|
"""
|
|
# Get platform and store from middleware
|
|
platform = getattr(request.state, "platform", None)
|
|
store = getattr(request.state, "store", None)
|
|
|
|
# Scenario 1: Store detected (custom domain like store.com)
|
|
if store:
|
|
logger.debug(f"[HOMEPAGE] Store detected: {store.subdomain}")
|
|
|
|
# Get platform_id (use platform from context or default to 1 for OMS)
|
|
platform_id = platform.id if platform else 1
|
|
|
|
# Try to find store landing page (slug='landing' or 'home')
|
|
landing_page = content_page_service.get_page_for_store(
|
|
db,
|
|
platform_id=platform_id,
|
|
slug="landing",
|
|
store_id=store.id,
|
|
include_unpublished=False,
|
|
)
|
|
|
|
if not landing_page:
|
|
landing_page = content_page_service.get_page_for_store(
|
|
db,
|
|
platform_id=platform_id,
|
|
slug="home",
|
|
store_id=store.id,
|
|
include_unpublished=False,
|
|
)
|
|
|
|
if landing_page:
|
|
# Render landing page with selected template
|
|
from app.modules.core.utils.page_context import get_storefront_context
|
|
|
|
template_name = landing_page.template or "default"
|
|
template_path = f"cms/storefront/landing-{template_name}.html"
|
|
|
|
logger.info(f"[HOMEPAGE] Rendering store landing page: {template_path}")
|
|
return templates.TemplateResponse(
|
|
template_path,
|
|
get_storefront_context(request, db=db, page=landing_page),
|
|
)
|
|
|
|
# No landing page - redirect to shop
|
|
store_context = getattr(request.state, "store_context", None)
|
|
access_method = (
|
|
store_context.get("detection_method", "unknown")
|
|
if store_context
|
|
else "unknown"
|
|
)
|
|
|
|
if access_method == "path":
|
|
full_prefix = (
|
|
store_context.get("full_prefix", "/store/")
|
|
if store_context
|
|
else "/store/"
|
|
)
|
|
return RedirectResponse(
|
|
url=f"{full_prefix}{store.subdomain}/storefront/", status_code=302
|
|
)
|
|
# Domain/subdomain - redirect to /storefront/
|
|
return RedirectResponse(url="/storefront/", status_code=302)
|
|
|
|
# Scenario 2: Platform marketing site (no store)
|
|
# Load platform homepage from CMS (slug='home')
|
|
platform_id = platform.id if platform else 1
|
|
|
|
cms_homepage = content_page_service.get_platform_page(
|
|
db, platform_id=platform_id, slug="home", include_unpublished=False
|
|
)
|
|
|
|
if cms_homepage:
|
|
# Use CMS-based homepage with template selection
|
|
context = get_platform_context(request, db)
|
|
context["page"] = cms_homepage
|
|
context["tiers"] = _get_tiers_data(db)
|
|
|
|
template_name = cms_homepage.template or "default"
|
|
template_path = f"cms/platform/homepage-{template_name}.html"
|
|
|
|
logger.info(f"[HOMEPAGE] Rendering CMS homepage with template: {template_path}")
|
|
return templates.TemplateResponse(template_path, context)
|
|
|
|
# Fallback: Default orion homepage (no CMS content)
|
|
logger.info("[HOMEPAGE] No CMS homepage found, using default orion template")
|
|
context = get_platform_context(request, db)
|
|
context["tiers"] = _get_tiers_data(db)
|
|
|
|
# Add-ons (hardcoded for now, will come from DB)
|
|
context["addons"] = [
|
|
{
|
|
"code": "domain",
|
|
"name": "Custom Domain",
|
|
"description": "Use your own domain (mydomain.com)",
|
|
"price": 15,
|
|
"billing_period": "year",
|
|
"icon": "globe",
|
|
},
|
|
{
|
|
"code": "ssl_premium",
|
|
"name": "Premium SSL",
|
|
"description": "EV certificate for trust badges",
|
|
"price": 49,
|
|
"billing_period": "year",
|
|
"icon": "shield-check",
|
|
},
|
|
{
|
|
"code": "email",
|
|
"name": "Email Package",
|
|
"description": "Professional email addresses",
|
|
"price": 5,
|
|
"billing_period": "month",
|
|
"icon": "mail",
|
|
"options": [
|
|
{"quantity": 5, "price": 5},
|
|
{"quantity": 10, "price": 9},
|
|
{"quantity": 25, "price": 19},
|
|
],
|
|
},
|
|
]
|
|
|
|
return templates.TemplateResponse(
|
|
"cms/platform/homepage-orion.html",
|
|
context,
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# GENERIC CONTENT PAGES (CMS)
|
|
# ============================================================================
|
|
# IMPORTANT: This route must be LAST as it catches all /{slug} URLs
|
|
|
|
|
|
@router.get("/{slug}", response_class=HTMLResponse, name="platform_content_page")
|
|
async def content_page(
|
|
request: Request,
|
|
slug: str,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
Serve CMS content pages (about, contact, faq, privacy, terms, etc.).
|
|
|
|
This is a catch-all route for dynamic content pages managed via the admin CMS.
|
|
Platform pages have store_id=None and is_platform_page=True.
|
|
"""
|
|
# Get platform from middleware (default to OMS platform_id=1)
|
|
platform = getattr(request.state, "platform", None)
|
|
platform_id = platform.id if platform else 1
|
|
|
|
# Load platform marketing page from database
|
|
page = content_page_service.get_platform_page(
|
|
db, platform_id=platform_id, slug=slug, include_unpublished=False
|
|
)
|
|
|
|
if not page:
|
|
raise HTTPException(status_code=404, detail=f"Page not found: {slug}")
|
|
|
|
context = get_platform_context(request, db)
|
|
context["page"] = page
|
|
context["page_title"] = page.title
|
|
|
|
return templates.TemplateResponse(
|
|
"cms/platform/content-page.html",
|
|
context,
|
|
)
|