Skip to content

feat(files): export workspace files to Google Drive#4938

Open
waleedlatif1 wants to merge 1 commit into
stagingfrom
worktree-files-export-to-drive
Open

feat(files): export workspace files to Google Drive#4938
waleedlatif1 wants to merge 1 commit into
stagingfrom
worktree-files-export-to-drive

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Add an Export action to the Files module that pushes selected workspace files straight to a user's Google Drive, reusing the same OAuth credentials the Google Drive block uses (no new auth flow)
  • The existing Download action becomes a dropdown (Download / Google Drive) in the row context menu, bulk action bar, and file-viewer menu — mirroring the integrations Connect → OAuth pattern
  • Extract a shared uploadBufferToDrive() helper and refactor the Drive tool upload route to use it, so the multipart upload path lives in one place
  • New contract-bound POST /api/workspaces/[id]/files/export-to-drive resolves a token via refreshAccessTokenIfNeeded, reads each file's bytes, uploads to My Drive root, and reports per-file success/failure (a partial failure never aborts the batch)
  • ExportToDriveModal with an account picker + inline connect via a dedicated files OAuth return origin, so connecting from Files returns to Files (not Integrations)

Type of Change

  • New feature

Testing

  • Added route tests covering auth (401/403), validation, missing token, no matching files, full success, partial failure, and id filtering (8/8 passing)
  • Typecheck, Biome, and check:api-validation all clean; workspace-files (26) and oauth-credentials (4) suites passing
  • Deleted/trashed files are excluded (scope='active'); credential picker is user-scoped; token resolution path is identical to the Drive block

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 10, 2026 4:30am

Request Review

@cursor

cursor Bot commented Jun 10, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches OAuth token resolution and streams workspace file bytes to a third-party API; auth and membership gates are in place and behavior mirrors the existing Drive block upload path.

Overview
Adds workspace Files → Google Drive export: users pick files and a connected Drive account; uploads land in My Drive root via existing Google Drive OAuth credentials.

Backend: New shared uploadBufferToDrive() (multipart upload, optional folder, workspace MIME conversion name patch) replaces inline Drive API code in the Google Drive tool upload route. New contract-bound POST /api/workspaces/[id]/files/export-to-drive checks session + workspace membership, refreshes the credential token, loads file bytes, exports up to 50 files per request, and returns per-file exported / failed without stopping the batch on a single failure.

UI: Download becomes an Export submenu (Download / Google Drive) in the bulk bar, row context menu, and file detail actions. ExportToDriveModal handles account selection, inline connect, and sessionStorage resume after OAuth redirect; OAuth return origin files routes back to Files with toast + credential refresh.

Reviewed by Cursor Bugbot for commit 5c9b2d8. Configure here.

Comment thread apps/sim/app/api/workspaces/[id]/files/export-to-drive/route.ts
Comment thread apps/sim/lib/google-drive/upload-to-drive.ts
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@greptile-apps

greptile-apps Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a Export to Google Drive action to the workspace Files module, reusing the existing OAuth credentials from the Google Drive workflow block. The upload logic is extracted into a shared uploadBufferToDrive() helper in lib/google-drive/upload-to-drive.ts, and a new POST /api/workspaces/[id]/files/export-to-drive route handles the batch export with per-file success/failure reporting.

  • A new ExportToDriveModal with a credential picker and inline "Connect account" path (using a dedicated files OAuth return origin) lets users pick a Drive account or connect a new one without leaving the export flow; a sessionStorage-backed pending-export token survives the OAuth full-page redirect and auto-reopens the modal on return.
  • Download actions in the bulk action bar, row context menu, and file-viewer menu are promoted to Export dropdowns (Download / Google Drive) when Drive credentials are available, mirroring the existing integrations connect pattern.
  • 8 route tests cover auth (401/403), validation, missing token, no matching files, full success, partial failure, and id filtering; 3 unit tests cover the uploadBufferToDrive helper including the non-2xx final-metadata guard.

Confidence Score: 5/5

This PR is safe to merge. The new export route, shared upload helper, and modal flow are well-structured and the issues raised in earlier review rounds have all been addressed.

The export route correctly checks auth, workspace membership, and token ownership before touching any Drive API. The shared upload helper properly guards both the multipart upload and the final metadata fetch against non-2xx responses, throwing a typed error in each case. The sessionStorage pending-export mechanism is structurally validated on consume. Test coverage is thorough across the route (8 cases) and the upload helper (3 cases). No logic gaps or security gaps were identified in the changed code.

No files require special attention.

Important Files Changed

