diff --git a/frontend/src/utils/apiFunctions.js b/frontend/src/utils/apiFunctions.js index fe536853..ba71191c 100755 --- a/frontend/src/utils/apiFunctions.js +++ b/frontend/src/utils/apiFunctions.js @@ -197,7 +197,7 @@ export async function fetchWorkflowsWithDesc() { const filesWithDesc = await Promise.all( data.workflows.map(async (filename) => { try { - const response = await fetch(apiUrl(`/api/workflows/${encodeURIComponent(filename)}/get`)) + const response = await fetch(apiUrl(`/api/workflows/${encodeURIComponent(filename)}/desc`)) const fileData = await response.json() return { name: filename, diff --git a/server/bootstrap.py b/server/bootstrap.py index 25ba6665..17942b85 100755 --- a/server/bootstrap.py +++ b/server/bootstrap.py @@ -1,8 +1,15 @@ +"""Application bootstrap helpers for the FastAPI server.""" + from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from server import state from server.config_schema_router import router as config_schema_router +from server.routes import ALL_ROUTERS +from utils.error_handler import add_exception_handlers +from utils.middleware import add_middleware + + def init_app(app: FastAPI) -> None: """Apply shared middleware, routers, and global state to ``app``.""" @@ -15,4 +22,11 @@ def init_app(app: FastAPI) -> None: ) add_exception_handlers(app) - add_middleware(app) \ No newline at end of file + add_middleware(app) + + state.init_state() + + for router in ALL_ROUTERS: + app.include_router(router) + + app.include_router(config_schema_router) diff --git a/server/routes/workflows.py b/server/routes/workflows.py index f0ff4dd1..70d22c57 100755 --- a/server/routes/workflows.py +++ b/server/routes/workflows.py @@ -153,6 +153,69 @@ async def get_workflow_args(filename: str): ) +@router.get("/api/workflows/{filename}/desc") +async def get_workflow_desc(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, + ) + + # Load and validate YAML content + raw_content = file_path.read_text(encoding="utf-8") + _, yaml_content = validate_workflow_content(safe_filename, raw_content) + + desc = "" + if isinstance(yaml_content, dict): + graph = yaml_content.get("graph") or {} + if isinstance(graph, dict): + desc = graph.get("description") or "" + if len(desc) == 0: + raise ResourceNotFoundError( + "Workflow file does not have args", + resource_type="workflow", + resource_id=safe_filename, + ) + logger = get_server_logger() + logger.info( + "Workflow description retrieved", + log_type=LogType.WORKFLOW, + filename=safe_filename, + ) + return {"description": desc} + except ValidationError as exc: + # 参数或文件名等校验错误 + raise HTTPException( + status_code=400, + detail={"message": str(exc)}, + ) + except SecurityError as exc: + # 安全相关错误(例如路径遍历) + raise HTTPException( + status_code=400, + detail={"message": str(exc)}, + ) + except ResourceNotFoundError as exc: + # 文件不存在 + raise HTTPException( + status_code=404, + detail={"message": str(exc)}, + ) + except Exception as exc: + logger = get_server_logger() + logger.log_exception(exc, f"Unexpected error retrieving workflow args: {filename}") + # 兜底错误 + raise HTTPException( + status_code=500, + detail={"message": f"Failed to retrieve workflow args: {exc}"}, + ) + + @router.post("/api/workflows/upload/content") async def upload_workflow_content(request: WorkflowUploadContentRequest): return _persist_workflow_from_content( @@ -294,4 +357,5 @@ async def get_workflow_raw_content(filename: str): 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}") \ No newline at end of file + raise WorkflowExecutionError(f"Failed to retrieve workflow: {exc}") + diff --git a/server/services/workflow_storage.py b/server/services/workflow_storage.py index a613aa60..51c35402 100755 --- a/server/services/workflow_storage.py +++ b/server/services/workflow_storage.py @@ -18,31 +18,16 @@ from utils.structured_logger import get_server_logger, LogType def _update_workflow_id(content: str, workflow_id: str) -> str: - # Pattern to match graph:\n id: - pattern = re.compile(r"(graph:\s*\n\s*id:\s*).*$", re.MULTILINE) + pattern = re.compile(r"^(id:\\s*).*$", re.MULTILINE) match = pattern.search(content) if match: - # Replace the value after "graph:\n id: " - return pattern.sub(rf"\1{workflow_id}", content, count=1) + return pattern.sub(rf"\\1{workflow_id}", content, count=1) - # If no graph.id found, look for standalone id: at root level (legacy support) - root_id_pattern = re.compile(r"^(id:\s*).*$", re.MULTILINE) - root_match = root_id_pattern.search(content) - if root_match: - return root_id_pattern.sub(rf"\1{workflow_id}", content, count=1) - - # If neither found, add graph.id after graph: section if it exists - graph_pattern = re.compile(r"(graph:\s*\n)") - graph_match = graph_pattern.search(content) - if graph_match: - return graph_pattern.sub(rf"\1 id: {workflow_id}\n", content, count=1) - - # Fallback (is invalid) lines = content.splitlines() insert_index = 0 if lines and lines[0].strip() == "---": insert_index = 1 - lines.insert(insert_index, f"graph:\n id: {workflow_id}") + lines.insert(insert_index, f"id: {workflow_id}") updated = "\n".join(lines) if content.endswith("\n"): updated += "\n"