from __future__ import annotations from typing import Annotated from fastapi import APIRouter, Depends, Header, Query, Request, status from app.analytics.schemas import ( AnalyticsCapabilities, AnalyticsOverview, AnalyticsPostList, AnalyticsQuery, AnalyticsSyncList, AnalyticsSyncRequest, AnalyticsSyncRunView, AnalyticsTimeseries, ) router = APIRouter(prefix="/v1/analytics", tags=["analytics"]) def _identity(request: Request) -> tuple[str, str]: context = request.state.auth if not context.workspace_id or not context.user_id: from fastapi import HTTPException raise HTTPException(status_code=403, detail="No active workspace membership.") return context.workspace_id, context.user_id def _service(request: Request): service = request.app.state.container.analytics service.ensure_ready() return service @router.get("/capabilities", response_model=list[AnalyticsCapabilities]) async def capabilities(request: Request) -> list[AnalyticsCapabilities]: return await _service(request).capabilities() @router.get("/overview", response_model=AnalyticsOverview) async def overview( request: Request, query: Annotated[AnalyticsQuery, Depends()] ) -> AnalyticsOverview: workspace_id, _ = _identity(request) return await _service(request).overview(workspace_id, query) @router.get("/timeseries", response_model=AnalyticsTimeseries) async def timeseries( request: Request, query: Annotated[AnalyticsQuery, Depends()] ) -> AnalyticsTimeseries: workspace_id, _ = _identity(request) return await _service(request).timeseries(workspace_id, query) @router.get("/platforms", response_model=AnalyticsOverview) async def platforms( request: Request, query: Annotated[AnalyticsQuery, Depends()] ) -> AnalyticsOverview: workspace_id, _ = _identity(request) return await _service(request).overview(workspace_id, query) @router.get("/platforms/{provider}", response_model=AnalyticsOverview) async def platform( request: Request, provider: str, query: Annotated[AnalyticsQuery, Depends()], ) -> AnalyticsOverview: workspace_id, _ = _identity(request) query.provider = provider return await _service(request).overview(workspace_id, query) @router.get("/posts", response_model=AnalyticsPostList) async def posts(request: Request, query: Annotated[AnalyticsQuery, Depends()]) -> AnalyticsPostList: workspace_id, _ = _identity(request) return await _service(request).posts(workspace_id, query) @router.get("/posts/{post_id}", response_model=AnalyticsPostList) async def post( request: Request, post_id: str, query: Annotated[AnalyticsQuery, Depends()], ) -> AnalyticsPostList: workspace_id, _ = _identity(request) return await _service(request).post(workspace_id, post_id, query) @router.get("/projects/{project_id}", response_model=AnalyticsOverview) async def project( request: Request, project_id: str, query: Annotated[AnalyticsQuery, Depends()], ) -> AnalyticsOverview: workspace_id, user_id = _identity(request) await request.app.state.container.projects.get( workspace_id=workspace_id, user_id=user_id, project_id=project_id ) query.project_id = project_id return await _service(request).overview(workspace_id, query) @router.get("/sync-runs", response_model=AnalyticsSyncList) async def sync_runs( request: Request, offset: int = Query(default=0, ge=0, le=100_000), limit: int = Query(default=50, ge=1, le=500), ) -> AnalyticsSyncList: workspace_id, _ = _identity(request) items = await _service(request).repository.list_syncs(workspace_id, offset=offset, limit=limit) return AnalyticsSyncList( items=[_service(request)._sync_view(item) for item in items], offset=offset, limit=limit, ) @router.get("/sync-runs/{run_id}", response_model=AnalyticsSyncRunView) async def sync_run(request: Request, run_id: str) -> AnalyticsSyncRunView: workspace_id, _ = _identity(request) return _service(request)._sync_view( await _service(request).repository.get_sync(workspace_id, run_id) ) @router.post("/sync-runs/{run_id}/cancel", response_model=AnalyticsSyncRunView) async def cancel_sync(request: Request, run_id: str) -> AnalyticsSyncRunView: workspace_id, _ = _identity(request) run = await _service(request).repository.cancel_sync(workspace_id, run_id) await _service(request).audit.record( workspace_id=workspace_id, event_type="analytics.sync_cancelled", metadata={"sync_run_id": run.id}, api_key_id=request.state.auth.api_key_id, request_id=request.state.request_id, ) return _service(request)._sync_view(run) @router.post("/sync", response_model=AnalyticsSyncRunView, status_code=status.HTTP_202_ACCEPTED) async def sync( request: Request, payload: AnalyticsSyncRequest, idempotency_key: str | None = Header(default=None, alias="Idempotency-Key"), ) -> AnalyticsSyncRunView: workspace_id, user_id = _identity(request) if idempotency_key: payload.idempotency_key = idempotency_key return await _service(request).create_sync(workspace_id, user_id, payload)