"""Operational feed API. Spec 041 ยท OF-API-002.""" from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel, Field from app import auth from app.ops_inbox_store import ( add_message, get_event, inbox_stats, init_inbox_schema, list_events, patch_event, ) from app.permissions import can_manage_users router = APIRouter(prefix="/api/v1/ops-inbox", tags=["ops-inbox"]) class InboxMessageBody(BaseModel): body: str = Field(min_length=1, max_length=8000) note_type: str = Field(default="reply", pattern="^(reply|internal_note)$") class InboxPatchBody(BaseModel): status: str | None = Field(default=None, pattern="^(open|pending|resolved)$") assignee: str | None = None priority: str | None = Field(default=None, pattern="^(normal|high|critical)$") def _db(): conn = auth.db() try: init_inbox_schema(conn) yield conn finally: conn.close() def _require_inbox(user: auth.DeskUser) -> None: if not can_manage_users(user.role): raise HTTPException(403, "insufficient permissions") @router.get("/stats") def inbox_statistics( user: auth.DeskUser = Depends(auth.get_current_user), conn=Depends(_db), ): _require_inbox(user) return inbox_stats(conn) @router.get("/events") def inbox_events( channel: str | None = Query(default="all"), priority: str | None = Query(default=None), status: str | None = Query(default=None), q: str | None = Query(default=None), limit: int = Query(default=128, ge=1, le=500), user: auth.DeskUser = Depends(auth.get_current_user), conn=Depends(_db), ): _require_inbox(user) items = list_events(conn, channel=channel, priority=priority, status=status, q=q, limit=limit) return {"events": items, "total": len(items)} @router.get("/events/{event_id}") def inbox_event_detail( event_id: str, user: auth.DeskUser = Depends(auth.get_current_user), conn=Depends(_db), ): _require_inbox(user) ev = get_event(conn, event_id) if not ev: raise HTTPException(404, "event not found") return {"event": ev} @router.post("/events/{event_id}/messages") def inbox_post_message( event_id: str, body: InboxMessageBody, user: auth.DeskUser = Depends(auth.get_current_user), conn=Depends(_db), ): _require_inbox(user) if not get_event(conn, event_id): raise HTTPException(404, "event not found") author_type = "internal" if body.note_type == "internal_note" else "operator" label = user.display_name or user.username msg = add_message(conn, event_id, author_type, label, body.body) conn.commit() return {"message": msg} @router.patch("/events/{event_id}") def inbox_patch( event_id: str, body: InboxPatchBody, user: auth.DeskUser = Depends(auth.get_current_user), conn=Depends(_db), ): _require_inbox(user) ev = patch_event( conn, event_id, status=body.status, assignee=body.assignee, priority=body.priority, ) if not ev: raise HTTPException(404, "event not found") conn.commit() return {"event": ev}