From aa1b158b261927590b8fcb1d20753da151b580f1 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 3 Feb 2026 15:50:50 -0800 Subject: [PATCH] fix dropbox --- .../sim/app/api/tools/dropbox/upload/route.ts | 140 ++++++++++++++++++ apps/sim/blocks/blocks/dropbox.ts | 16 +- apps/sim/tools/dropbox/types.ts | 4 +- apps/sim/tools/dropbox/upload.ts | 77 ++++------ 4 files changed, 183 insertions(+), 54 deletions(-) create mode 100644 apps/sim/app/api/tools/dropbox/upload/route.ts diff --git a/apps/sim/app/api/tools/dropbox/upload/route.ts b/apps/sim/app/api/tools/dropbox/upload/route.ts new file mode 100644 index 000000000..f8544e05d --- /dev/null +++ b/apps/sim/app/api/tools/dropbox/upload/route.ts @@ -0,0 +1,140 @@ +import { createLogger } from '@sim/logger' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { generateRequestId } from '@/lib/core/utils/request' +import { FileInputSchema } from '@/lib/uploads/utils/file-schemas' +import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' +import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('DropboxUploadAPI') + +/** + * Escapes non-ASCII characters in JSON string for HTTP header safety. + * Dropbox API requires characters 0x7F and all non-ASCII to be escaped as \uXXXX. + */ +function httpHeaderSafeJson(value: object): string { + return JSON.stringify(value).replace(/[\u007f-\uffff]/g, (c) => { + return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4) + }) +} + +const DropboxUploadSchema = z.object({ + accessToken: z.string().min(1, 'Access token is required'), + path: z.string().min(1, 'Destination path is required'), + file: FileInputSchema.optional().nullable(), + // Legacy field for backwards compatibility + fileContent: z.string().optional().nullable(), + fileName: z.string().optional().nullable(), + mode: z.enum(['add', 'overwrite']).optional().nullable(), + autorename: z.boolean().optional().nullable(), + mute: z.boolean().optional().nullable(), +}) + +export async function POST(request: NextRequest) { + const requestId = generateRequestId() + + try { + const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) + + if (!authResult.success) { + logger.warn(`[${requestId}] Unauthorized Dropbox upload attempt: ${authResult.error}`) + return NextResponse.json( + { success: false, error: authResult.error || 'Authentication required' }, + { status: 401 } + ) + } + + logger.info(`[${requestId}] Authenticated Dropbox upload request via ${authResult.authType}`) + + const body = await request.json() + const validatedData = DropboxUploadSchema.parse(body) + + let fileBuffer: Buffer + let fileName: string + + // Prefer UserFile input, fall back to legacy base64 string + if (validatedData.file) { + // Process UserFile input + const userFiles = processFilesToUserFiles([validatedData.file], requestId, logger) + + if (userFiles.length === 0) { + return NextResponse.json({ success: false, error: 'Invalid file input' }, { status: 400 }) + } + + const userFile = userFiles[0] + logger.info(`[${requestId}] Downloading file: ${userFile.name} (${userFile.size} bytes)`) + + fileBuffer = await downloadFileFromStorage(userFile, requestId, logger) + fileName = userFile.name + } else if (validatedData.fileContent) { + // Legacy: base64 string input (backwards compatibility) + logger.info(`[${requestId}] Using legacy base64 content input`) + fileBuffer = Buffer.from(validatedData.fileContent, 'base64') + fileName = validatedData.fileName || 'file' + } else { + return NextResponse.json( + { success: false, error: 'File or file content is required' }, + { status: 400 } + ) + } + + // Determine final path + let finalPath = validatedData.path + if (finalPath.endsWith('/')) { + finalPath = `${finalPath}${fileName}` + } + + logger.info(`[${requestId}] Uploading to Dropbox: ${finalPath} (${fileBuffer.length} bytes)`) + + const dropboxApiArg = { + path: finalPath, + mode: validatedData.mode || 'add', + autorename: validatedData.autorename ?? true, + mute: validatedData.mute ?? false, + } + + const response = await fetch('https://content.dropboxapi.com/2/files/upload', { + method: 'POST', + headers: { + Authorization: `Bearer ${validatedData.accessToken}`, + 'Content-Type': 'application/octet-stream', + 'Dropbox-API-Arg': httpHeaderSafeJson(dropboxApiArg), + }, + body: fileBuffer, + }) + + const data = await response.json() + + if (!response.ok) { + const errorMessage = data.error_summary || data.error?.message || 'Failed to upload file' + logger.error(`[${requestId}] Dropbox API error:`, { status: response.status, data }) + return NextResponse.json({ success: false, error: errorMessage }, { status: response.status }) + } + + logger.info(`[${requestId}] File uploaded successfully to ${data.path_display}`) + + return NextResponse.json({ + success: true, + output: { + file: data, + }, + }) + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn(`[${requestId}] Validation error:`, error.errors) + return NextResponse.json( + { success: false, error: error.errors[0]?.message || 'Validation failed' }, + { status: 400 } + ) + } + + logger.error(`[${requestId}] Unexpected error:`, error) + return NextResponse.json( + { success: false, error: error instanceof Error ? error.message : 'Unknown error' }, + { status: 500 } + ) + } +} diff --git a/apps/sim/blocks/blocks/dropbox.ts b/apps/sim/blocks/blocks/dropbox.ts index 044625cc6..8bea2b7ea 100644 --- a/apps/sim/blocks/blocks/dropbox.ts +++ b/apps/sim/blocks/blocks/dropbox.ts @@ -64,7 +64,7 @@ export const DropboxBlock: BlockConfig = { id: 'uploadFile', title: 'File', type: 'file-upload', - canonicalParamId: 'fileContent', + canonicalParamId: 'file', placeholder: 'Upload file to send to Dropbox', mode: 'basic', multiple: false, @@ -72,10 +72,10 @@ export const DropboxBlock: BlockConfig = { condition: { field: 'operation', value: 'dropbox_upload' }, }, { - id: 'fileContent', + id: 'fileRef', title: 'File', type: 'short-input', - canonicalParamId: 'fileContent', + canonicalParamId: 'file', placeholder: 'Reference file from previous blocks', mode: 'advanced', required: true, @@ -319,7 +319,11 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, // Normalize file input for upload operation // normalizeFileInput handles JSON stringified values from advanced mode - if (params.fileContent) { + if (params.file) { + params.file = normalizeFileInput(params.file, { single: true }) + } + // Legacy: also check fileContent for backwards compatibility + if (params.fileContent && !params.file) { params.fileContent = normalizeFileInput(params.fileContent, { single: true }) } @@ -358,7 +362,9 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, autorename: { type: 'boolean', description: 'Auto-rename on conflict' }, // Upload inputs uploadFile: { type: 'json', description: 'Uploaded file (UserFile)' }, - fileContent: { type: 'json', description: 'File reference or UserFile object' }, + file: { type: 'json', description: 'File to upload (UserFile object)' }, + fileRef: { type: 'json', description: 'File reference from previous block' }, + fileContent: { type: 'string', description: 'Legacy: base64 encoded file content' }, fileName: { type: 'string', description: 'Optional filename' }, mode: { type: 'string', description: 'Write mode: add or overwrite' }, mute: { type: 'boolean', description: 'Mute notifications' }, diff --git a/apps/sim/tools/dropbox/types.ts b/apps/sim/tools/dropbox/types.ts index 197dd49ec..d722e2810 100644 --- a/apps/sim/tools/dropbox/types.ts +++ b/apps/sim/tools/dropbox/types.ts @@ -71,7 +71,9 @@ export interface DropboxBaseParams { export interface DropboxUploadParams extends DropboxBaseParams { path: string - fileContent: string | UserFileLike + file?: UserFileLike + // Legacy field for backwards compatibility + fileContent?: string fileName?: string mode?: 'add' | 'overwrite' autorename?: boolean diff --git a/apps/sim/tools/dropbox/upload.ts b/apps/sim/tools/dropbox/upload.ts index b43ce6884..1a8914bc4 100644 --- a/apps/sim/tools/dropbox/upload.ts +++ b/apps/sim/tools/dropbox/upload.ts @@ -1,17 +1,6 @@ -import { extractBase64FromFileInput } from '@/lib/core/utils/user-file' import type { DropboxUploadParams, DropboxUploadResponse } from '@/tools/dropbox/types' import type { ToolConfig } from '@/tools/types' -/** - * Escapes non-ASCII characters in JSON string for HTTP header safety. - * Dropbox API requires characters 0x7F and all non-ASCII to be escaped as \uXXXX. - */ -function httpHeaderSafeJson(value: object): string { - return JSON.stringify(value).replace(/[\u007f-\uffff]/g, (c) => { - return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4) - }) -} - export const dropboxUploadTool: ToolConfig = { id: 'dropbox_upload', name: 'Dropbox Upload File', @@ -31,11 +20,18 @@ export const dropboxUploadTool: ToolConfig