from fastapi import APIRouter, HTTPException, Depends, Query from fastapi.responses import StreamingResponse from app.database import get_supabase from app.dependencies import get_current_user from app.models import LeadCreate, LeadResponse, LeadUpdate from typing import List, Optional import uuid import csv import io import logging logger = logging.getLogger(__name__) router = APIRouter(prefix="/leads", tags=["Leads"]) def _get_user_company_id(user_id: str, supabase) -> str: result = supabase.table("companies").select("id").eq("owner_id", user_id).execute() if not result.data: raise HTTPException(status_code=404, detail="Company not found") return result.data[0]["id"] def _check_leads_access(user_id: str, supabase): sub = supabase.table("subscriptions").select("plan").eq("user_id", user_id).eq("status", "active").execute() plan = sub.data[0]["plan"] if sub.data else "free" if plan not in ("starter", "business", "agency", "enterprise"): raise HTTPException(status_code=402, detail="Lead capture requires Starter plan or higher") return plan @router.get("", response_model=List[LeadResponse]) async def list_leads( chatbot_id: Optional[str] = Query(None), page: int = Query(1, ge=1), limit: int = Query(50, ge=1, le=200), user=Depends(get_current_user), ): """List leads for the user's chatbots.""" supabase = get_supabase() _check_leads_access(user.id, supabase) company_id = _get_user_company_id(user.id, supabase) # Get owned chatbot IDs chatbots_q = supabase.table("chatbots").select("id").eq("company_id", company_id) if chatbot_id: chatbots_q = chatbots_q.eq("id", chatbot_id) chatbots = chatbots_q.execute() chatbot_ids = [c["id"] for c in (chatbots.data or [])] if not chatbot_ids: return [] offset = (page - 1) * limit result = supabase.table("leads").select("*") \ .in_("chatbot_id", chatbot_ids) \ .order("created_at", desc=True) \ .range(offset, offset + limit - 1) \ .execute() return [LeadResponse(**lead) for lead in (result.data or [])] @router.patch("/{lead_id}", response_model=LeadResponse) async def update_lead( lead_id: str, data: LeadUpdate, user=Depends(get_current_user), ): """Update lead status or notes.""" supabase = get_supabase() _check_leads_access(user.id, supabase) company_id = _get_user_company_id(user.id, supabase) lead = supabase.table("leads").select("*, chatbots(company_id)") \ .eq("id", lead_id).execute() if not lead.data: raise HTTPException(status_code=404, detail="Lead not found") if lead.data[0].get("chatbots", {}).get("company_id") != company_id: raise HTTPException(status_code=403, detail="Not authorized") update_fields: dict = {} if data.status is not None: valid_statuses = ("new", "contacted", "qualified", "closed", "lost") if data.status not in valid_statuses: raise HTTPException(status_code=400, detail=f"Status must be one of {valid_statuses}") update_fields["status"] = data.status if data.notes is not None: update_fields["notes"] = data.notes if not update_fields: return LeadResponse(**lead.data[0]) result = supabase.table("leads").update(update_fields).eq("id", lead_id).execute() return LeadResponse(**result.data[0]) @router.get("/export") async def export_leads_csv( chatbot_id: Optional[str] = Query(None), user=Depends(get_current_user), ): """Export leads as CSV file.""" supabase = get_supabase() _check_leads_access(user.id, supabase) company_id = _get_user_company_id(user.id, supabase) chatbots_q = supabase.table("chatbots").select("id").eq("company_id", company_id) if chatbot_id: chatbots_q = chatbots_q.eq("id", chatbot_id) chatbots = chatbots_q.execute() chatbot_ids = [c["id"] for c in (chatbots.data or [])] if not chatbot_ids: leads_data = [] else: result = supabase.table("leads").select("*") \ .in_("chatbot_id", chatbot_ids) \ .order("created_at", desc=True) \ .execute() leads_data = result.data or [] # Build CSV output = io.StringIO() writer = csv.DictWriter(output, fieldnames=["id", "chatbot_id", "email", "name", "phone", "company", "created_at"]) writer.writeheader() for lead in leads_data: writer.writerow({ "id": lead.get("id", ""), "chatbot_id": lead.get("chatbot_id", ""), "email": lead.get("email", ""), "name": lead.get("name", ""), "phone": lead.get("phone", ""), "company": lead.get("company", ""), "created_at": lead.get("created_at", ""), }) csv_bytes = output.getvalue().encode("utf-8") return StreamingResponse( iter([csv_bytes]), media_type="text/csv", headers={"Content-Disposition": "attachment; filename=leads.csv"}, ) # Public endpoint — no auth required leads_public_router = APIRouter(tags=["Leads"]) @leads_public_router.post("/chatbots/{chatbot_id}/leads", response_model=LeadResponse, status_code=201) async def submit_lead(chatbot_id: str, data: LeadCreate): """Submit a lead for a chatbot (public endpoint, no auth required).""" supabase = get_supabase() # Verify chatbot exists chatbot = supabase.table("chatbots").select("id, lead_capture_enabled").eq("id", chatbot_id).execute() if not chatbot.data: raise HTTPException(status_code=404, detail="Chatbot not found") if not chatbot.data[0].get("lead_capture_enabled", False): raise HTTPException(status_code=400, detail="Lead capture not enabled for this chatbot") # Deduplicate by email+chatbot_id if data.email: existing = supabase.table("leads").select("*") \ .eq("chatbot_id", chatbot_id) \ .eq("email", data.email) \ .execute() if existing.data: return LeadResponse(**existing.data[0]) lead_data = { "id": str(uuid.uuid4()), "chatbot_id": chatbot_id, "conversation_id": data.conversation_id, "email": data.email, "name": data.name, "phone": data.phone, "company": data.company, } result = supabase.table("leads").insert(lead_data).execute() if not result.data: raise HTTPException(status_code=500, detail="Failed to save lead") return LeadResponse(**result.data[0])