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>
102 lines
3.5 KiB
Python
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)
|