-
Notifications
You must be signed in to change notification settings - Fork 3.2k
feat(execution-filesystem): system to pass files between blocks #866
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
497066b
feat(files): pass files between blocks
icecrasher321 c6dacb9
presigned URL for downloads
icecrasher321 4a2f616
Remove latest migration before merge
icecrasher321 06d48dc
Merge remote-tracking branch 'origin/staging' into feat/files-support
icecrasher321 0101c8b
starter block file upload wasn't getting logged
icecrasher321 24ec063
checkpoint in human readable form
icecrasher321 57695dd
checkpoint files / file type outputs
icecrasher321 83c98fe
file downloads working for block outputs
icecrasher321 d52b3df
checkpoint file download
icecrasher321 abe8eea
fix type issues
icecrasher321 638617e
remove filereference interface with simpler user file interface
icecrasher321 655d4b6
show files in the tag dropdown for start block
icecrasher321 d74ba31
more migration to simple url object, reduce presigned time to 5 min
icecrasher321 82f3699
Remove migration 0065_parallel_nightmare and related files
icecrasher321 ced42d3
Merge remote-tracking branch 'origin/staging' into feat/files-support
icecrasher321 9844b6b
add migration files
icecrasher321 86fdf92
fix tests
icecrasher321 405d2e1
Update apps/sim/lib/uploads/setup.ts
icecrasher321 48d8408
Update apps/sim/lib/workflows/execution-file-storage.ts
icecrasher321 5e1cd9a
Update apps/sim/lib/workflows/execution-file-storage.ts
icecrasher321 b4bb0b2
cleanup types
icecrasher321 0336db0
Merge branch 'feat/files-support' of github.com:simstudioai/sim into …
icecrasher321 2e764de
fix lint
icecrasher321 efcfa76
fix logs typing for file refs
icecrasher321 053d28f
open download in new tab
icecrasher321 16aa263
fixed
icecrasher321 3fb5b5e
Update apps/sim/tools/index.ts
icecrasher321 24989ca
fix file block
icecrasher321 174f011
Merge branch 'feat/files-support' of github.com:simstudioai/sim into …
icecrasher321 b1bb313
cleanup unused code
icecrasher321 fbdb1ba
fix bugs
icecrasher321 35e054b
remove hacky file id logic
icecrasher321 3fe0a0f
fix drag and drop
icecrasher321 c3a9578
fix tests
icecrasher321 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| import { type NextRequest, NextResponse } from 'next/server' | ||
| import { createLogger } from '@/lib/logs/console/logger' | ||
| import { getPresignedUrl, getPresignedUrlWithConfig, isUsingCloudStorage } from '@/lib/uploads' | ||
| import { BLOB_EXECUTION_FILES_CONFIG, S3_EXECUTION_FILES_CONFIG } from '@/lib/uploads/setup' | ||
| import { createErrorResponse } from '@/app/api/files/utils' | ||
|
|
||
| const logger = createLogger('FileDownload') | ||
|
|
||
| export const dynamic = 'force-dynamic' | ||
|
|
||
| export async function POST(request: NextRequest) { | ||
| try { | ||
| const body = await request.json() | ||
| const { key, name, storageProvider, bucketName, isExecutionFile } = body | ||
|
|
||
| if (!key) { | ||
| return createErrorResponse(new Error('File key is required'), 400) | ||
| } | ||
|
|
||
| logger.info(`Generating download URL for file: ${name || key}`) | ||
|
|
||
| if (isUsingCloudStorage()) { | ||
| // Generate a fresh 5-minute presigned URL for cloud storage | ||
| try { | ||
| let downloadUrl: string | ||
|
|
||
| // Use execution files storage if flagged as execution file | ||
| if (isExecutionFile) { | ||
| logger.info(`Using execution files storage for file: ${key}`) | ||
| downloadUrl = await getPresignedUrlWithConfig( | ||
| key, | ||
| { | ||
| bucket: S3_EXECUTION_FILES_CONFIG.bucket, | ||
| region: S3_EXECUTION_FILES_CONFIG.region, | ||
| }, | ||
| 5 * 60 // 5 minutes | ||
| ) | ||
| } else if (storageProvider && (storageProvider === 's3' || storageProvider === 'blob')) { | ||
| // Use explicitly specified storage provider (legacy support) | ||
| logger.info(`Using specified storage provider '${storageProvider}' for file: ${key}`) | ||
|
|
||
| if (storageProvider === 's3') { | ||
| downloadUrl = await getPresignedUrlWithConfig( | ||
| key, | ||
| { | ||
| bucket: bucketName || S3_EXECUTION_FILES_CONFIG.bucket, | ||
| region: S3_EXECUTION_FILES_CONFIG.region, | ||
| }, | ||
| 5 * 60 // 5 minutes | ||
| ) | ||
| } else { | ||
| // blob | ||
| downloadUrl = await getPresignedUrlWithConfig( | ||
| key, | ||
| { | ||
| accountName: BLOB_EXECUTION_FILES_CONFIG.accountName, | ||
| accountKey: BLOB_EXECUTION_FILES_CONFIG.accountKey, | ||
| connectionString: BLOB_EXECUTION_FILES_CONFIG.connectionString, | ||
| containerName: bucketName || BLOB_EXECUTION_FILES_CONFIG.containerName, | ||
| }, | ||
| 5 * 60 // 5 minutes | ||
| ) | ||
| } | ||
| } else { | ||
| // Use default storage (regular uploads) | ||
| logger.info(`Using default storage for file: ${key}`) | ||
| downloadUrl = await getPresignedUrl(key, 5 * 60) // 5 minutes | ||
| } | ||
|
|
||
| return NextResponse.json({ | ||
| downloadUrl, | ||
| expiresIn: 300, // 5 minutes in seconds | ||
| fileName: name || key.split('/').pop() || 'download', | ||
| }) | ||
| } catch (error) { | ||
| logger.error(`Failed to generate presigned URL for ${key}:`, error) | ||
| return createErrorResponse( | ||
| error instanceof Error ? error : new Error('Failed to generate download URL'), | ||
| 500 | ||
| ) | ||
| } | ||
| } else { | ||
| // For local storage, return the direct path | ||
| const downloadUrl = `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/api/files/serve/${key}` | ||
|
|
||
| return NextResponse.json({ | ||
| downloadUrl, | ||
| expiresIn: null, // Local URLs don't expire | ||
| fileName: name || key.split('/').pop() || 'download', | ||
| }) | ||
| } | ||
| } catch (error) { | ||
| logger.error('Error in file download endpoint:', error) | ||
| return createErrorResponse( | ||
| error instanceof Error ? error : new Error('Internal server error'), | ||
| 500 | ||
| ) | ||
| } | ||
| } | ||
70 changes: 70 additions & 0 deletions
70
apps/sim/app/api/files/execution/[executionId]/[fileId]/route.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| import { type NextRequest, NextResponse } from 'next/server' | ||
| import { createLogger } from '@/lib/logs/console/logger' | ||
| import { generateExecutionFileDownloadUrl } from '@/lib/workflows/execution-file-storage' | ||
| import { getExecutionFiles } from '@/lib/workflows/execution-files-server' | ||
| import type { UserFile } from '@/executor/types' | ||
|
|
||
| const logger = createLogger('ExecutionFileDownloadAPI') | ||
|
|
||
| /** | ||
| * Generate a short-lived presigned URL for secure execution file download | ||
| * GET /api/files/execution/[executionId]/[fileId] | ||
| */ | ||
| export async function GET( | ||
| request: NextRequest, | ||
| { params }: { params: Promise<{ executionId: string; fileId: string }> } | ||
| ) { | ||
| try { | ||
| const { executionId, fileId } = await params | ||
|
|
||
| if (!executionId || !fileId) { | ||
| return NextResponse.json({ error: 'Execution ID and File ID are required' }, { status: 400 }) | ||
| } | ||
|
|
||
| logger.info(`Generating download URL for file ${fileId} in execution ${executionId}`) | ||
|
|
||
| // Get files for this execution | ||
| const executionFiles = await getExecutionFiles(executionId) | ||
|
|
||
| if (executionFiles.length === 0) { | ||
| return NextResponse.json({ error: 'No files found for this execution' }, { status: 404 }) | ||
| } | ||
|
|
||
| // Find the specific file | ||
| const file = executionFiles.find((f) => f.id === fileId) | ||
| if (!file) { | ||
| return NextResponse.json({ error: 'File not found in this execution' }, { status: 404 }) | ||
| } | ||
|
|
||
| // Check if file is expired | ||
| if (new Date(file.expiresAt) < new Date()) { | ||
| return NextResponse.json({ error: 'File has expired' }, { status: 410 }) | ||
| } | ||
|
|
||
| // Since ExecutionFileMetadata is now just UserFile, no conversion needed | ||
| const userFile: UserFile = file | ||
|
|
||
| // Generate a new short-lived presigned URL (5 minutes) | ||
| const downloadUrl = await generateExecutionFileDownloadUrl(userFile) | ||
|
|
||
| logger.info(`Generated download URL for file ${file.name} (execution: ${executionId})`) | ||
|
|
||
| const response = NextResponse.json({ | ||
| downloadUrl, | ||
| fileName: file.name, | ||
| fileSize: file.size, | ||
| fileType: file.type, | ||
| expiresIn: 300, // 5 minutes | ||
| }) | ||
|
|
||
| // Ensure no caching of download URLs | ||
| response.headers.set('Cache-Control', 'no-cache, no-store, must-revalidate') | ||
| response.headers.set('Pragma', 'no-cache') | ||
| response.headers.set('Expires', '0') | ||
|
|
||
| return response | ||
| } catch (error) { | ||
| logger.error('Error generating execution file download URL:', error) | ||
| return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.