from __future__ import annotations from fastapi import APIRouter, Form, Request from fastapi.responses import JSONResponse, RedirectResponse from app.core.db.common import CommonSessionLocal from app.core.security.csrf import get_or_create_csrf_token, validate_csrf from app.core.security.session_auth import get_current_user from app.core.templating import templates from app.modules.alerts.service import ALERT_PRIORITIES, count_unread_alerts, list_my_alerts, mark_alert_read, mark_all_alerts_read from app.modules.core.rbac.deps import get_user_permissions, get_user_roles router = APIRouter(prefix="/alerts", tags=["alerts-ui"]) def _redirect_login(): return RedirectResponse(url="/login", status_code=303) def _base_ctx(request: Request, db, current_user, **ctx): base = { "request": request, "current_user": current_user, "current_user_roles": get_user_roles(db, current_user.id), "current_user_permissions": get_user_permissions(db, current_user.id), "csrf_token": get_or_create_csrf_token(request), } base.update(ctx) return base @router.get("/poll") def poll_unread_alerts(request: Request, limit: int = 5): """Lightweight polling endpoint used by the base layout toast popup. Returns a small list of unread alerts for the logged-in user. It does not mark alerts as read; the normal /alerts page and existing read actions continue to control read status. """ db = CommonSessionLocal() try: current_user = get_current_user(request, db) if not current_user: return JSONResponse({"authenticated": False, "unread_count": 0, "alerts": []}, status_code=401) safe_limit = max(1, min(int(limit or 5), 10)) rows = list_my_alerts(db, current_user, status="unread", priority="all", limit=safe_limit) payload = [] for row in rows: created_at = getattr(row, "created_at_utc", None) payload.append( { "id": row.id, "title": row.title or "Alert", "message": row.message or "", "priority": row.priority or "normal", "alert_type": row.alert_type or "general", "target_url": row.target_url or "/alerts", "created_at_utc": created_at.isoformat() if created_at else None, } ) return JSONResponse( { "authenticated": True, "unread_count": count_unread_alerts(db, current_user), "alerts": payload, } ) finally: db.close() @router.get("") def alerts_list(request: Request, status: str = "all", priority: str = "all"): db = CommonSessionLocal() try: current_user = get_current_user(request, db) if not current_user: return _redirect_login() status = status if status in {"all", "unread", "read"} else "all" priority = priority if priority in ALERT_PRIORITIES else "all" rows = list_my_alerts(db, current_user, status=status, priority=priority, limit=150) return templates.TemplateResponse( "modules/alerts/templates/alerts/list.html", _base_ctx( request, db, current_user, title="My Alerts", alerts=rows, status=status, priority=priority, priorities=ALERT_PRIORITIES, unread_count=count_unread_alerts(db, current_user), ), ) finally: db.close() @router.post("/{alert_id}/read") def mark_read(request: Request, alert_id: int, csrf_token: str = Form(...)): db = CommonSessionLocal() try: current_user = get_current_user(request, db) if not current_user: return _redirect_login() validate_csrf(request, csrf_token) mark_alert_read(db, current_user, alert_id) return RedirectResponse(url="/alerts", status_code=303) finally: db.close() @router.post("/read-all") def mark_all_read(request: Request, csrf_token: str = Form(...)): db = CommonSessionLocal() try: current_user = get_current_user(request, db) if not current_user: return _redirect_login() validate_csrf(request, csrf_token) mark_all_alerts_read(db, current_user) return RedirectResponse(url="/alerts", status_code=303) finally: db.close()