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
« 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
10router = APIRouter(prefix="/alerts", tags=["alerts"])
11alert_service = AlertService()
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
33class UnreadCountResponse(BaseModel):
34 """Unread alert count response"""
35 count: int
38class MarkReadRequest(BaseModel):
39 """Mark alert as read request"""
40 alert_id: str
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
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
63 except Exception as e:
64 raise HTTPException(status_code=500, detail=f"Error fetching alerts: {str(e)}")
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}
76 except Exception as e:
77 raise HTTPException(status_code=500, detail=f"Error fetching unread count: {str(e)}")
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)
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")
96 except Exception as e:
97 raise HTTPException(status_code=500, detail=f"Error marking alert as read: {str(e)}")
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'])
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")
113 except Exception as e:
114 raise HTTPException(status_code=500, detail=f"Error marking alerts as read: {str(e)}")
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()
126 query = """
127 SELECT * FROM user_notification_preferences
128 WHERE user_id = %s
129 """
130 result = db.execute_query(query, (user['id'],))
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 }
150 except Exception as e:
151 raise HTTPException(status_code=500, detail=f"Error fetching preferences: {str(e)}")
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
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()
181 # Build update query dynamically based on provided fields
182 updates = []
183 values = []
185 for field, value in request.dict(exclude_none=True).items():
186 updates.append(f"{field} = %s")
187 values.append(value)
189 if not updates:
190 raise HTTPException(status_code=400, detail="No preferences to update")
192 values.append(user['id'])
194 query = f"""
195 UPDATE user_notification_preferences
196 SET {', '.join(updates)}, updated_at = NOW()
197 WHERE user_id = %s
198 RETURNING *
199 """
201 result = db.execute_query(query, tuple(values))
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")
208 except HTTPException:
209 raise
210 except Exception as e:
211 raise HTTPException(status_code=500, detail=f"Error updating preferences: {str(e)}")