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)