olivas/backend/app/api/endpoints/aoi.py
Vadym Samoilenko f217a5aea6 Add Azure AD SSO authentication for backend and frontend
Replace X-User-Id header auth with Azure AD JWT token validation.
Backend validates tokens via JWKS, frontend uses MSAL for login/token
acquisition. Adds logout button, 401 handling, and configurable
AZURE_AUTH_ENABLED toggle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 18:41:06 +00:00

102 lines
3.5 KiB
Python

import numpy as np
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.session import get_db
from app.dependencies import get_user_id
from app.models.analysis import Analysis
from app.models.aoi import AOI
from app.schemas.aoi import AOICreate, AOIResult, AOIUpdate
from app.services.aoi_analysis import compute_aoi_attention
from app.services.storage import storage
router = APIRouter(tags=["aoi"])
@router.post("/analyses/{analysis_id}/aois", response_model=list[AOIResult], status_code=201)
async def create_aois(
analysis_id: str,
body: AOICreate,
db: AsyncSession = Depends(get_db),
user_id: str = Depends(get_user_id),
):
stmt = select(Analysis).where(Analysis.id == analysis_id, Analysis.user_id == user_id)
result = await db.execute(stmt)
analysis = result.scalar_one_or_none()
if not analysis:
raise HTTPException(status_code=404, detail="Analysis not found")
if analysis.status != "completed":
raise HTTPException(status_code=400, detail="Analysis not yet completed")
# Load saliency map
npy_path = storage.get_path(analysis_id, "saliency_raw.npy")
if not npy_path.exists():
raise HTTPException(status_code=400, detail="Saliency data not available")
saliency = np.load(str(npy_path))
regions = [r.model_dump() for r in body.regions]
attention_results = compute_aoi_attention(saliency, regions)
aoi_records = []
for region, att in zip(body.regions, attention_results):
aoi = AOI(
analysis_id=analysis_id,
label=region.label,
x=region.x,
y=region.y,
width=region.width,
height=region.height,
attention_pct=att["attention_pct"],
area_pct=att["area_pct"],
attention_density=att["attention_density"],
)
db.add(aoi)
aoi_records.append(aoi)
await db.flush()
for aoi in aoi_records:
await db.refresh(aoi)
return [AOIResult.model_validate(aoi) for aoi in aoi_records]
@router.get("/analyses/{analysis_id}/aois", response_model=list[AOIResult])
async def list_aois(
analysis_id: str,
db: AsyncSession = Depends(get_db),
user_id: str = Depends(get_user_id),
):
stmt = select(Analysis).where(Analysis.id == analysis_id, Analysis.user_id == user_id)
result = await db.execute(stmt)
analysis = result.scalar_one_or_none()
if not analysis:
raise HTTPException(status_code=404, detail="Analysis not found")
aoi_stmt = select(AOI).where(AOI.analysis_id == analysis_id)
aoi_result = await db.execute(aoi_stmt)
aois = aoi_result.scalars().all()
return [AOIResult.model_validate(aoi) for aoi in aois]
@router.delete("/analyses/{analysis_id}/aois/{aoi_id}", status_code=204)
async def delete_aoi(
analysis_id: str,
aoi_id: str,
db: AsyncSession = Depends(get_db),
user_id: str = Depends(get_user_id),
):
# Verify analysis ownership
stmt = select(Analysis).where(Analysis.id == analysis_id, Analysis.user_id == user_id)
result = await db.execute(stmt)
if not result.scalar_one_or_none():
raise HTTPException(status_code=404, detail="Analysis not found")
aoi_stmt = select(AOI).where(AOI.id == aoi_id, AOI.analysis_id == analysis_id)
aoi_result = await db.execute(aoi_stmt)
aoi = aoi_result.scalar_one_or_none()
if not aoi:
raise HTTPException(status_code=404, detail="AOI not found")
await db.delete(aoi)