From cb074b00766b20eca35b31fd8320c7df9ab8a332 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Tue, 27 Jan 2026 23:27:23 -0600 Subject: [PATCH] refactor(backend): extract shared download logic into helper function Both download_file and download_file_by_path now use _create_file_download_response() to eliminate ~40 lines of duplicated download handling code. Co-Authored-By: Claude Opus 4.5 --- .../backend/api/features/workspace/routes.py | 131 +++++++----------- 1 file changed, 51 insertions(+), 80 deletions(-) diff --git a/autogpt_platform/backend/backend/api/features/workspace/routes.py b/autogpt_platform/backend/backend/api/features/workspace/routes.py index ca2a43921b..ae485cedc0 100644 --- a/autogpt_platform/backend/backend/api/features/workspace/routes.py +++ b/autogpt_platform/backend/backend/api/features/workspace/routes.py @@ -58,6 +58,55 @@ def _file_to_info(file) -> WorkspaceFileInfo: ) +async def _create_file_download_response(file) -> Response: + """ + Create a download response for a workspace file. + + Handles both local storage (direct streaming) and GCS (signed URL redirect + with fallback to streaming). + """ + storage = await get_workspace_storage() + + # For local storage, stream the file directly + if file.storagePath.startswith("local://"): + content = await storage.retrieve(file.storagePath) + return Response( + content=content, + media_type=file.mimeType, + headers={ + "Content-Disposition": f'attachment; filename="{file.name}"', + "Content-Length": str(len(content)), + }, + ) + + # For GCS, try to redirect to signed URL, fall back to streaming + try: + url = await storage.get_download_url(file.storagePath, expires_in=300) + # If we got back an API path (fallback), stream directly instead + if url.startswith("/api/"): + content = await storage.retrieve(file.storagePath) + return Response( + content=content, + media_type=file.mimeType, + headers={ + "Content-Disposition": f'attachment; filename="{file.name}"', + "Content-Length": str(len(content)), + }, + ) + return fastapi.responses.RedirectResponse(url=url, status_code=302) + except Exception: + # Fall back to streaming directly from GCS + content = await storage.retrieve(file.storagePath) + return Response( + content=content, + media_type=file.mimeType, + headers={ + "Content-Disposition": f'attachment; filename="{file.name}"', + "Content-Length": str(len(content)), + }, + ) + + @router.get( "", summary="Get workspace info", @@ -275,46 +324,7 @@ async def download_file( if file is None: raise fastapi.HTTPException(status_code=404, detail="File not found") - storage = await get_workspace_storage() - - # For local storage, stream the file directly - if file.storagePath.startswith("local://"): - content = await storage.retrieve(file.storagePath) - return Response( - content=content, - media_type=file.mimeType, - headers={ - "Content-Disposition": f'attachment; filename="{file.name}"', - "Content-Length": str(len(content)), - }, - ) - - # For GCS, try to redirect to signed URL, fall back to streaming - try: - url = await storage.get_download_url(file.storagePath, expires_in=300) - # If we got back an API path (fallback), stream directly instead - if url.startswith("/api/"): - content = await storage.retrieve(file.storagePath) - return Response( - content=content, - media_type=file.mimeType, - headers={ - "Content-Disposition": f'attachment; filename="{file.name}"', - "Content-Length": str(len(content)), - }, - ) - return fastapi.responses.RedirectResponse(url=url, status_code=302) - except Exception: - # Fall back to streaming directly from GCS - content = await storage.retrieve(file.storagePath) - return Response( - content=content, - media_type=file.mimeType, - headers={ - "Content-Disposition": f'attachment; filename="{file.name}"', - "Content-Length": str(len(content)), - }, - ) + return await _create_file_download_response(file) @router.get( @@ -423,46 +433,7 @@ async def download_file_by_path( if file is None: raise fastapi.HTTPException(status_code=404, detail="File not found") - storage = await get_workspace_storage() - - # For local storage, stream the file directly - if file.storagePath.startswith("local://"): - content = await storage.retrieve(file.storagePath) - return Response( - content=content, - media_type=file.mimeType, - headers={ - "Content-Disposition": f'attachment; filename="{file.name}"', - "Content-Length": str(len(content)), - }, - ) - - # For GCS, try to redirect to signed URL, fall back to streaming - try: - url = await storage.get_download_url(file.storagePath, expires_in=300) - # If we got back an API path (fallback), stream directly instead - if url.startswith("/api/"): - content = await storage.retrieve(file.storagePath) - return Response( - content=content, - media_type=file.mimeType, - headers={ - "Content-Disposition": f'attachment; filename="{file.name}"', - "Content-Length": str(len(content)), - }, - ) - return fastapi.responses.RedirectResponse(url=url, status_code=302) - except Exception: - # Fall back to streaming directly from GCS - content = await storage.retrieve(file.storagePath) - return Response( - content=content, - media_type=file.mimeType, - headers={ - "Content-Disposition": f'attachment; filename="{file.name}"', - "Content-Length": str(len(content)), - }, - ) + return await _create_file_download_response(file) @router.delete(