feat(hosting): add industry template infrastructure (Workstream 3B)
5 industry templates as JSON presets, each with theme + multi-page content:
- generic: clean minimal (homepage, about, contact)
- restaurant: warm tones, Playfair Display (homepage, about, menu, contact)
- construction: amber/earth tones, Montserrat (homepage, services, projects, contact)
- auto-parts: red/bold, parts-focused (homepage, catalog, contact)
- professional-services: navy/blue, Merriweather (homepage, services, team, contact)
Each template has:
- meta.json (name, description, tags, languages)
- theme.json (colors, fonts, layout, header style)
- pages/*.json (section-based homepage + content pages with i18n)
- {{placeholder}} variables for prospect data injection
TemplateService loads from templates_library/ directory with caching.
GET /admin/hosting/sites/templates endpoint to list available templates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
114
app/modules/hosting/services/template_service.py
Normal file
114
app/modules/hosting/services/template_service.py
Normal file
@@ -0,0 +1,114 @@
|
||||
# app/modules/hosting/services/template_service.py
|
||||
"""
|
||||
Template service for the hosting module.
|
||||
|
||||
Loads and manages industry templates from the templates_library directory.
|
||||
Templates are JSON files that define page content, themes, and sections
|
||||
for different business types (restaurant, construction, etc.).
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TEMPLATES_DIR = Path(__file__).parent.parent / "templates_library"
|
||||
|
||||
|
||||
class TemplateService:
|
||||
"""Manages industry templates for POC site generation."""
|
||||
|
||||
def __init__(self):
|
||||
self._manifest = None
|
||||
self._cache: dict[str, dict] = {}
|
||||
|
||||
def _load_manifest(self) -> dict:
|
||||
"""Load the manifest.json file."""
|
||||
if self._manifest is None:
|
||||
manifest_path = TEMPLATES_DIR / "manifest.json"
|
||||
self._manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
return self._manifest
|
||||
|
||||
def list_templates(self) -> list[dict]:
|
||||
"""List all available templates with metadata."""
|
||||
manifest = self._load_manifest()
|
||||
templates = []
|
||||
for entry in manifest.get("templates", []):
|
||||
template_id = entry["id"]
|
||||
meta = self._load_meta(template_id)
|
||||
templates.append({
|
||||
"id": template_id,
|
||||
"name": meta.get("name", entry.get("name", template_id)),
|
||||
"description": meta.get("description", entry.get("description", "")),
|
||||
"tags": meta.get("tags", entry.get("tags", [])),
|
||||
"languages": meta.get("languages", []),
|
||||
"pages": entry.get("pages", []),
|
||||
})
|
||||
return templates
|
||||
|
||||
def get_template(self, template_id: str) -> dict | None:
|
||||
"""Load a complete template with meta, theme, and all pages."""
|
||||
if template_id in self._cache:
|
||||
return self._cache[template_id]
|
||||
|
||||
template_dir = TEMPLATES_DIR / template_id
|
||||
if not template_dir.is_dir():
|
||||
return None
|
||||
|
||||
meta = self._load_meta(template_id)
|
||||
theme = self._load_json(template_dir / "theme.json")
|
||||
pages = self._load_pages(template_dir)
|
||||
|
||||
template = {
|
||||
"id": template_id,
|
||||
"meta": meta,
|
||||
"theme": theme,
|
||||
"pages": pages,
|
||||
}
|
||||
self._cache[template_id] = template
|
||||
return template
|
||||
|
||||
def get_theme(self, template_id: str) -> dict | None:
|
||||
"""Load just the theme configuration for a template."""
|
||||
template_dir = TEMPLATES_DIR / template_id
|
||||
return self._load_json(template_dir / "theme.json")
|
||||
|
||||
def get_page(self, template_id: str, page_slug: str) -> dict | None:
|
||||
"""Load a single page definition from a template."""
|
||||
page_path = TEMPLATES_DIR / template_id / "pages" / f"{page_slug}.json"
|
||||
return self._load_json(page_path)
|
||||
|
||||
def template_exists(self, template_id: str) -> bool:
|
||||
"""Check if a template exists."""
|
||||
return (TEMPLATES_DIR / template_id / "meta.json").is_file()
|
||||
|
||||
def _load_meta(self, template_id: str) -> dict:
|
||||
"""Load meta.json for a template."""
|
||||
return self._load_json(TEMPLATES_DIR / template_id / "meta.json") or {}
|
||||
|
||||
def _load_pages(self, template_dir: Path) -> list[dict]:
|
||||
"""Load all page JSONs from a template's pages/ directory."""
|
||||
pages_dir = template_dir / "pages"
|
||||
if not pages_dir.is_dir():
|
||||
return []
|
||||
pages = []
|
||||
for page_file in sorted(pages_dir.glob("*.json")):
|
||||
page_data = self._load_json(page_file)
|
||||
if page_data:
|
||||
pages.append(page_data)
|
||||
return pages
|
||||
|
||||
@staticmethod
|
||||
def _load_json(path: Path) -> dict | None:
|
||||
"""Safely load a JSON file."""
|
||||
if not path.is_file():
|
||||
return None
|
||||
try:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
except (json.JSONDecodeError, OSError) as e:
|
||||
logger.warning("Failed to load template file %s: %s", path, e)
|
||||
return None
|
||||
|
||||
|
||||
template_service = TemplateService()
|
||||
Reference in New Issue
Block a user