Files
orion/app/modules/cms/routes/pages/store.py
Samir Boulahtit ce5b54f27b
Some checks failed
CI / ruff (push) Successful in 10s
CI / pytest (push) Failing after 45m18s
CI / validate (push) Successful in 24s
CI / dependency-scanning (push) Successful in 30s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped
feat: production routing support for subdomain and custom domain modes
Double-mount store routes at /store/* and /store/{store_code}/* so the
same handlers work in dev path-based, prod path-based, prod subdomain,
and prod custom-domain modes.  Wire StorePlatform.custom_subdomain into
StoreContextMiddleware for per-platform subdomain overrides.  Add admin
custom-domain management UI, fix stale /shop/ reset link, add
/merchants/ to reserved paths, and server-render window.STORE_CODE for
JS that previously parsed the URL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 00:15:06 +01:00

240 lines
7.1 KiB
Python

# app/modules/cms/routes/pages/store.py
"""
CMS Store Page Routes (HTML rendering).
Store pages for managing content pages and rendering CMS content.
"""
import logging
from fastapi import APIRouter, Depends, HTTPException, Path, Request
from fastapi.responses import HTMLResponse
from sqlalchemy.orm import Session
from app.api.deps import (
get_current_store_from_cookie_or_header,
get_db,
get_resolved_store_code,
)
from app.modules.cms.services import content_page_service
from app.modules.core.services.platform_settings_service import (
platform_settings_service, # MOD-004 - shared platform service
)
from app.modules.tenancy.models import Store, User
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,
}
# ============================================================================
# HELPER: Build Store Dashboard Context
# ============================================================================
def get_store_context(
request: Request,
db: Session,
current_user: User,
store_code: str,
**extra_context,
) -> dict:
"""
Build template context for store dashboard pages.
Resolves locale/currency using the platform settings service with
store override support.
"""
# Load store from database
store = db.query(Store).filter(Store.subdomain == store_code).first()
# Get platform defaults
platform_config = platform_settings_service.get_storefront_config(db)
# Resolve with store override
storefront_locale = platform_config["locale"]
storefront_currency = platform_config["currency"]
if store and store.storefront_locale:
storefront_locale = store.storefront_locale
context = {
"request": request,
"user": current_user,
"store": store,
"store_code": store_code,
"storefront_locale": storefront_locale,
"storefront_currency": storefront_currency,
"dashboard_language": store.dashboard_language if store else "en",
}
# Add any extra context
if extra_context:
context.update(extra_context)
return context
# ============================================================================
# CONTENT PAGES MANAGEMENT
# ============================================================================
@router.get(
"/content-pages", response_class=HTMLResponse, include_in_schema=False
)
async def store_content_pages_list(
request: Request,
store_code: str = Depends(get_resolved_store_code),
current_user: User = Depends(get_current_store_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render content pages management page.
Shows platform defaults (can be overridden) and store custom pages.
"""
return templates.TemplateResponse(
"cms/store/content-pages.html",
get_store_context(request, db, current_user, store_code),
)
@router.get(
"/content-pages/create",
response_class=HTMLResponse,
include_in_schema=False,
)
async def store_content_page_create(
request: Request,
store_code: str = Depends(get_resolved_store_code),
current_user: User = Depends(get_current_store_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render content page creation form.
"""
return templates.TemplateResponse(
"cms/store/content-page-edit.html",
get_store_context(request, db, current_user, store_code, page_id=None),
)
@router.get(
"/content-pages/{page_id}/edit",
response_class=HTMLResponse,
include_in_schema=False,
)
async def store_content_page_edit(
request: Request,
store_code: str = Depends(get_resolved_store_code),
page_id: int = Path(..., description="Content page ID"),
current_user: User = Depends(get_current_store_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render content page edit form.
"""
return templates.TemplateResponse(
"cms/store/content-page-edit.html",
get_store_context(request, db, current_user, store_code, page_id=page_id),
)
# ============================================================================
# DYNAMIC CONTENT PAGES (CMS) - Public Shop Display
# ============================================================================
@router.get(
"/{slug}", response_class=HTMLResponse, include_in_schema=False
)
async def store_content_page(
request: Request,
store_code: str = Depends(get_resolved_store_code),
slug: str = Path(..., description="Content page slug"),
db: Session = Depends(get_db),
):
"""
Generic content page handler for store shop (CMS).
Handles dynamic content pages like:
- /stores/orion/about, /stores/orion/faq, /stores/orion/contact, etc.
Features:
- Two-tier system: Store overrides take priority, fallback to platform defaults
- Only shows published pages
- Returns 404 if page not found or unpublished
NOTE: This is a catch-all route and must be registered LAST to avoid
shadowing other specific routes.
"""
logger.debug(
"[CMS] store_content_page REACHED",
extra={
"path": request.url.path,
"store_code": store_code,
"slug": slug,
"store": getattr(request.state, "store", "NOT SET"),
"context": getattr(request.state, "context_type", "NOT SET"),
},
)
store = getattr(request.state, "store", None)
platform = getattr(request.state, "platform", None)
store_id = store.id if store else None
if not platform:
raise HTTPException(status_code=400, detail="Platform context required")
platform_id = platform.id
# Load content page from database (store override → platform default)
page = content_page_service.get_page_for_store(
db, platform_id=platform_id, slug=slug, store_id=store_id, include_unpublished=False
)
if not page:
logger.info(
f"[CMS] Content page not found: {slug}",
extra={
"slug": slug,
"store_code": store_code,
"store_id": store_id,
},
)
raise HTTPException(status_code=404, detail="Page not found")
logger.info(
f"[CMS] Rendering page: {page.title}",
extra={
"slug": slug,
"page_id": page.id,
"is_store_override": page.store_id is not None,
"store_id": store_id,
},
)
# Resolve locale for shop template
platform_config = platform_settings_service.get_storefront_config(db)
storefront_locale = platform_config["locale"]
storefront_currency = platform_config["currency"]
if store and store.storefront_locale:
storefront_locale = store.storefront_locale
return templates.TemplateResponse(
"storefront/content-page.html",
{
"request": request,
"page": page,
"store": store,
"store_code": store_code,
"storefront_locale": storefront_locale,
"storefront_currency": storefront_currency,
},
)