"""Project API router.""" from typing import List from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel, Field from sqlalchemy.orm import Session from ..database import get_db from ..models.project import Project from ..schemas.project import ProjectCreate, ProjectUpdate, ProjectResponse, ProjectListResponse router = APIRouter(prefix="/api/projects", tags=["projects"]) def _project_to_response(db: Session, project: Project) -> ProjectResponse: plan_count = len(project.plans) if project.plans else 0 return ProjectResponse( id=project.id, name=project.name, description=project.description or "", topology=project.topology or "SSSR", model_path=project.model_path or "", status=project.status or "draft", boundary_conditions=project.get_boundary_conditions(), plan_count=plan_count, created_at=project.created_at, updated_at=project.updated_at, ) @router.get("", response_model=ProjectListResponse) def list_projects( skip: int = Query(0, ge=0), limit: int = Query(50, ge=1, le=200), topology: str | None = None, db: Session = Depends(get_db), ): """List all projects with optional topology filter.""" query = db.query(Project) if topology: query = query.filter(Project.topology == topology) total = query.count() projects = query.order_by(Project.updated_at.desc()).offset(skip).limit(limit).all() return ProjectListResponse( total=total, items=[_project_to_response(db, p) for p in projects], ) @router.post("", response_model=ProjectResponse, status_code=201) def create_project(data: ProjectCreate, db: Session = Depends(get_db)): """Create a new project.""" project = Project( name=data.name, description=data.description, topology=data.topology, model_path=data.model_path, ) project.set_boundary_conditions(data.boundary_conditions) db.add(project) db.commit() db.refresh(project) return _project_to_response(db, project) @router.get("/{project_id}", response_model=ProjectResponse) def get_project(project_id: int, db: Session = Depends(get_db)): """Get a project by ID.""" project = db.query(Project).filter(Project.id == project_id).first() if not project: raise HTTPException(status_code=404, detail="Project not found") return _project_to_response(db, project) @router.put("/{project_id}", response_model=ProjectResponse) def update_project(project_id: int, data: ProjectUpdate, db: Session = Depends(get_db)): """Update a project.""" project = db.query(Project).filter(Project.id == project_id).first() if not project: raise HTTPException(status_code=404, detail="Project not found") update_data = data.model_dump(exclude_unset=True) if "boundary_conditions" in update_data: project.set_boundary_conditions(update_data.pop("boundary_conditions")) for key, value in update_data.items(): setattr(project, key, value) db.commit() db.refresh(project) return _project_to_response(db, project) @router.delete("/{project_id}", status_code=204) def delete_project(project_id: int, db: Session = Depends(get_db)): """Delete a project and all its plans/results.""" project = db.query(Project).filter(Project.id == project_id).first() if not project: raise HTTPException(status_code=404, detail="Project not found") db.delete(project) db.commit() return None class BatchDeleteProjectsRequest(BaseModel): """Request to delete multiple projects.""" ids: List[int] = Field(..., description="Project IDs to delete") @router.post("/batch-delete") def batch_delete_projects(request: BatchDeleteProjectsRequest, db: Session = Depends(get_db)): """Delete multiple projects; missing IDs are reported, not fatal.""" deleted, errors = [], [] for pid in request.ids: project = db.query(Project).filter(Project.id == pid).first() if not project: errors.append({"id": pid, "error": "not found"}) continue db.delete(project) deleted.append(pid) db.commit() return {"deleted": deleted, "deleted_count": len(deleted), "errors": errors}