myrmidon / python /src /server /api_routes /marketing_api.py
tek Atrust
chore(deploy): build monolithic server for Hugging Face
d5ef46f
Raw
History Blame Contribute Delete
7.8 kB
"""
Marketing API Hardened - Entry point for Alice & Bob workflows.
Standardized RBAC Sealing with Full Test Compatibility.
"""
import logging
from fastapi import APIRouter, Depends, HTTPException, Query
from src.server.schemas.marketing import (
ApprovalRequest,
DraftBlogRequest,
DraftFromLeadsRequest,
LeadCreateRequest,
LeadUpdateRequest,
LogoRequest,
PitchRequest,
PitchResponse,
PromoteLeadRequest,
RejectSuggestionRequest,
)
from src.server.services.marketing_service import MarketingService
from ..auth.dependencies import get_current_user, requires_permission
from ..auth.permissions import (
AGENT_TRIGGER_MKT,
BRAND_ASSET_MANAGE,
CONTENT_PUBLISH,
TASK_READ_TEAM,
)
logger = logging.getLogger(__name__)
router: APIRouter = APIRouter(prefix="/api/marketing", tags=["marketing"])
def get_marketing_service():
"""Export for test compatibility."""
return MarketingService()
def _err(msg: str, code: int = 403):
raise HTTPException(status_code=code, detail=msg)
@router.get("/jobs")
async def search_jobs(keyword: str = Query(...), limit: int = 8, current_user: dict = Depends(get_current_user)):
service = MarketingService()
return await service.search_jobs(keyword, limit)
@router.get("/leads")
async def list_leads(current_user: dict = Depends(get_current_user)):
user_id = str(current_user.get("id"))
role = current_user.get("role", "member")
logger.info(f"DEBUG: list_leads accessed by UserID: {user_id}, Role: {role}")
service = MarketingService()
return await service.list_leads(user_id=user_id, role=role)
@router.post("/leads")
async def create_lead(req: LeadCreateRequest, current_user: dict = Depends(get_current_user)):
service = MarketingService()
success, res = await service.create_lead(req.model_dump(), creator_id=str(current_user.get("id")))
if not success:
_err(str(res), 400)
return res
@router.post("/leads/reset")
async def reset_leads(current_user: dict = Depends(requires_permission(CONTENT_PUBLISH))):
service = MarketingService()
success = await service.reset_leads()
return {"success": success}
@router.patch("/leads/{lead_id}")
async def update_lead(lead_id: str, req: LeadUpdateRequest, current_user: dict = Depends(get_current_user)):
service = MarketingService()
success, res = await service.update_lead(lead_id, req.model_dump(exclude_unset=True))
if not success:
_err(str(res), 400)
return res
@router.post("/leads/{lead_id}/promote")
async def promote_lead(lead_id: str, req: PromoteLeadRequest, current_user: dict = Depends(get_current_user)):
service = MarketingService()
success, res = await service.promote_to_vendor(lead_id, **req.model_dump(), owner_id=str(current_user.get("id")))
if not success:
_err(str(res), 400)
return res
@router.post("/generate-pitch", response_model=PitchResponse)
async def generate_pitch(req: PitchRequest, current_user: dict = Depends(get_current_user)):
role = current_user.get("role", "viewer").lower()
if role not in ["system_admin", "admin", "marketing", "sales"]:
raise HTTPException(status_code=403, detail=f"Role '{role}' is unauthorized to generate pitches")
service = MarketingService()
res = await service.generate_pitch(req.company, req.job_title)
if isinstance(res, dict) and "error_code" in res:
raise HTTPException(status_code=res["error_code"], detail=res["message"])
return res
@router.post("/draft-blog")
async def draft_blog_post(req: DraftBlogRequest, current_user: dict = Depends(requires_permission(AGENT_TRIGGER_MKT))):
service = MarketingService()
success, res = await service.draft_blog(req.topic, req.industry, req.keywords)
if not success:
_err(res.get("message", "AI Draft failed"), res.get("error_code", 500))
return res
@router.post("/draft-from-leads")
async def draft_from_leads(
req: DraftFromLeadsRequest, current_user: dict = Depends(requires_permission(AGENT_TRIGGER_MKT))
):
service = MarketingService()
success, res = await service.draft_from_leads(req.lead_ids)
if not success:
_err(res.get("message", "AI Draft from leads failed"), res.get("error_code", 500))
return res
@router.post("/blog/{post_id}/submit")
async def submit_blog(post_id: str, current_user: dict = Depends(get_current_user)):
service = MarketingService()
success, res = await service.submit_blog(post_id)
if not success:
_err(res.get("error", "Submission failed"), 400)
return res
@router.post("/generate-logo")
async def generate_logo(req: LogoRequest, current_user: dict = Depends(requires_permission(BRAND_ASSET_MANAGE))):
service = MarketingService()
return await service.generate_visual_asset(req.style)
@router.get("/sources")
async def get_combined_sources(current_user: dict = Depends(get_current_user)):
service = MarketingService()
return await service.get_combined_sources(user_id=str(current_user.get("id")))
@router.get("/context/{source_id}")
async def get_content_context(
source_id: str, source_type: str = Query("lead"), current_user: dict = Depends(get_current_user)
):
service = MarketingService()
return await service.get_content_context(source_id, source_type)
@router.get("/manager/alerts")
async def get_manager_alerts(current_user: dict = Depends(requires_permission(TASK_READ_TEAM))):
from ..utils import get_supabase_client
sb = get_supabase_client()
res = (
sb.table("archon_logs")
.select("*")
.eq("level", "ALERT")
.in_("source", ["sentinel", "twin_scout", "LeadScoring"])
.order("created_at", desc=True)
.limit(50)
.execute()
)
return res.data or []
@router.post("/manager/sentinel/run")
async def run_sentinel_manual(current_user: dict = Depends(requires_permission(AGENT_TRIGGER_MKT))):
service = MarketingService()
return await service.run_sentinel()
@router.get("/stats")
async def get_marketing_stats(current_user: dict = Depends(get_current_user)):
service = MarketingService()
return await service.get_marketing_stats()
@router.get("/trends")
async def get_marketing_trends(current_user: dict = Depends(get_current_user)):
service = MarketingService()
return await service.get_marketing_trends()
@router.get("/intelligence")
async def get_marketing_intelligence(current_user: dict = Depends(get_current_user)):
from ..services.stats.metrics import MetricsManager
metrics = MetricsManager()
return await metrics.get_marketing_intelligence(user_id=str(current_user.get("id")))
@router.post("/knowledge/seed")
async def seed_knowledge_base(current_user: dict = Depends(requires_permission(CONTENT_PUBLISH))):
service = MarketingService()
return await service.seed_knowledge()
@router.get("/approvals")
async def get_pending_approvals(current_user: dict = Depends(requires_permission(TASK_READ_TEAM))):
service = MarketingService()
return await service.get_pending_approvals()
@router.post("/approvals/reject-suggestion")
async def generate_reject_suggestion(
req: RejectSuggestionRequest,
current_user: dict = Depends(requires_permission(CONTENT_PUBLISH)),
):
service = MarketingService()
return await service.generate_reject_suggestion(req.item_type, req.item_id)
@router.post("/approvals/{item_type}/{item_id}/{action}")
async def process_approval(
item_type: str,
item_id: str,
action: str,
req: ApprovalRequest,
current_user: dict = Depends(requires_permission(CONTENT_PUBLISH)),
):
service = MarketingService()
success = await service.process_approval(item_type, item_id, action, req.notes)
return {"success": success}