251 lines
9.8 KiB
Python
251 lines
9.8 KiB
Python
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from fastapi.responses import JSONResponse
|
|
from sqlalchemy import and_, select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from ...core.database import get_db
|
|
from ...core.response import error_response, success_response
|
|
from ...dependencies import get_current_user
|
|
from ...models import Project, User, UserProject
|
|
from ...schemas.pmflow_os import (
|
|
PMFlowChangeItemDeferRequest,
|
|
PMFlowChangeItemEditRequest,
|
|
PMFlowChangeSetCommitRequest,
|
|
PMFlowExtractionCreateRequest,
|
|
PMFlowThreadMessageCreateRequest,
|
|
)
|
|
from ...services.auth import has_any_role
|
|
from ...services.pmflow_change_review_service import PMFlowChangeReviewError, PMFlowChangeReviewService
|
|
from ...services.pmflow_os_projection_service import PMFlowOSProjectionService
|
|
|
|
router = APIRouter(prefix="/pmflow-os", tags=["pmflow-os"])
|
|
|
|
|
|
def _review_error(exc: PMFlowChangeReviewError) -> JSONResponse:
|
|
return JSONResponse(status_code=exc.status_code, content=error_response(exc.status_code, f"{exc.code}: {exc.message}"))
|
|
|
|
|
|
async def _ensure_project_member_or_manager(
|
|
db: AsyncSession,
|
|
*,
|
|
tenant_id: int,
|
|
project_id: int,
|
|
current_user: User,
|
|
) -> None:
|
|
project_result = await db.execute(
|
|
select(Project.id).where(and_(Project.id == int(project_id), Project.tenant_id == int(tenant_id)))
|
|
)
|
|
if project_result.scalar_one_or_none() is None:
|
|
raise HTTPException(status_code=404, detail="project_not_found")
|
|
if has_any_role(str(getattr(current_user, "role", "") or ""), ["OWNER", "REVIEWER"]):
|
|
return
|
|
member_result = await db.execute(
|
|
select(UserProject.user_id).where(
|
|
and_(
|
|
UserProject.tenant_id == int(tenant_id),
|
|
UserProject.project_id == int(project_id),
|
|
UserProject.user_id == int(current_user.id),
|
|
)
|
|
)
|
|
)
|
|
if member_result.scalar_one_or_none() is None:
|
|
raise HTTPException(status_code=403, detail="forbidden")
|
|
|
|
|
|
@router.get("/projects/{project_id}/truth-state")
|
|
async def get_pmflow_os_truth_state(
|
|
project_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
tenant_id = int(current_user.tenant_id)
|
|
await _ensure_project_member_or_manager(
|
|
db,
|
|
tenant_id=tenant_id,
|
|
project_id=int(project_id),
|
|
current_user=current_user,
|
|
)
|
|
service = PMFlowOSProjectionService(db, tenant_id=tenant_id)
|
|
try:
|
|
payload = await service.get_truth_state(
|
|
project_id=int(project_id),
|
|
current_user_id=int(current_user.id),
|
|
)
|
|
except ValueError as exc:
|
|
if str(exc) == "project_not_found":
|
|
raise HTTPException(status_code=404, detail="project_not_found") from exc
|
|
raise
|
|
return success_response(payload.model_dump(mode="json"))
|
|
|
|
|
|
@router.post("/projects/{project_id}/threads/{thread_id}/messages")
|
|
async def create_pmflow_os_thread_message(
|
|
project_id: int,
|
|
thread_id: str,
|
|
body: PMFlowThreadMessageCreateRequest,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
tenant_id = int(current_user.tenant_id)
|
|
await _ensure_project_member_or_manager(db, tenant_id=tenant_id, project_id=int(project_id), current_user=current_user)
|
|
service = PMFlowChangeReviewService(db, tenant_id=tenant_id)
|
|
try:
|
|
payload = await service.create_thread_message(
|
|
project_id=int(project_id),
|
|
thread_id=thread_id,
|
|
body=body,
|
|
user_id=int(current_user.id),
|
|
)
|
|
except PMFlowChangeReviewError as exc:
|
|
return _review_error(exc)
|
|
return success_response(payload)
|
|
|
|
|
|
@router.post("/projects/{project_id}/extractions")
|
|
async def create_pmflow_os_extraction(
|
|
project_id: int,
|
|
body: PMFlowExtractionCreateRequest,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
tenant_id = int(current_user.tenant_id)
|
|
await _ensure_project_member_or_manager(db, tenant_id=tenant_id, project_id=int(project_id), current_user=current_user)
|
|
service = PMFlowChangeReviewService(db, tenant_id=tenant_id)
|
|
try:
|
|
payload = await service.create_extraction(project_id=int(project_id), body=body, user_id=int(current_user.id))
|
|
except PMFlowChangeReviewError as exc:
|
|
return _review_error(exc)
|
|
return success_response(payload)
|
|
|
|
|
|
@router.get("/projects/{project_id}/change-sets/{change_set_id}")
|
|
async def get_pmflow_os_change_set(
|
|
project_id: int,
|
|
change_set_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
tenant_id = int(current_user.tenant_id)
|
|
await _ensure_project_member_or_manager(db, tenant_id=tenant_id, project_id=int(project_id), current_user=current_user)
|
|
service = PMFlowChangeReviewService(db, tenant_id=tenant_id)
|
|
try:
|
|
payload = await service.get_change_set_detail(project_id=int(project_id), change_set_id=int(change_set_id))
|
|
except PMFlowChangeReviewError as exc:
|
|
return _review_error(exc)
|
|
return success_response(payload)
|
|
|
|
|
|
@router.post("/change-items/{change_item_id}/accept")
|
|
async def accept_pmflow_os_change_item(
|
|
change_item_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
tenant_id = int(current_user.tenant_id)
|
|
service = PMFlowChangeReviewService(db, tenant_id=tenant_id)
|
|
project_id = await service.get_item_project_id(change_item_id=int(change_item_id))
|
|
if project_id is None:
|
|
return JSONResponse(status_code=404, content=error_response(404, "change_item_not_found"))
|
|
await _ensure_project_member_or_manager(db, tenant_id=tenant_id, project_id=project_id, current_user=current_user)
|
|
try:
|
|
payload = await service.accept_item(change_item_id=int(change_item_id), user_id=int(current_user.id))
|
|
except PMFlowChangeReviewError as exc:
|
|
return _review_error(exc)
|
|
return success_response(payload)
|
|
|
|
|
|
@router.post("/change-items/{change_item_id}/edit")
|
|
async def edit_pmflow_os_change_item(
|
|
change_item_id: int,
|
|
body: PMFlowChangeItemEditRequest,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
tenant_id = int(current_user.tenant_id)
|
|
service = PMFlowChangeReviewService(db, tenant_id=tenant_id)
|
|
project_id = await service.get_item_project_id(change_item_id=int(change_item_id))
|
|
if project_id is None:
|
|
return JSONResponse(status_code=404, content=error_response(404, "change_item_not_found"))
|
|
await _ensure_project_member_or_manager(db, tenant_id=tenant_id, project_id=project_id, current_user=current_user)
|
|
try:
|
|
payload = await service.edit_item(change_item_id=int(change_item_id), body=body, user_id=int(current_user.id))
|
|
except PMFlowChangeReviewError as exc:
|
|
return _review_error(exc)
|
|
return success_response(payload)
|
|
|
|
|
|
@router.post("/change-items/{change_item_id}/defer")
|
|
async def defer_pmflow_os_change_item(
|
|
change_item_id: int,
|
|
body: PMFlowChangeItemDeferRequest | None = None,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
tenant_id = int(current_user.tenant_id)
|
|
service = PMFlowChangeReviewService(db, tenant_id=tenant_id)
|
|
project_id = await service.get_item_project_id(change_item_id=int(change_item_id))
|
|
if project_id is None:
|
|
return JSONResponse(status_code=404, content=error_response(404, "change_item_not_found"))
|
|
await _ensure_project_member_or_manager(db, tenant_id=tenant_id, project_id=project_id, current_user=current_user)
|
|
try:
|
|
payload = await service.defer_item(
|
|
change_item_id=int(change_item_id),
|
|
user_id=int(current_user.id),
|
|
reason=body.reason if body is not None else None,
|
|
)
|
|
except PMFlowChangeReviewError as exc:
|
|
return _review_error(exc)
|
|
return success_response(payload)
|
|
|
|
|
|
@router.post("/change-sets/{change_set_id}/commit")
|
|
async def commit_pmflow_os_change_set(
|
|
change_set_id: int,
|
|
body: PMFlowChangeSetCommitRequest | None = None,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
tenant_id = int(current_user.tenant_id)
|
|
service = PMFlowChangeReviewService(db, tenant_id=tenant_id)
|
|
project_id = await service.get_change_set_project_id(change_set_id=int(change_set_id))
|
|
if project_id is None:
|
|
return JSONResponse(status_code=404, content=error_response(404, "changeset_not_found"))
|
|
await _ensure_project_member_or_manager(db, tenant_id=tenant_id, project_id=project_id, current_user=current_user)
|
|
try:
|
|
payload = await service.commit_change_set(
|
|
change_set_id=int(change_set_id),
|
|
body=body or PMFlowChangeSetCommitRequest(),
|
|
user_id=int(current_user.id),
|
|
)
|
|
except PMFlowChangeReviewError as exc:
|
|
return _review_error(exc)
|
|
return success_response(payload)
|
|
|
|
|
|
@router.get("/projects/{project_id}/execution-summary")
|
|
async def get_pmflow_os_execution_summary(
|
|
project_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
tenant_id = int(current_user.tenant_id)
|
|
await _ensure_project_member_or_manager(
|
|
db,
|
|
tenant_id=tenant_id,
|
|
project_id=int(project_id),
|
|
current_user=current_user,
|
|
)
|
|
service = PMFlowOSProjectionService(db, tenant_id=tenant_id)
|
|
try:
|
|
payload = await service.get_execution_summary(
|
|
project_id=int(project_id),
|
|
current_user_id=int(current_user.id),
|
|
)
|
|
except ValueError as exc:
|
|
if str(exc) == "project_not_found":
|
|
raise HTTPException(status_code=404, detail="project_not_found") from exc
|
|
raise
|
|
return success_response(payload.model_dump(mode="json"))
|