Filename Overview
apps/sim/lib/google-drive/upload-to-drive.ts New shared upload helper extracted from the Drive tool route. Correctly guards both the multipart upload response and the final metadata GET against non-2xx status, throwing a typed DriveUploadError. Logs a warning on conversion-rename PATCH failures. Unit-tested.
apps/sim/app/api/workspaces/[id]/files/export-to-drive/route.ts New batch export route. Auth, membership, and token checks are correct; per-file failure isolation is clean; not-found IDs are pre-populated into failed so partial-match batches never silently succeed. Well-tested with 8 cases.
apps/sim/app/workspace/[workspaceId]/files/components/export-to-drive-modal/export-to-drive-modal.tsx New modal component that handles account picker, inline Connect flow, and export mutation. The priorCredentialIds-based auto-selection logic correctly identifies newly-connected accounts after the OAuth round-trip.
apps/sim/app/workspace/[workspaceId]/files/pending-export.ts sessionStorage-backed helper to persist an in-progress export across the OAuth redirect. consume() is destructive (removes on read), and basic structure validation guards against malformed stored data.
apps/sim/hooks/use-oauth-return.ts Adds files origin handling to the OAuth return router and a new useOAuthReturnForFiles hook that shows the success toast and dispatches credential updates on return from OAuth.
apps/sim/app/workspace/[workspaceId]/files/files.tsx Wires up the export flow: openExportToDrive callbacks, pending-export resume on mount, ExportToDriveModal rendering, and clearPendingDriveExport on modal dismiss. onExportToDrive is correctly withheld for folder-only selections.
apps/sim/app/api/tools/google_drive/upload/route.ts Refactored to use the shared uploadBufferToDrive() helper; DriveUploadError is re-mapped to the original error response shape. Behaviour is unchanged, code is substantially simplified.
apps/sim/lib/api/contracts/workspace-files.ts Adds a well-defined contract with min/max bounds on fileIds (1–50), required credentialId, and a typed response schema covering exported/failed arrays.

Sequence Diagram

sequenceDiagram
    actor User
    participant FilesUI as Files Page
    participant ExportModal as ExportToDriveModal
    participant OAuthModal as ConnectOAuthModal
    participant SessionStorage
    participant API as /api/workspaces/[id]/files/export-to-drive
    participant DriveHelper as uploadBufferToDrive
    participant GDrive as Google Drive API

    User->>FilesUI: Select files → Export to Drive
    FilesUI->>ExportModal: open(fileIds, fileNames)
    ExportModal->>ExportModal: Load OAuth credentials (useOAuthCredentials)

    alt No account / Connect new
        ExportModal->>SessionStorage: writePendingDriveExport(fileIds, fileNames, priorCredentialIds)
        ExportModal->>OAuthModal: "open ConnectOAuthModal (origin='files')"
        OAuthModal-->>User: Google OAuth redirect (full page)
        User-->>FilesUI: Return from OAuth
        FilesUI->>SessionStorage: consumePendingDriveExport()
        FilesUI->>ExportModal: re-open with priorCredentialIds (auto-selects new account)
    end

    User->>ExportModal: Click Export
    ExportModal->>API: "POST { fileIds, credentialId }"
    API->>API: getSession() + verifyWorkspaceMembership()
    API->>API: refreshAccessTokenIfNeeded(credentialId, userId)
    API->>API: listWorkspaceFiles(workspaceId)

    loop For each file
        API->>API: fetchWorkspaceFileBuffer(file)
        API->>DriveHelper: "uploadBufferToDrive({ accessToken, name, mimeType, buffer })"
        DriveHelper->>GDrive: POST multipart upload
        GDrive-->>DriveHelper: "{ id }"
        opt MIME conversion
            DriveHelper->>GDrive: PATCH re-apply filename
        end
        DriveHelper->>GDrive: GET file metadata
        GDrive-->>DriveHelper: DriveUploadedFile
        DriveHelper-->>API: DriveUploadedFile
    end

    API-->>ExportModal: "{ success, exported[], failed[] }"
    ExportModal->>User: toast.success (with Open link) / toast.error
    ExportModal->>FilesUI: onOpenChange(false) → clearPendingDriveExport()
Loading

Reviews (5): Last reviewed commit: "feat(files): export workspace files to G..." | Re-trigger Greptile

Comment thread apps/sim/lib/google-drive/upload-to-drive.ts
Comment thread apps/sim/app/workspace/[workspaceId]/files/files.tsx
@greptile-apps

greptile-apps Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds Google Drive export to the workspace Files module, reusing existing OAuth credentials and introducing a shared uploadBufferToDrive helper that both the Drive tool and the new export endpoint delegate to.

  • New POST /api/workspaces/[id]/files/export-to-drive: authenticated, workspace-scoped endpoint with per-file partial-failure handling and a 50-file batch cap enforced at the contract layer.
  • ExportToDriveModal: account picker with auto-select for single credentials, inline OAuth connect via a new files origin, and partial-success toasts with an "Open" deep-link.
  • uploadBufferToDrive helper: extracted multipart upload logic with a typed DriveUploadError; the final metadata GET is not error-checked, which can cause phantom failures that produce duplicate Drive files on retry.

