Compare commits

..

1 Commits

Author SHA1 Message Date
zhi
96cbe109ec Add support code-based action routes 2026-03-21 18:17:11 +00:00

View File

@@ -13,7 +13,7 @@ from pydantic import BaseModel
from app.core.config import get_db from app.core.config import get_db
from app.api.deps import get_current_user_or_apikey from app.api.deps import get_current_user_or_apikey
from app.api.rbac import ensure_can_edit_milestone from app.api.rbac import check_project_role, ensure_can_edit_milestone
from app.models import models from app.models import models
from app.models.apikey import APIKey from app.models.apikey import APIKey
from app.models.activity import ActivityLog from app.models.activity import ActivityLog
@@ -484,26 +484,58 @@ def create_milestone_task(project_code: str, milestone_id: int, task_data: dict,
# ============ Supports ============ # ============ Supports ============
def _find_support_by_id_or_code(db: Session, identifier: str) -> Support | None:
try:
support_id = int(identifier)
support = db.query(Support).filter(Support.id == support_id).first()
if support:
return support
except (TypeError, ValueError):
pass
return db.query(Support).filter(Support.support_code == str(identifier)).first()
def _serialize_support(db: Session, support: Support) -> dict:
project = db.query(models.Project).filter(models.Project.id == support.project_id).first()
milestone = db.query(MilestoneModel).filter(MilestoneModel.id == support.milestone_id).first()
assignee = None
if support.assignee_id:
assignee = db.query(models.User).filter(models.User.id == support.assignee_id).first()
return {
"id": support.id,
"code": support.support_code,
"support_code": support.support_code,
"title": support.title,
"description": support.description,
"status": support.status.value if hasattr(support.status, "value") else support.status,
"priority": support.priority.value if hasattr(support.priority, "value") else support.priority,
"project_id": support.project_id,
"project_code": project.project_code if project else None,
"milestone_id": support.milestone_id,
"milestone_code": milestone.milestone_code if milestone else None,
"reporter_id": support.reporter_id,
"assignee_id": support.assignee_id,
"taken_by": assignee.username if assignee else None,
"created_at": support.created_at,
"updated_at": support.updated_at,
}
@router.get("/supports/{project_code}/{milestone_id}", tags=["Supports"]) @router.get("/supports/{project_code}/{milestone_id}", tags=["Supports"])
def list_supports(project_code: str, milestone_id: int, db: Session = Depends(get_db)): def list_supports(project_code: str, milestone_id: int, db: Session = Depends(get_db)):
project = db.query(models.Project).filter(models.Project.project_code == project_code).first() project = db.query(models.Project).filter(models.Project.project_code == project_code).first()
if not project: if not project:
raise HTTPException(status_code=404, detail="Project not found") raise HTTPException(status_code=404, detail="Project not found")
supports = db.query(Support).filter( supports = db.query(Support).filter(
Support.project_id == project.id, Support.project_id == project.id,
Support.milestone_id == milestone_id Support.milestone_id == milestone_id
).all() ).all()
return [{ return [_serialize_support(db, s) for s in supports]
"id": s.id,
"title": s.title,
"description": s.description,
"status": s.status.value,
"priority": s.priority.value,
"assignee_id": s.assignee_id,
"created_at": s.created_at,
} for s in supports]
@router.post("/supports/{project_code}/{milestone_id}", status_code=status.HTTP_201_CREATED, tags=["Supports"]) @router.post("/supports/{project_code}/{milestone_id}", status_code=status.HTTP_201_CREATED, tags=["Supports"])
@@ -511,19 +543,19 @@ def create_support(project_code: str, milestone_id: int, support_data: dict, db:
project = db.query(models.Project).filter(models.Project.project_code == project_code).first() project = db.query(models.Project).filter(models.Project.project_code == project_code).first()
if not project: if not project:
raise HTTPException(status_code=404, detail="Project not found") raise HTTPException(status_code=404, detail="Project not found")
ms = db.query(MilestoneModel).filter(MilestoneModel.id == milestone_id).first() ms = db.query(MilestoneModel).filter(MilestoneModel.id == milestone_id).first()
if not ms: if not ms:
raise HTTPException(status_code=404, detail="Milestone not found") raise HTTPException(status_code=404, detail="Milestone not found")
if ms.status and hasattr(ms.status, "value") and ms.status.value == "undergoing": if ms.status and hasattr(ms.status, "value") and ms.status.value == "undergoing":
raise HTTPException(status_code=400, detail="Cannot add items to a milestone that is undergoing") raise HTTPException(status_code=400, detail="Cannot add items to a milestone that is undergoing")
milestone_code = ms.milestone_code or f"m{ms.id}" milestone_code = ms.milestone_code or f"m{ms.id}"
max_support = db.query(Support).filter(Support.milestone_id == milestone_id).order_by(Support.id.desc()).first() max_support = db.query(Support).filter(Support.milestone_id == milestone_id).order_by(Support.id.desc()).first()
next_num = (max_support.id + 1) if max_support else 1 next_num = (max_support.id + 1) if max_support else 1
support_code = f"{milestone_code}:S{next_num:05x}" support_code = f"{milestone_code}:S{next_num:05x}"
support = Support( support = Support(
title=support_data.get("title"), title=support_data.get("title"),
description=support_data.get("description"), description=support_data.get("description"),
@@ -537,7 +569,89 @@ def create_support(project_code: str, milestone_id: int, support_data: dict, db:
db.add(support) db.add(support)
db.commit() db.commit()
db.refresh(support) db.refresh(support)
return support return _serialize_support(db, support)
@router.get("/supports/{support_id}", tags=["Supports"])
def get_support(support_id: str, db: Session = Depends(get_db), current_user: models.User = Depends(get_current_user_or_apikey)):
support = _find_support_by_id_or_code(db, support_id)
if not support:
raise HTTPException(status_code=404, detail="Support not found")
check_project_role(db, current_user.id, support.project_id, min_role="viewer")
return _serialize_support(db, support)
@router.patch("/supports/{support_id}", tags=["Supports"])
def update_support(support_id: str, support_data: dict, db: Session = Depends(get_db), current_user: models.User = Depends(get_current_user_or_apikey)):
support = _find_support_by_id_or_code(db, support_id)
if not support:
raise HTTPException(status_code=404, detail="Support not found")
check_project_role(db, current_user.id, support.project_id, min_role="dev")
allowed_fields = {"title", "description", "status", "priority"}
updated = False
for field, value in support_data.items():
if field not in allowed_fields:
continue
if field == "status" and value is not None:
value = SupportStatus(value)
if field == "priority" and value is not None:
value = SupportPriority(value)
setattr(support, field, value)
updated = True
if not updated:
raise HTTPException(status_code=400, detail="No supported fields to update")
db.commit()
db.refresh(support)
return _serialize_support(db, support)
@router.delete("/supports/{support_id}", status_code=status.HTTP_204_NO_CONTENT, tags=["Supports"])
def delete_support(support_id: str, db: Session = Depends(get_db), current_user: models.User = Depends(get_current_user_or_apikey)):
support = _find_support_by_id_or_code(db, support_id)
if not support:
raise HTTPException(status_code=404, detail="Support not found")
check_project_role(db, current_user.id, support.project_id, min_role="dev")
db.delete(support)
db.commit()
return None
@router.post("/supports/{support_id}/take", tags=["Supports"])
def take_support(support_id: str, db: Session = Depends(get_db), current_user: models.User = Depends(get_current_user_or_apikey)):
support = _find_support_by_id_or_code(db, support_id)
if not support:
raise HTTPException(status_code=404, detail="Support not found")
check_project_role(db, current_user.id, support.project_id, min_role="dev")
if support.assignee_id and support.assignee_id != current_user.id:
assignee = db.query(models.User).filter(models.User.id == support.assignee_id).first()
assignee_name = assignee.username if assignee else str(support.assignee_id)
raise HTTPException(status_code=409, detail=f"Support is already taken by {assignee_name}")
support.assignee_id = current_user.id
db.commit()
db.refresh(support)
return _serialize_support(db, support)
@router.post("/supports/{support_id}/transition", tags=["Supports"])
def transition_support(support_id: str, support_data: dict, db: Session = Depends(get_db), current_user: models.User = Depends(get_current_user_or_apikey)):
support = _find_support_by_id_or_code(db, support_id)
if not support:
raise HTTPException(status_code=404, detail="Support not found")
check_project_role(db, current_user.id, support.project_id, min_role="dev")
status_value = support_data.get("status")
if not status_value:
raise HTTPException(status_code=400, detail="status is required")
support.status = SupportStatus(status_value)
db.commit()
db.refresh(support)
return _serialize_support(db, support)
# ============ Meetings ============ # ============ Meetings ============