Spaces:
Sleeping
Sleeping
| # app/api/v1/alerts.py | |
| # Alert endpoints for parents and children | |
| from fastapi import APIRouter, HTTPException, Depends | |
| from pydantic import BaseModel | |
| from typing import Optional | |
| from app.core.dependencies import get_current_user | |
| from app.db.models.user import UserDocument, UserRole | |
| from app.db.models.alert import AlertDocument, AlertStatus | |
| from app.observability.logging import get_logger | |
| from datetime import datetime | |
| logger = get_logger(__name__) | |
| router = APIRouter(prefix="/alerts", tags=["Alerts"]) | |
| async def list_alerts( | |
| page: int = 1, | |
| limit: int = 20, | |
| status: Optional[str] = None, | |
| severity: Optional[str] = None, | |
| user: UserDocument = Depends(get_current_user), | |
| ): | |
| """List alerts. Parents see all their children's alerts; children see their own.""" | |
| skip = (page - 1) * limit | |
| if user.role == UserRole.PARENT: | |
| query = AlertDocument.find(AlertDocument.parent_id == str(user.id)) | |
| else: | |
| query = AlertDocument.find(AlertDocument.child_id == str(user.id)) | |
| if status: | |
| query = query.find(AlertDocument.status == AlertStatus(status)) | |
| if severity: | |
| from app.db.models.alert import AlertSeverity | |
| query = query.find(AlertDocument.severity == AlertSeverity(severity)) | |
| alerts = await query.sort(-AlertDocument.created_at).skip(skip).limit(limit).to_list() | |
| return { | |
| "success": True, | |
| "page": page, | |
| "limit": limit, | |
| "alerts": [_fmt(a) for a in alerts], | |
| } | |
| async def get_alert( | |
| alert_id: str, | |
| user: UserDocument = Depends(get_current_user), | |
| ): | |
| alert = await AlertDocument.get(alert_id) | |
| if not alert: | |
| raise HTTPException(status_code=404, detail="Alert not found") | |
| _check_access(alert, user) | |
| return {"success": True, "alert": _fmt(alert)} | |
| class AcknowledgeRequest(BaseModel): | |
| resolution_notes: Optional[str] = None | |
| async def acknowledge_alert( | |
| alert_id: str, | |
| user: UserDocument = Depends(get_current_user), | |
| ): | |
| alert = await AlertDocument.get(alert_id) | |
| if not alert: | |
| raise HTTPException(status_code=404, detail="Alert not found") | |
| _check_access(alert, user) | |
| alert.status = AlertStatus.ACKNOWLEDGED | |
| alert.acknowledged_at = datetime.utcnow() | |
| alert.updated_at = datetime.utcnow() | |
| await alert.save() | |
| return {"success": True, "alert": _fmt(alert)} | |
| async def resolve_alert( | |
| alert_id: str, | |
| body: AcknowledgeRequest, | |
| user: UserDocument = Depends(get_current_user), | |
| ): | |
| if user.role != UserRole.PARENT: | |
| raise HTTPException(status_code=403, detail="Only parents can resolve alerts") | |
| alert = await AlertDocument.get(alert_id) | |
| if not alert: | |
| raise HTTPException(status_code=404, detail="Alert not found") | |
| if alert.parent_id != str(user.id): | |
| raise HTTPException(status_code=403, detail="Cannot resolve this alert") | |
| alert.status = AlertStatus.RESOLVED | |
| alert.resolved_at = datetime.utcnow() | |
| alert.resolved_by = str(user.id) | |
| alert.resolution_notes = body.resolution_notes | |
| alert.updated_at = datetime.utcnow() | |
| await alert.save() | |
| return {"success": True, "alert": _fmt(alert)} | |
| async def alert_stats(user: UserDocument = Depends(get_current_user)): | |
| if user.role != UserRole.PARENT: | |
| raise HTTPException(status_code=403, detail="Only parents can view stats") | |
| alerts = await AlertDocument.find( | |
| AlertDocument.parent_id == str(user.id) | |
| ).to_list() | |
| by_severity: dict = {} | |
| by_status: dict = {} | |
| by_category: dict = {} | |
| for a in alerts: | |
| by_severity[a.severity.value] = by_severity.get(a.severity.value, 0) + 1 | |
| by_status[a.status.value] = by_status.get(a.status.value, 0) + 1 | |
| for c in a.categories: | |
| by_category[c] = by_category.get(c, 0) + 1 | |
| return { | |
| "success": True, | |
| "total": len(alerts), | |
| "by_severity": by_severity, | |
| "by_status": by_status, | |
| "by_category": by_category, | |
| } | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Helpers | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _check_access(alert: AlertDocument, user: UserDocument): | |
| if alert.parent_id != str(user.id) and alert.child_id != str(user.id): | |
| raise HTTPException(status_code=403, detail="Cannot access this alert") | |
| def _fmt(a: AlertDocument) -> dict: | |
| return { | |
| "id": str(a.id), | |
| "child_id": a.child_id, | |
| "parent_id": a.parent_id, | |
| "title": a.title, | |
| "message": a.message, | |
| "guidance": a.guidance, | |
| "severity": a.severity.value, | |
| "categories": a.categories, | |
| "status": a.status.value, | |
| "created_at": a.created_at.isoformat(), | |
| "acknowledged_at": a.acknowledged_at.isoformat() if a.acknowledged_at else None, | |
| "resolved_at": a.resolved_at.isoformat() if a.resolved_at else None, | |
| } | |