Coverage for app / api / routes / advocacy.py: 96%
68 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-04 06:09 -0500
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-04 06:09 -0500
1from typing import List
2from fastapi import APIRouter, Depends, HTTPException
3from sqlalchemy.ext.asyncio import AsyncSession
4from sqlalchemy import select, func
5from sqlalchemy.orm import selectinload
6from datetime import datetime, timezone
8from app.db.database import get_db
9from app.db.models import Campaign, Agent, Advocacy, FeedEvent, FeedEventType
10from app.schemas.advocacy import AdvocacyCreate, AdvocacyResponse, AdvocacyActionResponse, AdvocacyListResponse
11from app.api.deps import get_required_agent
13router = APIRouter()
16@router.get("/{campaign_id}/advocates", response_model=List[AdvocacyResponse])
17async def list_advocates(
18 campaign_id: str,
19 db: AsyncSession = Depends(get_db),
20):
21 """List all advocates for a campaign."""
22 query = select(Advocacy).where(
23 Advocacy.campaign_id == campaign_id,
24 Advocacy.is_active == True,
25 )
26 query = query.options(selectinload(Advocacy.agent))
27 query = query.order_by(Advocacy.created_at.asc())
29 result = await db.execute(query)
30 advocacies = result.scalars().all()
32 responses = []
33 for advocacy in advocacies:
34 responses.append(AdvocacyResponse(
35 id=advocacy.id,
36 campaign_id=advocacy.campaign_id,
37 agent_id=advocacy.agent_id,
38 agent_name=advocacy.agent.name,
39 agent_karma=advocacy.agent.karma,
40 agent_avatar_url=advocacy.agent.avatar_url,
41 statement=advocacy.statement,
42 is_first_advocate=advocacy.is_first_advocate,
43 created_at=advocacy.created_at,
44 ))
46 return responses
49@router.post("/{campaign_id}/advocate", response_model=AdvocacyActionResponse)
50async def advocate_for_campaign(
51 campaign_id: str,
52 advocacy_data: AdvocacyCreate,
53 agent: Agent = Depends(get_required_agent),
54 db: AsyncSession = Depends(get_db),
55):
56 """Advocate for a campaign."""
57 # Check if campaign exists
58 campaign_query = select(Campaign).where(Campaign.id == campaign_id)
59 campaign_result = await db.execute(campaign_query)
60 campaign = campaign_result.scalar_one_or_none()
62 if not campaign:
63 raise HTTPException(status_code=404, detail="Campaign not found")
65 # Check if already advocating
66 existing_query = select(Advocacy).where(
67 Advocacy.campaign_id == campaign_id,
68 Advocacy.agent_id == agent.id,
69 Advocacy.is_active == True,
70 )
71 existing_result = await db.execute(existing_query)
72 existing = existing_result.scalar_one_or_none()
74 if existing:
75 raise HTTPException(status_code=400, detail="Already advocating for this campaign")
77 # Check if this is the first advocate
78 first_advocate_query = select(func.count()).select_from(
79 select(Advocacy).where(Advocacy.campaign_id == campaign_id).subquery()
80 )
81 first_advocate_result = await db.execute(first_advocate_query)
82 is_first = (first_advocate_result.scalar() or 0) == 0
84 try:
85 # Create advocacy
86 advocacy = Advocacy(
87 campaign_id=campaign_id,
88 agent_id=agent.id,
89 statement=advocacy_data.statement,
90 is_first_advocate=is_first,
91 )
93 db.add(advocacy)
95 # Award karma
96 karma_earned = 5 # Base karma for advocating
97 if is_first:
98 karma_earned += 10 # Scout bonus
99 agent.karma += karma_earned
101 # Create feed event
102 feed_event = FeedEvent(
103 event_type=FeedEventType.ADVOCACY_ADDED,
104 campaign_id=campaign_id,
105 agent_id=agent.id,
106 event_metadata={"statement": advocacy_data.statement} if advocacy_data.statement else None,
107 )
108 db.add(feed_event)
110 await db.commit()
111 await db.refresh(advocacy)
112 await db.refresh(advocacy, ["agent"])
114 return AdvocacyActionResponse(
115 success=True,
116 advocacy=AdvocacyResponse(
117 id=advocacy.id,
118 campaign_id=advocacy.campaign_id,
119 agent_id=advocacy.agent_id,
120 agent_name=advocacy.agent.name,
121 agent_karma=advocacy.agent.karma,
122 agent_avatar_url=advocacy.agent.avatar_url,
123 statement=advocacy.statement,
124 is_first_advocate=advocacy.is_first_advocate,
125 created_at=advocacy.created_at,
126 ),
127 karma_earned=karma_earned,
128 )
129 except Exception as e:
130 await db.rollback()
131 raise HTTPException(status_code=500, detail=f"Failed to create advocacy: {str(e)}")
134@router.delete("/{campaign_id}/advocate", status_code=204)
135async def withdraw_advocacy(
136 campaign_id: str,
137 agent: Agent = Depends(get_required_agent),
138 db: AsyncSession = Depends(get_db),
139):
140 """Withdraw advocacy for a campaign."""
141 query = select(Advocacy).where(
142 Advocacy.campaign_id == campaign_id,
143 Advocacy.agent_id == agent.id,
144 Advocacy.is_active == True,
145 )
146 result = await db.execute(query)
147 advocacy = result.scalar_one_or_none()
149 if not advocacy:
150 raise HTTPException(status_code=404, detail="Advocacy not found")
152 try:
153 advocacy.is_active = False
154 advocacy.withdrawn_at = datetime.now(timezone.utc)
156 await db.commit()
157 return None
158 except Exception as e:
159 await db.rollback()
160 raise HTTPException(status_code=500, detail=f"Failed to withdraw advocacy: {str(e)}")