fix(uploads): write workspaceFiles row when issuing presigned URL (#4537)

* fix(uploads): write workspaceFiles row when issuing presigned URL

* test(uploads): cover insertFileMetadata failure path in presigned route
This commit is contained in:
Waleed
2026-05-09 14:44:10 -07:00
committed by GitHub
parent 02f381d08c
commit f122b686a4
2 changed files with 152 additions and 0 deletions
@@ -25,6 +25,7 @@ const {
mockGetUserEntityPermissions,
mockGenerateWorkspaceFileKey,
mockGenerateExecutionFileKey,
mockInsertFileMetadata,
} = vi.hoisted(() => ({
mockVerifyFileAccess: vi.fn().mockResolvedValue(true),
mockVerifyWorkspaceFileAccess: vi.fn().mockResolvedValue(true),
@@ -50,6 +51,7 @@ const {
(ctx: { workspaceId: string; workflowId: string; executionId: string }, fileName: string) =>
`execution/${ctx.workspaceId}/${ctx.workflowId}/${ctx.executionId}/${fileName}`
),
mockInsertFileMetadata: vi.fn().mockResolvedValue({ id: 'wf_test' }),
}))
vi.mock('@/app/api/files/authorization', () => ({
@@ -89,6 +91,10 @@ vi.mock('@/lib/uploads/contexts/execution/utils', () => ({
generateExecutionFileKey: mockGenerateExecutionFileKey,
}))
vi.mock('@/lib/uploads/server/metadata', () => ({
insertFileMetadata: mockInsertFileMetadata,
}))
vi.mock('@/lib/uploads/utils/file-utils', () => ({
isImageFileType: mockIsImageFileType,
}))
@@ -614,6 +620,57 @@ describe('/api/files/presigned', () => {
const response = await POST(request)
expect(response.status).toBe(403)
})
it('inserts a workspaceFiles row with context=mothership so previews authorize', async () => {
setupFileApiMocks({ cloudEnabled: true, storageProvider: 's3' })
const request = new NextRequest(
'http://localhost:3000/api/files/presigned?type=mothership&workspaceId=ws-1',
{
method: 'POST',
body: JSON.stringify({
fileName: 'screenshot.png',
contentType: 'image/png',
fileSize: 4096,
}),
}
)
const response = await POST(request)
const data = await response.json()
expect(response.status).toBe(200)
expect(mockInsertFileMetadata).toHaveBeenCalledTimes(1)
expect(mockInsertFileMetadata).toHaveBeenCalledWith({
key: data.fileInfo.key,
userId: 'test-user-id',
workspaceId: 'ws-1',
context: 'mothership',
originalName: 'screenshot.png',
contentType: 'image/png',
size: 4096,
})
})
it('returns 500 when insertFileMetadata fails so callers do not get an unauthorizable URL', async () => {
setupFileApiMocks({ cloudEnabled: true, storageProvider: 's3' })
mockInsertFileMetadata.mockRejectedValueOnce(new Error('DB connection lost'))
const request = new NextRequest(
'http://localhost:3000/api/files/presigned?type=mothership&workspaceId=ws-1',
{
method: 'POST',
body: JSON.stringify({
fileName: 'screenshot.png',
contentType: 'image/png',
fileSize: 4096,
}),
}
)
const response = await POST(request)
expect(response.status).toBe(500)
})
})
describe('execution uploads', () => {
@@ -682,6 +739,70 @@ describe('/api/files/presigned', () => {
const response = await POST(request)
expect(response.status).toBe(400)
})
it('inserts a workspaceFiles row with context=execution so previews authorize', async () => {
setupFileApiMocks({ cloudEnabled: true, storageProvider: 's3' })
const request = new NextRequest(
'http://localhost:3000/api/files/presigned?type=execution&workspaceId=ws-1&workflowId=wf-1&executionId=exec-1',
{
method: 'POST',
body: JSON.stringify({
fileName: 'output.mp4',
contentType: 'video/mp4',
fileSize: 4096,
}),
}
)
const response = await POST(request)
const data = await response.json()
expect(response.status).toBe(200)
expect(mockInsertFileMetadata).toHaveBeenCalledTimes(1)
expect(mockInsertFileMetadata).toHaveBeenCalledWith({
key: data.fileInfo.key,
userId: 'test-user-id',
workspaceId: 'ws-1',
context: 'execution',
originalName: 'output.mp4',
contentType: 'video/mp4',
size: 4096,
})
})
})
describe('workspace-logos uploads', () => {
it('inserts a workspaceFiles row with context=workspace-logos so logos authorize', async () => {
setupFileApiMocks({ cloudEnabled: true, storageProvider: 's3' })
const request = new NextRequest(
'http://localhost:3000/api/files/presigned?type=workspace-logos&workspaceId=ws-1',
{
method: 'POST',
body: JSON.stringify({
fileName: 'logo.png',
contentType: 'image/png',
fileSize: 4096,
}),
}
)
const response = await POST(request)
const data = await response.json()
expect(response.status).toBe(200)
expect(mockInsertFileMetadata).toHaveBeenCalledTimes(1)
expect(mockInsertFileMetadata).toHaveBeenCalledWith({
key: data.fileInfo.key,
userId: 'test-user-id',
workspaceId: 'ws-1',
context: 'workspace-logos',
originalName: 'logo.png',
contentType: 'image/png',
size: 4096,
})
})
})
describe('knowledge-base uploads', () => {
+31
View File
@@ -10,6 +10,7 @@ import { USE_BLOB_STORAGE } from '@/lib/uploads/config'
import { generateExecutionFileKey } from '@/lib/uploads/contexts/execution/utils'
import { generateWorkspaceFileKey } from '@/lib/uploads/contexts/workspace/workspace-file-manager'
import { generatePresignedUploadUrl, hasCloudStorage } from '@/lib/uploads/core/storage-service'
import { insertFileMetadata } from '@/lib/uploads/server/metadata'
import { isImageFileType } from '@/lib/uploads/utils/file-utils'
import { validateAttachmentFileType, validateFileType } from '@/lib/uploads/utils/validation'
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
@@ -157,6 +158,16 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
expirationSeconds: 3600,
metadata: { workspaceId },
})
await insertFileMetadata({
key: presignedUrlResponse.key,
userId: sessionUserId,
workspaceId,
context: 'mothership',
originalName: fileName,
contentType,
size: fileSize,
})
} else if (uploadType === 'execution') {
const workflowId = request.nextUrl.searchParams.get('workflowId')
const executionId = request.nextUrl.searchParams.get('executionId')
@@ -191,6 +202,16 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
expirationSeconds: 3600,
metadata: { workspaceId, workflowId, executionId },
})
await insertFileMetadata({
key: presignedUrlResponse.key,
userId: sessionUserId,
workspaceId,
context: 'execution',
originalName: fileName,
contentType,
size: fileSize,
})
} else if (uploadType === 'workspace-logos') {
const workspaceId = request.nextUrl.searchParams.get('workspaceId')
if (!workspaceId?.trim()) {
@@ -222,6 +243,16 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
expirationSeconds: 3600,
metadata: { workspaceId },
})
await insertFileMetadata({
key: presignedUrlResponse.key,
userId: sessionUserId,
workspaceId,
context: 'workspace-logos',
originalName: fileName,
contentType,
size: fileSize,
})
} else {
if (uploadType === 'profile-pictures') {
if (!sessionUserId?.trim()) {