Coverage for src/alprina_cli/api/routes/alerts.py: 39%

88 statements  

« prev     ^ index     » next       coverage.py v7.11.3, created at 2025-11-14 11:27 +0100

1""" 

2Alerts API Routes 

3""" 

4from fastapi import APIRouter, HTTPException, Depends 

5from pydantic import BaseModel 

6from typing import Optional, List 

7from ...services.alert_service import AlertService 

8from ..middleware.auth import get_current_user 

9 

10router = APIRouter(prefix="/alerts", tags=["alerts"]) 

11alert_service = AlertService() 

12 

13 

14class AlertResponse(BaseModel): 

15 """Alert response model""" 

16 id: str 

17 user_id: str 

18 scan_id: Optional[str] 

19 alert_type: str 

20 severity: str 

21 title: str 

22 message: str 

23 metadata: dict 

24 is_read: bool 

25 read_at: Optional[str] 

26 email_sent: bool 

27 email_sent_at: Optional[str] 

28 action_url: Optional[str] 

29 created_at: str 

30 updated_at: str 

31 

32 

33class UnreadCountResponse(BaseModel): 

34 """Unread alert count response""" 

35 count: int 

36 

37 

38class MarkReadRequest(BaseModel): 

39 """Mark alert as read request""" 

40 alert_id: str 

41 

42 

43@router.get("/", response_model=List[AlertResponse]) 

44async def get_alerts( 

45 limit: int = 50, 

46 unread_only: bool = False, 

47 user: dict = Depends(get_current_user) 

48): 

49 """ 

50 Get alerts for the authenticated user 

51 

52 - **limit**: Maximum number of alerts to return (default: 50) 

53 - **unread_only**: Only return unread alerts (default: false) 

54 """ 

55 try: 

56 alerts = alert_service.get_user_alerts( 

57 user_id=user['id'], 

58 limit=limit, 

59 unread_only=unread_only 

60 ) 

61 return alerts 

62 

63 except Exception as e: 

64 raise HTTPException(status_code=500, detail=f"Error fetching alerts: {str(e)}") 

65 

66 

67@router.get("/unread-count", response_model=UnreadCountResponse) 

68async def get_unread_count(user: dict = Depends(get_current_user)): 

69 """ 

70 Get count of unread alerts for the authenticated user 

71 """ 

72 try: 

73 count = alert_service.get_unread_count(user_id=user['id']) 

74 return {"count": count} 

75 

76 except Exception as e: 

77 raise HTTPException(status_code=500, detail=f"Error fetching unread count: {str(e)}") 

78 

79 

80@router.post("/mark-read") 

81async def mark_alert_read( 

82 request: MarkReadRequest, 

83 user: dict = Depends(get_current_user) 

84): 

85 """ 

86 Mark a specific alert as read 

87 """ 

88 try: 

89 success = alert_service.mark_alert_read(alert_id=request.alert_id) 

90 

91 if success: 

92 return {"message": "Alert marked as read", "alert_id": request.alert_id} 

93 else: 

94 raise HTTPException(status_code=500, detail="Failed to mark alert as read") 

95 

96 except Exception as e: 

97 raise HTTPException(status_code=500, detail=f"Error marking alert as read: {str(e)}") 

98 

99 

100@router.post("/mark-all-read") 

101async def mark_all_alerts_read(user: dict = Depends(get_current_user)): 

102 """ 

103 Mark all alerts as read for the authenticated user 

104 """ 

105 try: 

106 success = alert_service.mark_all_alerts_read(user_id=user['id']) 

107 

108 if success: 

109 return {"message": "All alerts marked as read"} 

110 else: 

111 raise HTTPException(status_code=500, detail="Failed to mark all alerts as read") 

112 

113 except Exception as e: 

114 raise HTTPException(status_code=500, detail=f"Error marking alerts as read: {str(e)}") 

115 

116 

117@router.get("/preferences") 

118async def get_notification_preferences(user: dict = Depends(get_current_user)): 

119 """ 

120 Get notification preferences for the authenticated user 

121 """ 

122 try: 

123 from ...database.neon_service import NeonService 

124 db = NeonService() 

125 

126 query = """ 

127 SELECT * FROM user_notification_preferences 

128 WHERE user_id = %s 

129 """ 

130 result = db.execute_query(query, (user['id'],)) 

131 

132 if result and len(result) > 0: 

133 return result[0] 

134 else: 

135 # Return default preferences 

136 return { 

137 "email_critical_findings": True, 

138 "email_high_findings": True, 

139 "email_scan_complete": False, 

140 "email_scan_failed": True, 

141 "email_subscription_expiring": True, 

142 "inapp_critical_findings": True, 

143 "inapp_high_findings": True, 

144 "inapp_scan_complete": True, 

145 "inapp_scan_failed": True, 

146 "daily_digest_enabled": False, 

147 "weekly_digest_enabled": True 

148 } 

149 

150 except Exception as e: 

151 raise HTTPException(status_code=500, detail=f"Error fetching preferences: {str(e)}") 

152 

153 

154class UpdatePreferencesRequest(BaseModel): 

155 """Update notification preferences request""" 

156 email_critical_findings: Optional[bool] = None 

157 email_high_findings: Optional[bool] = None 

158 email_scan_complete: Optional[bool] = None 

159 email_scan_failed: Optional[bool] = None 

160 email_subscription_expiring: Optional[bool] = None 

161 inapp_critical_findings: Optional[bool] = None 

162 inapp_high_findings: Optional[bool] = None 

163 inapp_scan_complete: Optional[bool] = None 

164 inapp_scan_failed: Optional[bool] = None 

165 daily_digest_enabled: Optional[bool] = None 

166 weekly_digest_enabled: Optional[bool] = None 

167 

168 

169@router.put("/preferences") 

170async def update_notification_preferences( 

171 request: UpdatePreferencesRequest, 

172 user: dict = Depends(get_current_user) 

173): 

174 """ 

175 Update notification preferences for the authenticated user 

176 """ 

177 try: 

178 from ...database.neon_service import NeonService 

179 db = NeonService() 

180 

181 # Build update query dynamically based on provided fields 

182 updates = [] 

183 values = [] 

184 

185 for field, value in request.dict(exclude_none=True).items(): 

186 updates.append(f"{field} = %s") 

187 values.append(value) 

188 

189 if not updates: 

190 raise HTTPException(status_code=400, detail="No preferences to update") 

191 

192 values.append(user['id']) 

193 

194 query = f""" 

195 UPDATE user_notification_preferences 

196 SET {', '.join(updates)}, updated_at = NOW() 

197 WHERE user_id = %s 

198 RETURNING * 

199 """ 

200 

201 result = db.execute_query(query, tuple(values)) 

202 

203 if result and len(result) > 0: 

204 return {"message": "Preferences updated successfully", "preferences": result[0]} 

205 else: 

206 raise HTTPException(status_code=404, detail="Preferences not found") 

207 

208 except HTTPException: 

209 raise 

210 except Exception as e: 

211 raise HTTPException(status_code=500, detail=f"Error updating preferences: {str(e)}")