ChatDev/server/routes/workflows.py
2026-01-07 16:24:01 +08:00

212 lines
7.1 KiB
Python
Executable File

from fastapi import APIRouter, HTTPException
from server.models import (
WorkflowCopyRequest,
WorkflowRenameRequest,
WorkflowUpdateContentRequest,
WorkflowUploadContentRequest,
)
from server.services.workflow_storage import (
copy_workflow,
persist_workflow,
rename_workflow,
validate_workflow_content,
validate_workflow_filename,
)
from server.settings import YAML_DIR
from utils.exceptions import (
ResourceConflictError,
ResourceNotFoundError,
SecurityError,
ValidationError,
WorkflowExecutionError,
)
from utils.structured_logger import get_server_logger, LogType
router = APIRouter()
def _persist_workflow_from_content(
filename: str,
content: str,
*,
allow_overwrite: bool,
action: str,
success_message: str,
):
try:
safe_filename, yaml_content = validate_workflow_content(filename.strip(), content)
save_path = YAML_DIR / safe_filename
if save_path.exists() and not allow_overwrite:
raise HTTPException(status_code=409, detail="Workflow already exists; use the update API to overwrite")
if not save_path.exists() and allow_overwrite:
raise HTTPException(status_code=404, detail="Workflow file not found")
persist_workflow(safe_filename, content, yaml_content, action=action, directory=YAML_DIR)
return {
"status": "success",
"filename": safe_filename,
"message": success_message.format(filename=safe_filename),
}
except ValidationError:
raise
except HTTPException:
raise
except Exception as exc:
logger = get_server_logger()
logger.log_exception(exc, f"Unexpected error during workflow {action}")
raise WorkflowExecutionError(f"Failed to {action} workflow: {exc}")
@router.get("/api/workflows")
async def list_workflows():
if not YAML_DIR.exists():
return {"workflows": []}
return {"workflows": [file.name for file in YAML_DIR.glob("*.yaml")]}
@router.post("/api/workflows/upload/content")
async def upload_workflow_content(request: WorkflowUploadContentRequest):
return _persist_workflow_from_content(
request.filename,
request.content,
allow_overwrite=False,
action="upload",
success_message="Workflow {filename} created successfully from content",
)
@router.put("/api/workflows/{filename}")
async def update_workflow_content(filename: str, request: WorkflowUpdateContentRequest):
return _persist_workflow_from_content(
filename,
request.content,
allow_overwrite=True,
action="update",
success_message="Workflow {filename} updated successfully",
)
@router.delete("/api/workflows/{filename}")
async def delete_workflow(filename: str):
try:
safe_filename = validate_workflow_filename(filename, require_yaml_extension=True)
file_path = YAML_DIR / safe_filename
if not file_path.exists() or not file_path.is_file():
raise ResourceNotFoundError(
"Workflow file not found",
resource_type="workflow",
resource_id=safe_filename,
)
try:
file_path.unlink()
except Exception as exc:
logger = get_server_logger()
logger.log_exception(exc, f"Failed to delete workflow file: {safe_filename}")
raise WorkflowExecutionError("Failed to delete workflow file", details={"filename": safe_filename})
logger = get_server_logger()
logger.info(
"Workflow file deleted",
log_type=LogType.WORKFLOW,
filename=safe_filename,
)
return {
"status": "deleted",
"filename": safe_filename,
"message": f"Workflow '{safe_filename}' deleted successfully",
}
except ValidationError:
raise
except SecurityError:
raise
except ResourceNotFoundError:
raise
except Exception as exc:
logger = get_server_logger()
logger.log_exception(exc, f"Unexpected error deleting workflow: {filename}")
raise WorkflowExecutionError(f"Failed to delete workflow: {exc}")
@router.post("/api/workflows/{filename}/rename")
async def rename_workflow_file(filename: str, request: WorkflowRenameRequest):
try:
rename_workflow(filename, request.new_filename, directory=YAML_DIR)
return {
"status": "success",
"source": validate_workflow_filename(filename, require_yaml_extension=True),
"target": validate_workflow_filename(request.new_filename, require_yaml_extension=True),
"message": f"Workflow renamed to '{request.new_filename}' successfully",
}
except ValidationError:
raise
except SecurityError:
raise
except ResourceConflictError:
raise
except ResourceNotFoundError:
raise
except Exception as exc:
logger = get_server_logger()
logger.log_exception(exc, f"Unexpected error renaming workflow: {filename}")
raise WorkflowExecutionError(f"Failed to rename workflow: {exc}")
@router.post("/api/workflows/{filename}/copy")
async def copy_workflow_file(filename: str, request: WorkflowCopyRequest):
try:
copy_workflow(filename, request.new_filename, directory=YAML_DIR)
return {
"status": "success",
"source": validate_workflow_filename(filename, require_yaml_extension=True),
"target": validate_workflow_filename(request.new_filename, require_yaml_extension=True),
"message": f"Workflow copied to '{request.new_filename}' successfully",
}
except ValidationError:
raise
except SecurityError:
raise
except ResourceConflictError:
raise
except ResourceNotFoundError:
raise
except Exception as exc:
logger = get_server_logger()
logger.log_exception(exc, f"Unexpected error copying workflow: {filename}")
raise WorkflowExecutionError(f"Failed to copy workflow: {exc}")
@router.get("/api/workflows/{filename}")
async def get_workflow_raw_content(filename: str):
try:
safe_filename = validate_workflow_filename(filename, require_yaml_extension=True)
file_path = YAML_DIR / safe_filename
if not file_path.exists() or not file_path.is_file():
raise ResourceNotFoundError(
"Workflow file not found",
resource_type="workflow",
resource_id=safe_filename,
)
with open(file_path, "r", encoding="utf-8") as handle:
raw_content = handle.read()
logger = get_server_logger()
logger.info("Workflow file content retrieved", log_type=LogType.WORKFLOW, filename=safe_filename)
return {"content": raw_content}
except ValidationError:
raise
except SecurityError:
raise
except ResourceNotFoundError:
raise
except Exception as exc:
logger = get_server_logger()
logger.log_exception(exc, f"Unexpected error retrieving workflow: {filename}")
raise WorkflowExecutionError(f"Failed to retrieve workflow: {exc}")