Files
replypier/backend/app/api/lead_capture.py
T

113 lines
4.8 KiB
Python
Raw Normal View History

"""
Copyright 2024-2026 ReplyPier
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from uuid import UUID
from app.core.logger import get_logger
from app.database import get_db
from app.core.auth import require_any_permission, require_permissions
from app.models.user import User
from app.models.agent import Agent
from app.repositories.lead_capture import LeadCaptureConfigRepository
from app.models.schemas.lead_capture import (
LeadCaptureConfigUpdate, LeadCaptureConfigResponse,
)
# Enterprise gating — Lead Capture is a Pro-plan feature where the enterprise
# module is installed; OSS/community deployments are unrestricted (same shape as
# the MCP/Workflow/Advanced gates, which are all Pro+ only).
try:
from app.enterprise.repositories.plan import PlanRepository
from app.enterprise.services.feature_access import require_accessible_subscription
HAS_ENTERPRISE = True
except ImportError:
HAS_ENTERPRISE = False
router = APIRouter()
logger = get_logger(__name__)
def check_lead_capture_access(current_user: User, db: Session) -> None:
"""Gate Lead Capture behind the Pro plan when the enterprise module is present.
Requires an accessible subscription whose plan has the 'lead_capture' feature
(Pro/Enterprise only — Free and Base do not). OSS deployments are unrestricted."""
if not HAS_ENTERPRISE:
return
# Raises 403 when the org has no accessible plan.
subscription = require_accessible_subscription(db, current_user.organization_id)
if not PlanRepository(db).check_feature_availability(str(subscription.plan_id), 'lead_capture'):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Lead Capture is not available in your current plan. Please upgrade to Pro to access this feature.",
)
def _get_owned_agent(agent_id: UUID, current_user: User, db: Session) -> Agent:
agent = db.query(Agent).filter(Agent.id == agent_id).first()
# Return 404 for both missing and cross-org agents (don't reveal existence).
if not agent or agent.organization_id != current_user.organization_id:
raise HTTPException(status_code=404, detail="Agent not found")
return agent
@router.get("/{agent_id}/lead-capture", response_model=LeadCaptureConfigResponse)
async def get_lead_capture_config(
agent_id: UUID,
current_user: User = Depends(
require_any_permission("view_agents", "manage_agents")),
db: Session = Depends(get_db),
):
"""Fetch the agent's lead-capture config, lazily creating a default (OFF) row."""
try:
_get_owned_agent(agent_id, current_user, db)
config = LeadCaptureConfigRepository(db).get_or_create(agent_id)
return config
except HTTPException:
raise
except Exception as e:
logger.error(f"Error fetching lead capture config: {str(e)}")
raise HTTPException(status_code=500, detail="Failed to fetch lead capture config")
@router.put("/{agent_id}/lead-capture", response_model=LeadCaptureConfigResponse)
async def update_lead_capture_config(
agent_id: UUID,
data: LeadCaptureConfigUpdate,
current_user: User = Depends(require_permissions("manage_agents")),
db: Session = Depends(get_db),
):
"""Full-replace the agent's lead-capture config (whole tab saved at once)."""
try:
_get_owned_agent(agent_id, current_user, db)
check_lead_capture_access(current_user, db)
# Assignment target (stored-only in phase 1) must belong to this org — reject
# cross-org user ids so the routing FK can never dangle when routing ships.
if data.assignment_target_user_id is not None:
target = db.query(User).filter(
User.id == data.assignment_target_user_id,
User.organization_id == current_user.organization_id,
).first()
if not target:
raise HTTPException(status_code=400, detail="Assignment target user not found in this organization")
config = LeadCaptureConfigRepository(db).update(agent_id, data)
return config
except HTTPException:
raise
except Exception as e:
logger.error(f"Error updating lead capture config: {str(e)}")
raise HTTPException(status_code=500, detail="Failed to update lead capture config")