Some checks failed
Clean up 28 backward compatibility instances identified in the codebase. The app is not live, so all shims are replaced with the target architecture: - Remove legacy Inventory.location column (use bin_location exclusively) - Remove dashboard _extract_metric_value helper (use flat metrics dict) - Remove legacy stat field duplicates (total_stores, total_imports, etc.) - Remove 13 re-export shims and class aliases across modules - Remove module-enabling JSON fallback (use PlatformModule junction table) - Remove menu_to_legacy_format() conversion (return dataclasses directly) - Remove title/description from MarketplaceProductBase schema - Clean billing convenience method docstrings - Clean test fixtures and backward-compat comments - Add PlatformModule seeding to init_production.py 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 /)
|
|
- 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}")
|
|
|
|
# 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,
|
|
)
|