"""Template Marketplace API — Phase 4.
Public routes for browsing templates, authenticated route for cloning.
All routes return JSON.
"""
from flask import Blueprint, jsonify, request, session
from app.models import (
accept_site_creation,
can_create_site,
get_template_by_slug,
get_user,
list_templates,
seed_templates,
)
from app.routes.auth import login_required
templates_bp = Blueprint("templates", __name__, url_prefix="/api/v2/templates")
# ─── Seed endpoint (internal) ────────────────────────────────────────────────
@templates_bp.route("/_seed", methods=["POST"])
@login_required
def seed():
"""Internal endpoint to seed templates. Requires admin user."""
user_id = session["user_id"]
user = get_user(user_id)
if not user or user.get("role") != "admin":
return jsonify({"error": "Admin only"}), 403
seed_templates()
count = len(list_templates())
return jsonify({"status": "seeded", "total_templates": count})
# ─── Public routes ───────────────────────────────────────────────────────────
@templates_bp.route("/", methods=["GET"])
def list():
"""List all templates with optional filtering.
Query params:
category: Filter by category (Business, Events, Hiring, Support, E-commerce, Onboarding)
featured: 'true' for featured only
q: Search query (searches name and description)
"""
category = request.args.get("category")
featured_only = request.args.get("featured") == "true"
search = request.args.get("q")
# Strip 'all' category (treat as no filter)
if category and category.lower() == "all":
category = None
templates = list_templates(category=category, featured_only=featured_only, search=search)
# Get distinct categories for the client
categories = sorted(set(t["category"] for t in templates))
return jsonify(
{
"templates": [
{
"id": t["id"],
"slug": t["slug"],
"name": t["name"],
"description": t["description"],
"category": t["category"],
"is_featured": bool(t["is_featured"]),
"featured": bool(t["is_featured"]), # alias for frontend compat
"field_count": len(t["field_config"]),
"steps": len(set(f.get("step", 1) for f in t["field_config"])),
"has_conditionals": any(f.get("condition") for f in t["field_config"]),
}
for t in templates
],
"categories": categories,
"total": len(templates),
}
)
@templates_bp.route("/<slug>", methods=["GET"])
def get(slug):
"""Get a single template by slug.
Returns full template including field_config.
"""
template = get_template_by_slug(slug)
if not template:
return jsonify({"error": f"Template '{slug}' not found"}), 404
return jsonify(
{
"id": template["id"],
"slug": template["slug"],
"name": template["name"],
"description": template["description"],
"category": template["category"],
"is_featured": bool(template["is_featured"]),
"success_message": template.get("success_message"),
"field_config": template["field_config"],
"field_count": len(template["field_config"]),
}
)
# ─── Authenticated routes ────────────────────────────────────────────────────
@templates_bp.route("/<slug>/clone", methods=["POST"])
@login_required
def clone(slug):
"""Clone a template as a new form for the authenticated user.
Requires login. Checks tier limits before creating.
Request body (optional):
name: Override the form name (default: template name)
Returns:
The newly created site with token and field_config.
"""
template = get_template_by_slug(slug)
if not template:
return jsonify({"error": f"Template '{slug}' not found"}), 404
user_id = session["user_id"]
user = get_user(user_id)
if not user:
return jsonify({"error": "User not found"}), 404
# Check tier limits
if not can_create_site(user_id, user["tier"]):
return jsonify(
{
"error": "Site limit reached for your tier. Upgrade to create more forms.",
}
), 403
# Clone the template
import copy
field_config = copy.deepcopy(template["field_config"])
form_name = request.get_json(silent=True).get("name") or template["name"]
success, site, current_count, max_sites = accept_site_creation(
name=form_name,
owner_email=user["email"],
user_id=user_id,
user_tier=user["tier"],
field_config=field_config,
)
if not success:
return jsonify(
{
"error": "Site limit reached for your tier. Upgrade to create more forms.",
"current_count": current_count,
"max_sites": max_sites,
}
), 403
return jsonify(
{
"status": "created",
"site": {
"id": site["id"],
"token": site["token"],
"name": site["name"],
"field_config": field_config,
},
}
), 201