feat: enrich member/comment/propose APIs with usernames

- ProjectMemberResponse now includes username and full_name
- Comment list endpoint returns author_username
- ProposeResponse now includes created_by_username
- All serializers resolve User objects to surface human-readable names
- Supports frontend code-first migration (TODO §3.1/3.2)
This commit is contained in:
zhi
2026-03-21 20:28:28 +00:00
parent f45f5957f4
commit 3ff9132596
4 changed files with 46 additions and 9 deletions

View File

@@ -17,6 +17,24 @@ from app.services.activity import log_activity
router = APIRouter(prefix="/projects/{project_id}/proposes", tags=["Proposes"])
def _serialize_propose(db: Session, propose: Propose) -> dict:
"""Serialize propose with created_by_username."""
creator = db.query(models.User).filter(models.User.id == propose.created_by_id).first() if propose.created_by_id else None
return {
"id": propose.id,
"title": propose.title,
"description": propose.description,
"propose_code": propose.propose_code,
"status": propose.status.value if hasattr(propose.status, "value") else propose.status,
"project_id": propose.project_id,
"created_by_id": propose.created_by_id,
"created_by_username": creator.username if creator else None,
"feat_task_id": propose.feat_task_id,
"created_at": propose.created_at,
"updated_at": propose.updated_at,
}
def _find_project(db, identifier):
"""Look up project by numeric id or project_code."""
try:
@@ -92,7 +110,7 @@ def list_proposes(
.order_by(Propose.id.desc())
.all()
)
return proposes
return [_serialize_propose(db, p) for p in proposes]
@router.post("", response_model=schemas.ProposeResponse, status_code=status.HTTP_201_CREATED)
@@ -123,7 +141,7 @@ def create_propose(
log_activity(db, "create", "propose", propose.id, user_id=current_user.id, details={"title": propose.title})
return propose
return _serialize_propose(db, propose)
@router.get("/{propose_id}", response_model=schemas.ProposeResponse)
@@ -140,7 +158,7 @@ def get_propose(
propose = _find_propose(db, propose_id, project.id)
if not propose:
raise HTTPException(status_code=404, detail="Propose not found")
return propose
return _serialize_propose(db, propose)
@router.patch("/{propose_id}", response_model=schemas.ProposeResponse)
@@ -177,7 +195,7 @@ def update_propose(
log_activity(db, "update", "propose", propose.id, user_id=current_user.id, details=data)
return propose
return _serialize_propose(db, propose)
# ---- Actions ----
@@ -256,7 +274,7 @@ def accept_propose(
"task_code": task_code,
})
return propose
return _serialize_propose(db, propose)
class RejectRequest(schemas.BaseModel):
@@ -293,7 +311,7 @@ def reject_propose(
"reason": body.reason if body else None,
})
return propose
return _serialize_propose(db, propose)
@router.post("/{propose_id}/reopen", response_model=schemas.ProposeResponse)
@@ -323,4 +341,4 @@ def reopen_propose(
log_activity(db, "reopen", "propose", propose.id, user_id=current_user.id)
return propose
return _serialize_propose(db, propose)