projects.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. """Project API router."""
  2. from typing import List
  3. from fastapi import APIRouter, Depends, HTTPException, Query
  4. from pydantic import BaseModel, Field
  5. from sqlalchemy.orm import Session
  6. from ..database import get_db
  7. from ..models.project import Project
  8. from ..schemas.project import ProjectCreate, ProjectUpdate, ProjectResponse, ProjectListResponse
  9. router = APIRouter(prefix="/api/projects", tags=["projects"])
  10. def _project_to_response(db: Session, project: Project) -> ProjectResponse:
  11. plan_count = len(project.plans) if project.plans else 0
  12. return ProjectResponse(
  13. id=project.id,
  14. name=project.name,
  15. description=project.description or "",
  16. topology=project.topology or "SSSR",
  17. model_path=project.model_path or "",
  18. status=project.status or "draft",
  19. boundary_conditions=project.get_boundary_conditions(),
  20. plan_count=plan_count,
  21. created_at=project.created_at,
  22. updated_at=project.updated_at,
  23. )
  24. @router.get("", response_model=ProjectListResponse)
  25. def list_projects(
  26. skip: int = Query(0, ge=0),
  27. limit: int = Query(50, ge=1, le=200),
  28. topology: str | None = None,
  29. db: Session = Depends(get_db),
  30. ):
  31. """List all projects with optional topology filter."""
  32. query = db.query(Project)
  33. if topology:
  34. query = query.filter(Project.topology == topology)
  35. total = query.count()
  36. projects = query.order_by(Project.updated_at.desc()).offset(skip).limit(limit).all()
  37. return ProjectListResponse(
  38. total=total,
  39. items=[_project_to_response(db, p) for p in projects],
  40. )
  41. @router.post("", response_model=ProjectResponse, status_code=201)
  42. def create_project(data: ProjectCreate, db: Session = Depends(get_db)):
  43. """Create a new project."""
  44. project = Project(
  45. name=data.name,
  46. description=data.description,
  47. topology=data.topology,
  48. model_path=data.model_path,
  49. )
  50. project.set_boundary_conditions(data.boundary_conditions)
  51. db.add(project)
  52. db.commit()
  53. db.refresh(project)
  54. return _project_to_response(db, project)
  55. @router.get("/{project_id}", response_model=ProjectResponse)
  56. def get_project(project_id: int, db: Session = Depends(get_db)):
  57. """Get a project by ID."""
  58. project = db.query(Project).filter(Project.id == project_id).first()
  59. if not project:
  60. raise HTTPException(status_code=404, detail="Project not found")
  61. return _project_to_response(db, project)
  62. @router.put("/{project_id}", response_model=ProjectResponse)
  63. def update_project(project_id: int, data: ProjectUpdate, db: Session = Depends(get_db)):
  64. """Update a project."""
  65. project = db.query(Project).filter(Project.id == project_id).first()
  66. if not project:
  67. raise HTTPException(status_code=404, detail="Project not found")
  68. update_data = data.model_dump(exclude_unset=True)
  69. if "boundary_conditions" in update_data:
  70. project.set_boundary_conditions(update_data.pop("boundary_conditions"))
  71. for key, value in update_data.items():
  72. setattr(project, key, value)
  73. db.commit()
  74. db.refresh(project)
  75. return _project_to_response(db, project)
  76. @router.delete("/{project_id}", status_code=204)
  77. def delete_project(project_id: int, db: Session = Depends(get_db)):
  78. """Delete a project and all its plans/results."""
  79. project = db.query(Project).filter(Project.id == project_id).first()
  80. if not project:
  81. raise HTTPException(status_code=404, detail="Project not found")
  82. db.delete(project)
  83. db.commit()
  84. return None
  85. class BatchDeleteProjectsRequest(BaseModel):
  86. """Request to delete multiple projects."""
  87. ids: List[int] = Field(..., description="Project IDs to delete")
  88. @router.post("/batch-delete")
  89. def batch_delete_projects(request: BatchDeleteProjectsRequest, db: Session = Depends(get_db)):
  90. """Delete multiple projects; missing IDs are reported, not fatal."""
  91. deleted, errors = [], []
  92. for pid in request.ids:
  93. project = db.query(Project).filter(Project.id == pid).first()
  94. if not project:
  95. errors.append({"id": pid, "error": "not found"})
  96. continue
  97. db.delete(project)
  98. deleted.append(pid)
  99. db.commit()
  100. return {"deleted": deleted, "deleted_count": len(deleted), "errors": errors}