feat(hosting): implement POC builder service (Workstream 3C)

One-click POC site generation from prospect data + industry template:

PocBuilderService.build_poc():
1. Loads prospect (scraped content, contacts, business info)
2. Loads industry template (pages, theme, sections)
3. Creates HostedSite + Store via hosted_site_service
4. Populates CMS ContentPages from template, replacing {{placeholders}}
   (business_name, city, phone, email, address, meta_description,
   about_paragraph) with prospect data
5. Applies StoreTheme (colors, fonts, layout) from template
6. Auto-transitions to POC_READY status

API: POST /admin/hosting/sites/poc/build
Body: {prospect_id, template_id, merchant_id?}

Tested: prospect 1 (batirenovation-strasbourg.fr) + "construction"
template → 4 pages created, theme applied, subdomain assigned.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-01 22:46:59 +02:00
parent 2e043260eb
commit bc951a36d9
2 changed files with 290 additions and 0 deletions

View File

@@ -7,6 +7,7 @@ import logging
from math import ceil
from fastapi import APIRouter, Depends, Path, Query
from pydantic import BaseModel
from sqlalchemy.orm import Session
from app.api.deps import get_current_admin_api
@@ -24,6 +25,7 @@ from app.modules.hosting.schemas.hosted_site import (
)
from app.modules.hosting.schemas.template import TemplateListResponse, TemplateResponse
from app.modules.hosting.services.hosted_site_service import hosted_site_service
from app.modules.hosting.services.poc_builder_service import poc_builder_service
from app.modules.hosting.services.template_service import template_service
from app.modules.tenancy.schemas.auth import UserContext
@@ -42,6 +44,42 @@ def list_templates(
)
class BuildPocRequest(BaseModel):
"""Request to build a POC site from prospect + template."""
prospect_id: int
template_id: str
merchant_id: int | None = None
class BuildPocResponse(BaseModel):
"""Response from POC builder."""
hosted_site_id: int
store_id: int
pages_created: int
theme_applied: bool
template_id: str
subdomain: str | None = None
@router.post("/poc/build", response_model=BuildPocResponse)
def build_poc(
data: BuildPocRequest,
db: Session = Depends(get_db),
current_admin: UserContext = Depends(get_current_admin_api),
):
"""Build a POC site from prospect data + industry template."""
result = poc_builder_service.build_poc(
db,
prospect_id=data.prospect_id,
template_id=data.template_id,
merchant_id=data.merchant_id,
)
db.commit()
return BuildPocResponse(**result)
def _to_response(site) -> HostedSiteResponse:
"""Convert a hosted site model to response schema."""
return HostedSiteResponse(