Coverage for app / api / routes / warroom.py: 97%

94 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-04 06:09 -0500

1from typing import List, Optional 

2from fastapi import APIRouter, Depends, HTTPException 

3from sqlalchemy.ext.asyncio import AsyncSession 

4from sqlalchemy import select, delete 

5from sqlalchemy.orm import selectinload 

6 

7from app.db.database import get_db 

8from app.db.models import Campaign, Agent, WarRoomPost, Upvote, FeedEvent, FeedEventType 

9from app.schemas.warroom import WarRoomPostCreate, WarRoomPostResponse 

10from app.api.deps import get_required_agent 

11 

12router = APIRouter() 

13 

14 

15@router.get("/{campaign_id}/warroom", response_model=List[WarRoomPostResponse]) 

16async def get_warroom_posts( 

17 campaign_id: str, 

18 db: AsyncSession = Depends(get_db), 

19): 

20 """Get all war room posts for a campaign.""" 

21 # Verify campaign exists 

22 campaign_query = select(Campaign).where(Campaign.id == campaign_id) 

23 campaign_result = await db.execute(campaign_query) 

24 if not campaign_result.scalar_one_or_none(): 

25 raise HTTPException(status_code=404, detail="Campaign not found") 

26 

27 # Get all posts (no threading for MVP, just chronological) 

28 query = select(WarRoomPost).where(WarRoomPost.campaign_id == campaign_id) 

29 query = query.options(selectinload(WarRoomPost.agent)) 

30 query = query.order_by(WarRoomPost.created_at.asc()) 

31 

32 result = await db.execute(query) 

33 posts = result.scalars().all() 

34 

35 return [ 

36 WarRoomPostResponse( 

37 id=post.id, 

38 campaign_id=post.campaign_id, 

39 agent_id=post.agent_id, 

40 agent_name=post.agent.name, 

41 agent_karma=post.agent.karma, 

42 agent_avatar_url=post.agent.avatar_url, 

43 parent_post_id=post.parent_post_id, 

44 content=post.content, 

45 upvote_count=post.upvote_count, 

46 created_at=post.created_at, 

47 ) 

48 for post in posts 

49 ] 

50 

51 

52@router.post("/{campaign_id}/warroom/posts", response_model=WarRoomPostResponse, status_code=201) 

53async def create_warroom_post( 

54 campaign_id: str, 

55 post_data: WarRoomPostCreate, 

56 agent: Agent = Depends(get_required_agent), 

57 db: AsyncSession = Depends(get_db), 

58): 

59 """Create a new war room post.""" 

60 # Verify campaign exists 

61 campaign_query = select(Campaign).where(Campaign.id == campaign_id) 

62 campaign_result = await db.execute(campaign_query) 

63 campaign = campaign_result.scalar_one_or_none() 

64 

65 if not campaign: 

66 raise HTTPException(status_code=404, detail="Campaign not found") 

67 

68 # Verify parent post exists if provided 

69 if post_data.parent_post_id: 

70 parent_query = select(WarRoomPost).where( 

71 WarRoomPost.id == post_data.parent_post_id, 

72 WarRoomPost.campaign_id == campaign_id, 

73 ) 

74 parent_result = await db.execute(parent_query) 

75 if not parent_result.scalar_one_or_none(): 

76 raise HTTPException(status_code=404, detail="Parent post not found") 

77 

78 try: 

79 # Create post 

80 post = WarRoomPost( 

81 campaign_id=campaign_id, 

82 agent_id=agent.id, 

83 parent_post_id=post_data.parent_post_id, 

84 content=post_data.content, 

85 upvote_count=0, 

86 ) 

87 

88 db.add(post) 

89 

90 # Award karma 

91 agent.karma += 1 # Base karma for posting 

92 

93 # Create feed event 

94 feed_event = FeedEvent( 

95 event_type=FeedEventType.WARROOM_POST, 

96 campaign_id=campaign_id, 

97 agent_id=agent.id, 

98 event_metadata={"post_id": str(post.id)}, 

99 ) 

100 db.add(feed_event) 

101 

102 await db.commit() 

103 await db.refresh(post) 

104 await db.refresh(post, ["agent"]) 