Confidence Score: 4/5

The feature is well-structured and auth/permission checks are solid, but the shared upload helper can produce duplicate Drive files or return incorrect metadata when the final metadata fetch fails transiently.

The upload helper does not check finalResponse.ok before parsing the metadata response. A transient Drive API error at that step causes the function to throw after the file is already in Drive; the export route then marks it as failed, and a user retry produces a duplicate. The fix is a two-line guard but the bug affects both the new export path and the existing Drive tool upload path.

apps/sim/lib/google-drive/upload-to-drive.ts — the final metadata fetch and the post-conversion PATCH both need error handling.

Important Files Changed

Filename Overview
apps/sim/lib/google-drive/upload-to-drive.ts New shared Drive upload helper — correctly extracts multipart upload logic, but the final metadata GET is not error-checked and the post-conversion PATCH silently swallows failures without logging.
apps/sim/app/api/workspaces/[id]/files/export-to-drive/route.ts New export endpoint with solid auth (session + membership check), per-file partial-failure handling, and a 50-file batch cap.
apps/sim/app/api/workspaces/[id]/files/export-to-drive/route.test.ts 8 focused tests covering auth, validation, token resolution, empty results, full success, partial failure, and ID filtering.
apps/sim/app/workspace/[workspaceId]/files/components/export-to-drive-modal/export-to-drive-modal.tsx New modal with auto-select for single credentials, inline OAuth connect, partial-failure toast, and correct modal close semantics.
apps/sim/app/workspace/[workspaceId]/files/files.tsx Wires up export handlers for single-file viewer, bulk action bar, and context menu; correctly guards Drive export to file-only selections.
apps/sim/hooks/use-oauth-return.ts Adds files origin to OAuthReturnRouter and a new useOAuthReturnForFiles hook following the same consume-then-toast pattern as kb-connectors.
apps/sim/lib/api/contracts/workspace-files.ts New contract with a 50-file cap, non-empty fileIds validation, and per-file success/failure response schema.
apps/sim/app/api/tools/google_drive/upload/route.ts Cleanly refactored to delegate to uploadBufferToDrive; DriveUploadError is properly caught and mapped to the original HTTP status code.

Sequence Diagram

sequenceDiagram
    participant UI as Files UI
    participant Modal as ExportToDriveModal
    participant API as POST /files/export-to-drive
    participant Drive as Google Drive API
    participant OAuth as OAuth (refreshAccessTokenIfNeeded)

    UI->>Modal: openExportToDrive(fileIds, fileNames)
    Modal->>API: "POST {fileIds, credentialId}"
    API->>OAuth: refreshAccessTokenIfNeeded(credentialId, userId)
    OAuth-->>API: accessToken
    API->>API: listWorkspaceFiles filter by fileIds
    loop per file
        API->>API: fetchWorkspaceFileBuffer(file)
        API->>Drive: POST multipart upload
        Drive-->>API: "{id}"
        API->>Drive: "GET files/{id}?fields=..."
        Drive-->>API: DriveUploadedFile
    end
    API-->>Modal: "{success, exported[], failed[]}"
    Modal->>UI: toast.success / toast.error
Loading

Reviews (2): Last reviewed commit: "feat(files): export workspace files to G..." | Re-trigger Greptile

Comment thread apps/sim/lib/google-drive/upload-to-drive.ts
Comment thread apps/sim/lib/google-drive/upload-to-drive.ts
@waleedlatif1 waleedlatif1 force-pushed the worktree-files-export-to-drive branch from 13fbcb3 to 6d9c0d1 Compare June 10, 2026 04:14
@waleedlatif1 waleedlatif1 force-pushed the worktree-files-export-to-drive branch from 6d9c0d1 to 7804a50 Compare June 10, 2026 04:16
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/app/workspace/[workspaceId]/files/pending-export.ts
Add an Export action to the Files module that pushes selected workspace
files straight to a user's Google Drive, reusing the same OAuth
credentials the Google Drive block uses. The existing Download action
becomes a dropdown (Download / Google Drive) in the row context menu,
bulk action bar, and file-viewer menu.

- Shared uploadBufferToDrive() helper; refactor the Drive tool upload
  route to use it (single upload path)
- Contract-bound POST /api/workspaces/[id]/files/export-to-drive with
  per-file error reporting; tokens via refreshAccessTokenIfNeeded
- ExportToDriveModal: account picker + inline connect via a dedicated
  'files' OAuth return origin
- Route tests cover auth, validation, token, no-files, success, partial
@waleedlatif1 waleedlatif1 force-pushed the worktree-files-export-to-drive branch from 7804a50 to 5c9b2d8 Compare June 10, 2026 04:30
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile review

1 similar comment
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 5c9b2d8. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant