Skip to content

Commit 8d342ec

Browse files
committed
address comments, antipatterns
1 parent 55da2f6 commit 8d342ec

28 files changed

Lines changed: 954 additions & 489 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,6 @@ i18n.cache
8585
.claude/worktrees/
8686
.claude/scheduled_tasks.lock
8787
.deepsec/
88+
89+
# Personal Cursor Skills
90+
.cursor/skills/ask-sim/

apps/sim/app/api/files/upload/route.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,26 @@ describe('File Upload API Route', () => {
227227
expect(uploadWorkspaceFile).toHaveBeenCalled()
228228
})
229229

230+
it('should accept chunked multipart uploads without a content-length header', async () => {
231+
setupFileApiMocks({
232+
cloudEnabled: false,
233+
storageProvider: 'local',
234+
})
235+
236+
const formData = createMockFormData([createMockFile()])
237+
const req = new NextRequest('http://localhost:3000/api/files/upload', {
238+
method: 'POST',
239+
body: formData,
240+
})
241+
242+
expect(req.headers.get('content-length')).toBeNull()
243+
244+
const response = await POST(req)
245+
246+
expect(response.status).toBe(200)
247+
expect(uploadWorkspaceFile).toHaveBeenCalled()
248+
})
249+
230250
it('should upload a file to S3 when in S3 mode', async () => {
231251
setupFileApiMocks({
232252
cloudEnabled: true,

apps/sim/app/api/files/upload/route.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import {
1111
import { getValidationErrorMessage } from '@/lib/api/server'
1212
import { getSession } from '@/lib/auth'
1313
import {
14-
assertContentLengthWithinLimit,
1514
assertKnownSizeWithinLimit,
1615
isPayloadSizeLimitError,
1716
readFileToBufferWithLimit,
17+
readFormDataWithLimit,
1818
} from '@/lib/core/utils/stream-limits'
1919
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
2020
import { captureServerEvent } from '@/lib/posthog/server'
@@ -50,19 +50,10 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
5050
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
5151
}
5252

53-
if (request.headers && !request.headers.get('content-length')) {
54-
return NextResponse.json(
55-
{ error: 'Content-Length is required for multipart uploads' },
56-
{ status: 411 }
57-
)
58-
}
59-
assertContentLengthWithinLimit(
60-
request.headers,
61-
MAX_WORKSPACE_FORMDATA_FILE_SIZE + MAX_MULTIPART_OVERHEAD_BYTES,
62-
'multipart upload body'
63-
)
64-
65-
const formData = await request.formData()
53+
const formData = await readFormDataWithLimit(request, {
54+
maxBytes: MAX_WORKSPACE_FORMDATA_FILE_SIZE + MAX_MULTIPART_OVERHEAD_BYTES,
55+
label: 'multipart upload body',
56+
})
6657

6758
const rawFiles = formData.getAll('file')
6859
const filesResult = uploadFilesFormFilesSchema.safeParse(rawFiles)

apps/sim/app/api/table/[tableId]/import/route.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,21 @@ describe('POST /api/table/[tableId]/import', () => {
229229
expect(mockReplaceTableRowsWithTx).not.toHaveBeenCalled()
230230
})
231231

232+
it('accepts chunked multipart imports without a content-length header', async () => {
233+
const form = createFormData(createCsvFile('name,age\nAlice,30'), { mode: 'append' })
234+
const req = new NextRequest('http://localhost:3000/api/table/tbl_1/import', {
235+
method: 'POST',
236+
body: form,
237+
})
238+
239+
expect(req.headers.get('content-length')).toBeNull()
240+
241+
const response = await POST(req, { params: Promise.resolve({ tableId: 'tbl_1' }) })
242+
243+
expect(response.status).toBe(200)
244+
expect(mockBatchInsertRowsWithTx).toHaveBeenCalledTimes(1)
245+
})
246+
232247
it('rejects append when it would exceed maxRows', async () => {
233248
mockCheckAccess.mockResolvedValueOnce({
234249
ok: true,

apps/sim/app/api/table/[tableId]/import/route.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ import { getValidationErrorMessage } from '@/lib/api/server'
1515
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
1616
import { generateRequestId } from '@/lib/core/utils/request'
1717
import {
18-
assertContentLengthWithinLimit,
1918
isPayloadSizeLimitError,
2019
readFileToBufferWithLimit,
20+
readFormDataWithLimit,
2121
} from '@/lib/core/utils/stream-limits'
2222
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
2323
import {
@@ -56,19 +56,10 @@ export const POST = withRouteHandler(async (request: NextRequest, { params }: Ro
5656
return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
5757
}
5858

59-
if (request.headers && !request.headers.get('content-length')) {
60-
return NextResponse.json(
61-
{ error: 'Content-Length is required for CSV imports' },
62-
{ status: 411 }
63-
)
64-
}
65-
assertContentLengthWithinLimit(
66-
request.headers,
67-
CSV_MAX_FILE_SIZE_BYTES + MAX_MULTIPART_OVERHEAD_BYTES,
68-
'CSV import body'
69-
)
70-
71-
const formData = await request.formData()
59+
const formData = await readFormDataWithLimit(request, {
60+
maxBytes: CSV_MAX_FILE_SIZE_BYTES + MAX_MULTIPART_OVERHEAD_BYTES,
61+
label: 'CSV import body',
62+
})
7263
const formValidation = csvImportFormSchema.safeParse({
7364
file: formData.get('file'),
7465
workspaceId: formData.get('workspaceId'),

apps/sim/app/api/table/import-csv/route.test.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,17 +90,15 @@ describe('POST /api/table/import-csv', () => {
9090
expect(mockCreateTable).not.toHaveBeenCalled()
9191
})
9292

93-
it('requires content-length when request headers are available', async () => {
93+
it('accepts chunked multipart requests without a content-length header', async () => {
9494
const req = {
9595
headers: new Headers({ 'transfer-encoding': 'chunked' }),
9696
formData: vi.fn(async () => createFormData(createCsvFile('name\nAlice'))),
9797
} as unknown as NextRequest
9898

9999
const response = await POST(req)
100-
const data = await response.json()
101100

102-
expect(response.status).toBe(411)
103-
expect(data.error).toMatch(/Content-Length is required/)
104-
expect(req.formData).not.toHaveBeenCalled()
101+
expect(response.status).not.toBe(411)
102+
expect(req.formData).toHaveBeenCalled()
105103
})
106104
})

apps/sim/app/api/table/import-csv/route.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import { getValidationErrorMessage } from '@/lib/api/server'
77
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
88
import { generateRequestId } from '@/lib/core/utils/request'
99
import {
10-
assertContentLengthWithinLimit,
1110
isPayloadSizeLimitError,
1211
readFileToBufferWithLimit,
12+
readFormDataWithLimit,
1313
} from '@/lib/core/utils/stream-limits'
1414
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
1515
import {
@@ -41,19 +41,10 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
4141
return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
4242
}
4343

44-
if (request.headers && !request.headers.get('content-length')) {
45-
return NextResponse.json(
46-
{ error: 'Content-Length is required for CSV imports' },
47-
{ status: 411 }
48-
)
49-
}
50-
assertContentLengthWithinLimit(
51-
request.headers,
52-
CSV_MAX_FILE_SIZE_BYTES + MAX_MULTIPART_OVERHEAD_BYTES,
53-
'CSV import body'
54-
)
55-
56-
const formData = await request.formData()
44+
const formData = await readFormDataWithLimit(request, {
45+
maxBytes: CSV_MAX_FILE_SIZE_BYTES + MAX_MULTIPART_OVERHEAD_BYTES,
46+
label: 'CSV import body',
47+
})
5748
const validation = csvImportFormSchema.safeParse({
5849
file: formData.get('file'),
5950
workspaceId: formData.get('workspaceId'),

apps/sim/app/api/tools/docusign/route.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import {
88
assertKnownSizeWithinLimit,
99
DEFAULT_MAX_ERROR_BODY_BYTES,
1010
isPayloadSizeLimitError,
11-
PayloadSizeLimitError,
1211
readResponseJsonWithLimit,
1312
readResponseTextWithLimit,
1413
readResponseToBufferWithLimit,
1514
} from '@/lib/core/utils/stream-limits'
1615
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
16+
import { uploadCopilotFile } from '@/lib/uploads/contexts/copilot'
1717
import { uploadExecutionFile } from '@/lib/uploads/contexts/execution'
1818
import { FileInputSchema } from '@/lib/uploads/utils/file-schemas'
1919
import { processFilesToUserFiles, type RawFileInput } from '@/lib/uploads/utils/file-utils'
@@ -23,7 +23,6 @@ import { assertToolFileAccess } from '@/app/api/files/authorization'
2323
const logger = createLogger('DocuSignAPI')
2424
const MAX_DOCUSIGN_DOCUMENT_BYTES = 25 * 1024 * 1024
2525
const MAX_DOCUSIGN_JSON_BYTES = 2 * 1024 * 1024
26-
const MAX_LEGACY_INLINE_DOCUMENT_BYTES = 7 * 1024 * 1024
2726

2827
interface DocuSignAccountInfo {
2928
accountId: string
@@ -140,7 +139,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
140139
case 'void_envelope':
141140
return await handleVoidEnvelope(apiBase, headers, params)
142141
case 'download_document':
143-
return await handleDownloadDocument(apiBase, headers, params)
142+
return await handleDownloadDocument(apiBase, headers, params, authResult.userId)
144143
case 'list_templates':
145144
return await handleListTemplates(apiBase, headers, params)
146145
case 'list_recipients':
@@ -445,7 +444,8 @@ async function handleVoidEnvelope(
445444
async function handleDownloadDocument(
446445
apiBase: string,
447446
headers: Record<string, string>,
448-
params: Record<string, unknown>
447+
params: Record<string, unknown>,
448+
userId: string
449449
) {
450450
const { envelopeId, documentId } = params
451451
if (!envelopeId) {
@@ -509,17 +509,14 @@ async function handleDownloadDocument(
509509
})
510510
}
511511

512-
if (buffer.length > MAX_LEGACY_INLINE_DOCUMENT_BYTES) {
513-
throw new PayloadSizeLimitError({
514-
label: 'DocuSign legacy inline document',
515-
maxBytes: MAX_LEGACY_INLINE_DOCUMENT_BYTES,
516-
observedBytes: buffer.length,
517-
})
518-
}
519-
520-
const base64Content = buffer.toString('base64')
512+
const file = await uploadCopilotFile({
513+
buffer,
514+
fileName,
515+
contentType,
516+
userId,
517+
})
521518

522-
return NextResponse.json({ base64Content, mimeType: contentType, fileName })
519+
return NextResponse.json({ file, mimeType: contentType, fileName })
523520
}
524521

525522
async function handleListTemplates(

0 commit comments

Comments
 (0)