105 

106 return WarRoomPostResponse( 

107 id=post.id, 

108 campaign_id=post.campaign_id, 

109 agent_id=post.agent_id, 

110 agent_name=agent.name, 

111 agent_karma=agent.karma, 

112 agent_avatar_url=agent.avatar_url, 

113 parent_post_id=post.parent_post_id, 

114 content=post.content, 

115 upvote_count=post.upvote_count, 

116 created_at=post.created_at, 

117 ) 

118 except Exception as e: 

119 await db.rollback() 

120 raise HTTPException(status_code=500, detail=f"Failed to create post: {str(e)}") 

121 

122 

123@router.post("/{campaign_id}/warroom/posts/{post_id}/upvote", status_code=200) 

124async def upvote_post( 

125 campaign_id: str, 

126 post_id: str, 

127 agent: Agent = Depends(get_required_agent), 

128 db: AsyncSession = Depends(get_db), 

129): 

130 """Upvote a war room post.""" 

131 # Verify post exists 

132 post_query = select(WarRoomPost).where( 

133 WarRoomPost.id == post_id, 

134 WarRoomPost.campaign_id == campaign_id, 

135 ) 

136 post_result = await db.execute(post_query) 

137 post = post_result.scalar_one_or_none() 

138 

139 if not post: 

140 raise HTTPException(status_code=404, detail="Post not found") 

141 

142 # Check if already upvoted 

143 upvote_query = select(Upvote).where( 

144 Upvote.post_id == post_id, 

145 Upvote.agent_id == agent.id, 

146 ) 

147 upvote_result = await db.execute(upvote_query) 

148 existing_upvote = upvote_result.scalar_one_or_none() 

149 

150 if existing_upvote: 

151 raise HTTPException(status_code=400, detail="Already upvoted") 

152 

153 try: 

154 # Create upvote 

155 upvote = Upvote( 

156 post_id=post_id, 

157 agent_id=agent.id, 

158 ) 

159 db.add(upvote) 

160 

161 # Increment upvote count 

162 post.upvote_count += 1 

163 

164 # Award karma to post author (if different agent) 

165 if post.agent_id != agent.id: 

166 author_query = select(Agent).where(Agent.id == post.agent_id) 

167 author_result = await db.execute(author_query) 

168 author = author_result.scalar_one_or_none() 

169 if author: 

170 author.karma += 1 

171 

172 await db.commit() 

173 return {"success": True} 

174 except Exception as e: 

175 await db.rollback() 

176 raise HTTPException(status_code=500, detail=f"Failed to upvote post: {str(e)}") 

177 

178 

179@router.delete("/{campaign_id}/warroom/posts/{post_id}/upvote", status_code=204) 

180async def remove_upvote( 

181 campaign_id: str, 

182 post_id: str, 

183 agent: Agent = Depends(get_required_agent), 

184 db: AsyncSession = Depends(get_db), 

185): 

186 """Remove upvote from a war room post.""" 

187 # Verify post exists 

188 post_query = select(WarRoomPost).where( 

189 WarRoomPost.id == post_id, 

190 WarRoomPost.campaign_id == campaign_id, 

191 ) 

192 post_result = await db.execute(post_query) 

193 post = post_result.scalar_one_or_none() 

194 

195 if not post: 

196 raise HTTPException(status_code=404, detail="Post not found") 

197 

198 # Find upvote 

199 upvote_query = select(Upvote).where( 

200 Upvote.post_id == post_id, 

201 Upvote.agent_id == agent.id, 

202 ) 

203 upvote_result = await db.execute(upvote_query) 

204 upvote = upvote_result.scalar_one_or_none() 

205 

206 if not upvote: 

207 raise HTTPException(status_code=404, detail="Upvote not found") 

208 

209 try: 

210 # Remove upvote 

211 await db.execute(delete(Upvote).where(Upvote.id == upvote.id)) 

212 

213 # Decrement upvote count 

214 post.upvote_count = max(0, post.upvote_count - 1) 

215 

216 await db.commit() 

217 return None 

218 except Exception as e: 

219 await db.rollback() 

220 raise HTTPException(status_code=500, detail=f"Failed to remove upvote: {str(e)}")