feat: add code-first API support for projects, milestones, proposes, tasks
- Projects: get/update/delete/members endpoints now accept project_code - Milestones: all project-scoped and top-level endpoints accept milestone_code - Proposes: all endpoints accept project_code and propose_code - Tasks: code-first support for all CRUD + transition + take + search - Schemas: add code/type/due_date/project_code/milestone_code/taken_by fields - All endpoints use id-or-code lookup helpers for backward compatibility - Milestone serializer now includes milestone_code and code fields - Task serializer enriches responses with project_code, milestone_code, taken_by Addresses TODO §2.1: code-first API support across CLI-targeted resources
This commit is contained in:
@@ -145,17 +145,29 @@ def list_milestones(project_id: int = None, status_filter: str = None, db: Sessi
|
||||
return query.order_by(MilestoneModel.due_date.is_(None), MilestoneModel.due_date.asc()).all()
|
||||
|
||||
|
||||
def _find_milestone_by_id_or_code(db, identifier) -> MilestoneModel | None:
|
||||
"""Look up milestone by numeric id or milestone_code."""
|
||||
try:
|
||||
mid = int(identifier)
|
||||
ms = db.query(MilestoneModel).filter(MilestoneModel.id == mid).first()
|
||||
if ms:
|
||||
return ms
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
return db.query(MilestoneModel).filter(MilestoneModel.milestone_code == str(identifier)).first()
|
||||
|
||||
|
||||
@router.get("/milestones/{milestone_id}", response_model=schemas.MilestoneResponse, tags=["Milestones"])
|
||||
def get_milestone(milestone_id: int, db: Session = Depends(get_db)):
|
||||
ms = db.query(MilestoneModel).filter(MilestoneModel.id == milestone_id).first()
|
||||
def get_milestone(milestone_id: str, db: Session = Depends(get_db)):
|
||||
ms = _find_milestone_by_id_or_code(db, milestone_id)
|
||||
if not ms:
|
||||
raise HTTPException(status_code=404, detail="Milestone not found")
|
||||
return ms
|
||||
|
||||
|
||||
@router.patch("/milestones/{milestone_id}", response_model=schemas.MilestoneResponse, tags=["Milestones"])
|
||||
def update_milestone(milestone_id: int, ms_update: schemas.MilestoneUpdate, db: Session = Depends(get_db), current_user: models.User = Depends(get_current_user_or_apikey)):
|
||||
ms = db.query(MilestoneModel).filter(MilestoneModel.id == milestone_id).first()
|
||||
def update_milestone(milestone_id: str, ms_update: schemas.MilestoneUpdate, db: Session = Depends(get_db), current_user: models.User = Depends(get_current_user_or_apikey)):
|
||||
ms = _find_milestone_by_id_or_code(db, milestone_id)
|
||||
if not ms:
|
||||
raise HTTPException(status_code=404, detail="Milestone not found")
|
||||
ensure_can_edit_milestone(db, current_user.id, ms)
|
||||
@@ -167,8 +179,8 @@ def update_milestone(milestone_id: int, ms_update: schemas.MilestoneUpdate, db:
|
||||
|
||||
|
||||
@router.delete("/milestones/{milestone_id}", status_code=status.HTTP_204_NO_CONTENT, tags=["Milestones"])
|
||||
def delete_milestone(milestone_id: int, db: Session = Depends(get_db)):
|
||||
ms = db.query(MilestoneModel).filter(MilestoneModel.id == milestone_id).first()
|
||||
def delete_milestone(milestone_id: str, db: Session = Depends(get_db)):
|
||||
ms = _find_milestone_by_id_or_code(db, milestone_id)
|
||||
if not ms:
|
||||
raise HTTPException(status_code=404, detail="Milestone not found")
|
||||
db.delete(ms)
|
||||
@@ -177,8 +189,8 @@ def delete_milestone(milestone_id: int, db: Session = Depends(get_db)):
|
||||
|
||||
|
||||
@router.get("/milestones/{milestone_id}/progress", tags=["Milestones"])
|
||||
def milestone_progress(milestone_id: int, db: Session = Depends(get_db)):
|
||||
ms = db.query(MilestoneModel).filter(MilestoneModel.id == milestone_id).first()
|
||||
def milestone_progress(milestone_id: str, db: Session = Depends(get_db)):
|
||||
ms = _find_milestone_by_id_or_code(db, milestone_id)
|
||||
if not ms:
|
||||
raise HTTPException(status_code=404, detail="Milestone not found")
|
||||
tasks = db.query(Task).filter(Task.milestone_id == milestone_id).all()
|
||||
|
||||
Reference in New Issue
Block a user