Files
orion/app/modules/hosting/routes/pages/public.py
Samir Boulahtit cff0af31be feat(hosting): signed preview URLs for POC sites
Replace the standalone POC viewer (duplicate rendering) with signed
JWT preview tokens that bypass StorefrontAccessMiddleware:

Architecture:
1. Admin clicks Preview → route generates signed JWT
2. Redirects to /storefront/{subdomain}/homepage?_preview=token
3. Middleware validates token signature + expiry + store_id
4. Sets request.state.is_preview = True, skips subscription check
5. Full storefront renders with HostWizard preview banner injected

New files:
- app/core/preview_token.py: create_preview_token/verify_preview_token

Changes:
- middleware/storefront_access.py: preview token bypass before sub check
- storefront/base.html: preview banner injection via is_preview state
- hosting/routes/pages/public.py: redirect with signed token (was direct render)
- hosting/routes/api/admin_sites.py: GET /sites/{id}/preview-url endpoint

Removed:
- hosting/templates/hosting/public/poc-viewer.html (replaced by storefront)

Benefits: one rendering path, all section types work, shareable 24h links.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 22:41:34 +02:00

75 lines
2.5 KiB
Python

# app/modules/hosting/routes/pages/public.py
"""
Hosting Public Page Routes.
POC site preview via signed URL redirect to the storefront.
The StorefrontAccessMiddleware validates the preview token and
allows rendering without an active subscription.
"""
from fastapi import APIRouter, Depends, Path, Query
from fastapi.responses import HTMLResponse, RedirectResponse
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.core.preview_token import create_preview_token
router = APIRouter()
@router.get(
"/hosting/sites/{site_id}/preview",
include_in_schema=False,
)
async def poc_site_viewer(
site_id: int = Path(..., description="Hosted Site ID"),
page: str = Query("homepage", description="Page slug to preview"),
db: Session = Depends(get_db),
):
"""Redirect to storefront with signed preview token.
Generates a time-limited JWT and redirects to the store's
storefront URL. The StorefrontAccessMiddleware validates the
token and bypasses the subscription check.
"""
from app.modules.hosting.models import HostedSite, HostedSiteStatus
site = db.query(HostedSite).filter(HostedSite.id == site_id).first()
if not site or site.status not in (
HostedSiteStatus.POC_READY,
HostedSiteStatus.PROPOSAL_SENT,
HostedSiteStatus.ACCEPTED,
):
return HTMLResponse(content="<h1>Site not available for preview</h1>", status_code=404)
store = site.store
if not store:
return HTMLResponse(content="<h1>Store not found</h1>", status_code=404)
# Generate signed preview token — use subdomain for URL routing
subdomain = store.subdomain or store.store_code
token = create_preview_token(store.id, subdomain, site.id)
# Get platform code for dev-mode URL prefix
from app.core.config import settings
from app.modules.tenancy.models import StorePlatform
store_platform = (
db.query(StorePlatform)
.filter(StorePlatform.store_id == store.id)
.first()
)
# In dev mode, storefront needs /platforms/{code}/ prefix
if settings.debug and store_platform and store_platform.platform:
platform_code = store_platform.platform.code
base_url = f"/platforms/{platform_code}/storefront/{subdomain}"
else:
base_url = f"/storefront/{subdomain}"
# Append page slug — storefront needs /{slug} (root has no catch-all)
base_url += f"/{page}"
return RedirectResponse(f"{base_url}?_preview={token}", status_code=302)