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>
136 lines
4 KiB
Python
136 lines
4 KiB
Python
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy import func, select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy.orm import selectinload
|
|
|
|
from app.db.session import get_db
|
|
from app.dependencies import get_user_id
|
|
from app.models.analysis import Analysis
|
|
from app.models.project import Project
|
|
from app.schemas.project import ProjectCreate, ProjectDetail, ProjectSummary, ProjectUpdate
|
|
|
|
router = APIRouter(prefix="/projects", tags=["projects"])
|
|
|
|
|
|
@router.post("", response_model=ProjectSummary, status_code=201)
|
|
async def create_project(
|
|
body: ProjectCreate,
|
|
db: AsyncSession = Depends(get_db),
|
|
user_id: str = Depends(get_user_id),
|
|
):
|
|
project = Project(user_id=user_id, name=body.name, description=body.description)
|
|
db.add(project)
|
|
await db.flush()
|
|
await db.refresh(project)
|
|
return ProjectSummary(
|
|
id=project.id,
|
|
name=project.name,
|
|
description=project.description,
|
|
analysis_count=0,
|
|
created_at=project.created_at,
|
|
updated_at=project.updated_at,
|
|
)
|
|
|
|
|
|
@router.get("", response_model=list[ProjectSummary])
|
|
async def list_projects(
|
|
page: int = 1,
|
|
per_page: int = 20,
|
|
db: AsyncSession = Depends(get_db),
|
|
user_id: str = Depends(get_user_id),
|
|
):
|
|
offset = (page - 1) * per_page
|
|
|
|
stmt = (
|
|
select(
|
|
Project,
|
|
func.count(Analysis.id).label("analysis_count"),
|
|
)
|
|
.outerjoin(Analysis)
|
|
.where(Project.user_id == user_id)
|
|
.group_by(Project.id)
|
|
.order_by(Project.updated_at.desc())
|
|
.offset(offset)
|
|
.limit(per_page)
|
|
)
|
|
result = await db.execute(stmt)
|
|
rows = result.all()
|
|
|
|
return [
|
|
ProjectSummary(
|
|
id=p.id,
|
|
name=p.name,
|
|
description=p.description,
|
|
analysis_count=count,
|
|
created_at=p.created_at,
|
|
updated_at=p.updated_at,
|
|
)
|
|
for p, count in rows
|
|
]
|
|
|
|
|
|
@router.get("/{project_id}", response_model=ProjectDetail)
|
|
async def get_project(
|
|
project_id: str,
|
|
db: AsyncSession = Depends(get_db),
|
|
user_id: str = Depends(get_user_id),
|
|
):
|
|
stmt = (
|
|
select(Project)
|
|
.options(selectinload(Project.analyses))
|
|
.where(Project.id == project_id, Project.user_id == user_id)
|
|
)
|
|
result = await db.execute(stmt)
|
|
project = result.scalar_one_or_none()
|
|
if not project:
|
|
raise HTTPException(status_code=404, detail="Project not found")
|
|
return project
|
|
|
|
|
|
@router.put("/{project_id}", response_model=ProjectSummary)
|
|
async def update_project(
|
|
project_id: str,
|
|
body: ProjectUpdate,
|
|
db: AsyncSession = Depends(get_db),
|
|
user_id: str = Depends(get_user_id),
|
|
):
|
|
stmt = select(Project).where(Project.id == project_id, Project.user_id == user_id)
|
|
result = await db.execute(stmt)
|
|
project = result.scalar_one_or_none()
|
|
if not project:
|
|
raise HTTPException(status_code=404, detail="Project not found")
|
|
|
|
if body.name is not None:
|
|
project.name = body.name
|
|
if body.description is not None:
|
|
project.description = body.description
|
|
|
|
await db.flush()
|
|
await db.refresh(project)
|
|
|
|
count_stmt = select(func.count(Analysis.id)).where(Analysis.project_id == project_id)
|
|
count_result = await db.execute(count_stmt)
|
|
analysis_count = count_result.scalar() or 0
|
|
|
|
return ProjectSummary(
|
|
id=project.id,
|
|
name=project.name,
|
|
description=project.description,
|
|
analysis_count=analysis_count,
|
|
created_at=project.created_at,
|
|
updated_at=project.updated_at,
|
|
)
|
|
|
|
|
|
@router.delete("/{project_id}", status_code=204)
|
|
async def delete_project(
|
|
project_id: str,
|
|
db: AsyncSession = Depends(get_db),
|
|
user_id: str = Depends(get_user_id),
|
|
):
|
|
stmt = select(Project).where(Project.id == project_id, Project.user_id == user_id)
|
|
result = await db.execute(stmt)
|
|
project = result.scalar_one_or_none()
|
|
if not project:
|
|
raise HTTPException(status_code=404, detail="Project not found")
|
|
await db.delete(project)
|