Some checks failed
_get_tiers_data() was querying all active tiers across all platforms. Now accepts platform_id parameter to scope tiers to the current platform. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
221 lines
7.6 KiB
Python
221 lines
7.6 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, platform_id: int | None = None) -> list[dict]:
|
|
"""Build tier data for display in templates from database."""
|
|
from app.modules.billing.models import SubscriptionTier, TierCode
|
|
|
|
filters = [
|
|
SubscriptionTier.is_active.is_(True),
|
|
SubscriptionTier.is_public.is_(True),
|
|
]
|
|
if platform_id is not None:
|
|
filters.append(SubscriptionTier.platform_id == platform_id)
|
|
|
|
tiers_db = (
|
|
db.query(SubscriptionTier)
|
|
.filter(*filters)
|
|
.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 /)
|
|
- omsflow.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}")
|
|
|
|
if not platform:
|
|
raise HTTPException(status_code=400, detail="Platform context required")
|
|
platform_id = platform.id
|
|
|
|
# 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" and platform:
|
|
return RedirectResponse(
|
|
url=f"/platforms/{platform.code}/storefront/{store.store_code}/",
|
|
status_code=302,
|
|
)
|
|
# Domain/subdomain - root is storefront
|
|
return RedirectResponse(url="/", status_code=302)
|
|
|
|
# Scenario 2: Platform marketing site (no store)
|
|
# Load platform homepage from CMS (slug='home')
|
|
if not platform:
|
|
raise HTTPException(status_code=400, detail="Platform context required")
|
|
platform_id = platform.id
|
|
|
|
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, platform_id=platform_id)
|
|
|
|
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 homepage template with placeholder content
|
|
logger.info("[HOMEPAGE] No CMS homepage found, using default template with placeholders")
|
|
context = get_platform_context(request, db)
|
|
context["tiers"] = _get_tiers_data(db, platform_id=platform_id)
|
|
|
|
return templates.TemplateResponse(
|
|
"cms/platform/homepage-default.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.
|
|
"""
|
|
platform = getattr(request.state, "platform", None)
|
|
if not platform:
|
|
raise HTTPException(status_code=400, detail="Platform context required")
|
|
platform_id = platform.id
|
|
|
|
# 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,
|
|
)
|