"""admin/routers/dashboard.py — Sessions, transcripts, and message moderation. Routes: GET /{slug}/dashboard GET /{slug}/sessions GET /{slug}/sessions/search POST /{slug}/sessions/{id}/block POST /{slug}/sessions/{id}/unblock GET /{slug}/sessions/{id}/transcript POST /{slug}/sessions/{id}/messages/{mid}/annotate POST /{slug}/sessions/{id}/messages/{mid}/flag """ from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.ext.asyncio import AsyncSession from app.dependencies import get_db, get_current_author_scoped from app.schemas.admin import AnnotateRequest, FlagRequest from app.services.dashboard_service import DashboardService router = APIRouter() @router.get("/{author_slug}/dashboard") async def dashboard( author_slug: str, current_user=Depends(get_current_author_scoped), db: AsyncSession = Depends(get_db), ): """Return high-level dashboard stats for the author.""" return await DashboardService(db).get_dashboard_stats(current_user.id) @router.get("/{author_slug}/sessions") async def list_sessions( author_slug: str, limit: int = Query(50, ge=1, le=200), offset: int = 0, current_user=Depends(get_current_author_scoped), db: AsyncSession = Depends(get_db), ): """List reader sessions with pagination.""" return await DashboardService(db).list_sessions(current_user.id, limit, offset) @router.get("/{author_slug}/sessions/search") async def search_sessions( author_slug: str, q: str = Query("", min_length=0, max_length=200), page: int = Query(1, ge=1), per_page: int = Query(20, ge=1, le=100), current_user=Depends(get_current_author_scoped), db: AsyncSession = Depends(get_db), ): """Full-text search across chat messages for the author.""" return await DashboardService(db).search_sessions(current_user.id, q, page, per_page) @router.post("/{author_slug}/sessions/{session_id}/block") async def block_session( author_slug: str, session_id: str, current_user=Depends(get_current_author_scoped), db: AsyncSession = Depends(get_db), ): """Block a visitor session.""" if not await DashboardService(db).block_session(current_user.id, session_id): raise HTTPException(404, "Session not found") return {"message": "Session blocked"} @router.post("/{author_slug}/sessions/{session_id}/unblock") async def unblock_session( author_slug: str, session_id: str, current_user=Depends(get_current_author_scoped), db: AsyncSession = Depends(get_db), ): """Unblock a visitor session.""" if not await DashboardService(db).unblock_session(current_user.id, session_id): raise HTTPException(404, "Session not found") return {"message": "Session unblocked"} @router.get("/{author_slug}/sessions/{session_id}/transcript") async def get_transcript( author_slug: str, session_id: str, page: int = Query(1, ge=1), per_page: int = Query(50, ge=1, le=200), current_user=Depends(get_current_author_scoped), db: AsyncSession = Depends(get_db), ): """Return paginated message transcript for a session.""" result = await DashboardService(db).get_transcript( current_user.id, session_id, page, per_page, ) if result is None: raise HTTPException(404, "Session not found") return result @router.post("/{author_slug}/sessions/{session_id}/messages/{message_id}/annotate") async def annotate_message( author_slug: str, session_id: str, message_id: str, body: AnnotateRequest, current_user=Depends(get_current_author_scoped), db: AsyncSession = Depends(get_db), ): """Add admin annotation to a chat message (append-only). R-029: Schema validated.""" result = await DashboardService(db).annotate_message( current_user.id, session_id, message_id, body.annotation, ) if result is None: raise HTTPException(404, "Message not found") return result @router.post("/{author_slug}/sessions/{session_id}/messages/{message_id}/flag") async def flag_message( author_slug: str, session_id: str, message_id: str, body: FlagRequest, current_user=Depends(get_current_author_scoped), db: AsyncSession = Depends(get_db), ): """Flag a message as spam, quality issue, or escalation. R-029: Schema validated.""" result = await DashboardService(db).flag_message( current_user.id, session_id, message_id, body.flag_type, ) if result is None: raise HTTPException(404, "Message not found") return result