chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -0,0 +1,235 @@
/**
* Tests for file serve API route
*
* @vitest-environment node
*/
import { hybridAuthMockFns, storageServiceMock, storageServiceMockFns } from '@sim/testing'
import { NextRequest } from 'next/server'
import { beforeEach, describe, expect, it, vi } from 'vitest'
const {
mockVerifyFileAccess,
mockReadFile,
mockIsUsingCloudStorage,
mockDownloadCopilotFile,
mockInferContextFromKey,
mockGetContentType,
mockFindLocalFile,
mockCreateFileResponse,
mockCreateErrorResponse,
FileNotFoundError,
} = vi.hoisted(() => {
class FileNotFoundErrorClass extends Error {
constructor(message: string) {
super(message)
this.name = 'FileNotFoundError'
}
}
return {
mockVerifyFileAccess: vi.fn(),
mockReadFile: vi.fn(),
mockIsUsingCloudStorage: vi.fn(),
mockDownloadCopilotFile: vi.fn(),
mockInferContextFromKey: vi.fn(),
mockGetContentType: vi.fn(),
mockFindLocalFile: vi.fn(),
mockCreateFileResponse: vi.fn(),
mockCreateErrorResponse: vi.fn(),
FileNotFoundError: FileNotFoundErrorClass,
}
})
vi.mock('fs/promises', () => ({
readFile: mockReadFile,
access: vi.fn().mockResolvedValue(undefined),
stat: vi.fn().mockResolvedValue({ isFile: () => true, size: 100 }),
}))
vi.mock('@/app/api/files/authorization', () => ({
verifyFileAccess: mockVerifyFileAccess,
}))
vi.mock('@/lib/uploads', () => ({
CopilotFiles: {
downloadCopilotFile: mockDownloadCopilotFile,
},
isUsingCloudStorage: mockIsUsingCloudStorage,
}))
vi.mock('@/lib/uploads/core/storage-service', () => storageServiceMock)
vi.mock('@/lib/uploads/utils/file-utils', () => ({
inferContextFromKey: mockInferContextFromKey,
}))
vi.mock('@/lib/uploads/setup.server', () => ({}))
vi.mock('@/lib/execution/sandbox/run-task', () => ({
runSandboxTask: vi
.fn()
.mockImplementation(async (taskId: string) =>
taskId === 'pdf-generate' ? Buffer.from('%PDF-compiled') : Buffer.from('PK\x03\x04compiled')
),
}))
vi.mock('@/lib/uploads/contexts/workspace/workspace-file-manager', () => ({
parseWorkspaceFileKey: vi.fn().mockReturnValue(undefined),
}))
vi.mock('@/app/api/files/utils', () => ({
FileNotFoundError,
createFileResponse: mockCreateFileResponse,
createErrorResponse: mockCreateErrorResponse,
getContentType: mockGetContentType,
extractStorageKey: vi.fn().mockImplementation((path: string) => path.split('/').pop()),
extractFilename: vi.fn().mockImplementation((path: string) => path.split('/').pop()),
findLocalFile: mockFindLocalFile,
}))
import { GET } from '@/app/api/files/serve/[...path]/route'
describe('File Serve API Route', () => {
beforeEach(() => {
vi.clearAllMocks()
hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({
success: true,
userId: 'test-user-id',
})
mockVerifyFileAccess.mockResolvedValue(true)
mockReadFile.mockResolvedValue(Buffer.from('test content'))
mockIsUsingCloudStorage.mockReturnValue(false)
storageServiceMockFns.mockHasCloudStorage.mockReturnValue(true)
mockInferContextFromKey.mockReturnValue('workspace')
mockGetContentType.mockReturnValue('text/plain')
mockFindLocalFile.mockReturnValue('/test/uploads/test-file.txt')
mockCreateFileResponse.mockImplementation(
(file: { buffer: Buffer; contentType: string; filename: string }) => {
return new Response(file.buffer, {
status: 200,
headers: {
'Content-Type': file.contentType,
'Content-Disposition': `inline; filename="${file.filename}"`,
},
})
}
)
mockCreateErrorResponse.mockImplementation((error: Error) => {
return new Response(JSON.stringify({ error: error.name, message: error.message }), {
status: error.name === 'FileNotFoundError' ? 404 : 500,
headers: { 'Content-Type': 'application/json' },
})
})
})
it('should serve local file successfully', async () => {
const req = new NextRequest(
'http://localhost:3000/api/files/serve/workspace/test-workspace-id/test-file.txt'
)
const params = { path: ['workspace', 'test-workspace-id', 'test-file.txt'] }
const response = await GET(req, { params: Promise.resolve(params) })
expect(response.status).toBe(200)
expect(response.headers.get('Content-Type')).toBe('text/plain')
const disposition = response.headers.get('Content-Disposition')
expect(disposition).toContain('inline')
expect(disposition).toContain('filename=')
expect(disposition).toContain('test-file.txt')
expect(mockReadFile).toHaveBeenCalled()
})
it('should handle nested paths correctly', async () => {
mockFindLocalFile.mockReturnValue('/test/uploads/nested/path/file.txt')
const req = new NextRequest(
'http://localhost:3000/api/files/serve/workspace/test-workspace-id/nested-path-file.txt'
)
const params = { path: ['workspace', 'test-workspace-id', 'nested-path-file.txt'] }
const response = await GET(req, { params: Promise.resolve(params) })
expect(response.status).toBe(200)
expect(mockReadFile).toHaveBeenCalledWith('/test/uploads/nested/path/file.txt')
})
it('should serve cloud file by downloading and proxying', async () => {
mockIsUsingCloudStorage.mockReturnValue(true)
storageServiceMockFns.mockDownloadFile.mockResolvedValue(Buffer.from('test cloud file content'))
mockGetContentType.mockReturnValue('image/png')
const req = new NextRequest(
'http://localhost:3000/api/files/serve/workspace/test-workspace-id/1234567890-image.png'
)
const params = { path: ['workspace', 'test-workspace-id', '1234567890-image.png'] }
const response = await GET(req, { params: Promise.resolve(params) })
expect(response.status).toBe(200)
expect(response.headers.get('Content-Type')).toBe('image/png')
expect(storageServiceMockFns.mockDownloadFile).toHaveBeenCalledWith({
key: 'workspace/test-workspace-id/1234567890-image.png',
context: 'workspace',
})
})
it('should return 404 when file not found', async () => {
mockVerifyFileAccess.mockResolvedValue(false)
mockFindLocalFile.mockReturnValue(null)
const req = new NextRequest(
'http://localhost:3000/api/files/serve/workspace/test-workspace-id/nonexistent.txt'
)
const params = { path: ['workspace', 'test-workspace-id', 'nonexistent.txt'] }
const response = await GET(req, { params: Promise.resolve(params) })
expect(response.status).toBe(404)
const responseData = await response.json()
expect(responseData).toEqual({
error: 'FileNotFoundError',
message: expect.stringContaining('File not found'),
})
})
describe('content type detection', () => {
const contentTypeTests = [
{ ext: 'pdf', contentType: 'application/pdf' },
{ ext: 'json', contentType: 'application/json' },
{ ext: 'jpg', contentType: 'image/jpeg' },
{ ext: 'txt', contentType: 'text/plain' },
{ ext: 'unknown', contentType: 'application/octet-stream' },
]
for (const test of contentTypeTests) {
it(`should serve ${test.ext} file with correct content type`, async () => {
mockGetContentType.mockReturnValue(test.contentType)
mockFindLocalFile.mockReturnValue(`/test/uploads/file.${test.ext}`)
mockCreateFileResponse.mockImplementation(
(obj: { buffer: Buffer; contentType: string; filename: string }) =>
new Response(obj.buffer, {
status: 200,
headers: {
'Content-Type': obj.contentType,
'Content-Disposition': `inline; filename="${obj.filename}"`,
'Cache-Control': 'public, max-age=31536000',
},
})
)
const req = new NextRequest(
`http://localhost:3000/api/files/serve/workspace/test-workspace-id/file.${test.ext}`
)
const params = { path: ['workspace', 'test-workspace-id', `file.${test.ext}`] }
const response = await GET(req, { params: Promise.resolve(params) })
expect(response.headers.get('Content-Type')).toBe(test.contentType)
})
}
})
})
@@ -0,0 +1,345 @@
import { readFile } from 'fs/promises'
import { createLogger } from '@sim/logger'
import type { NextRequest } from 'next/server'
import { NextResponse } from 'next/server'
import { fileServeParamsSchema, fileServeQuerySchema } from '@/lib/api/contracts/storage-transfer'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import {
DocCompileUserError,
resolveServableDocBytes,
} from '@/lib/copilot/tools/server/files/doc-compile'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
import { CopilotFiles, isUsingCloudStorage } from '@/lib/uploads'
import type { StorageContext } from '@/lib/uploads/config'
import { parseWorkspaceFileKey } from '@/lib/uploads/contexts/workspace/workspace-file-manager'
import { downloadFile } from '@/lib/uploads/core/storage-service'
import { inferContextFromKey } from '@/lib/uploads/utils/file-utils'
import { verifyFileAccess } from '@/app/api/files/authorization'
import {
createErrorResponse,
createFileResponse,
FileNotFoundError,
findLocalFile,
getContentType,
} from '@/app/api/files/utils'
const logger = createLogger('FilesServeAPI')
/**
* Resolves the bytes + content type to serve for a stored file via the shared
* {@link resolveServableDocBytes} (generated docs → compiled artifact). `raw=1`
* bypasses resolution and serves the stored source as-is.
*/
async function compileDocumentIfNeeded(
buffer: Buffer,
filename: string,
workspaceId: string | undefined,
raw: boolean,
ownerKey: string | undefined,
signal: AbortSignal | undefined
): Promise<{ buffer: Buffer; contentType: string }> {
if (raw) return { buffer, contentType: getContentType(filename) }
return resolveServableDocBytes({
rawBuffer: buffer,
fileName: filename,
workspaceId,
ownerKey,
signal,
})
}
const STORAGE_KEY_PREFIX_RE = /^\d{13}-[a-z0-9]{7}-/
function stripStorageKeyPrefix(segment: string): string {
return STORAGE_KEY_PREFIX_RE.test(segment) ? segment.replace(STORAGE_KEY_PREFIX_RE, '') : segment
}
function getWorkspaceIdForCompile(key: string): string | undefined {
return parseWorkspaceFileKey(key) ?? undefined
}
const IMMUTABLE_CACHE_CONTROL = 'private, max-age=31536000, immutable'
const WORKSPACE_REVALIDATE_CACHE_CONTROL = 'private, no-cache, must-revalidate'
/**
* Cache-Control for a served file. A versioned request (`?v=<updatedAt>`) addresses
* content-immutable bytes — generated docs are content-addressed and the version
* bumps on every edit — so the browser may cache it indefinitely; re-opens and
* focus refetches then resolve from cache with no round trip. Unversioned workspace
* reads stay revalidated because the same storage key is edited in place.
*/
function resolveServeCacheControl(
versioned: boolean,
context: string | undefined
): string | undefined {
if (versioned) return IMMUTABLE_CACHE_CONTROL
return context === 'workspace' ? WORKSPACE_REVALIDATE_CACHE_CONTROL : undefined
}
export const GET = withRouteHandler(
async (request: NextRequest, { params }: { params: Promise<{ path: string[] }> }) => {
try {
const paramsResult = fileServeParamsSchema.safeParse(await params)
if (!paramsResult.success) {
throw new FileNotFoundError('No file path provided')
}
const { path } = paramsResult.data
if (!path || path.length === 0) {
throw new FileNotFoundError('No file path provided')
}
logger.info('File serve request:', { path })
const fullPath = path.join('/')
const isS3Path = path[0] === 's3'
const isBlobPath = path[0] === 'blob'
const isCloudPath = isS3Path || isBlobPath
const cloudKey = isCloudPath ? path.slice(1).join('/') : fullPath
const isPublicByKeyPrefix =
cloudKey.startsWith('profile-pictures/') ||
cloudKey.startsWith('og-images/') ||
cloudKey.startsWith('workspace-logos/')
if (isPublicByKeyPrefix) {
const context = inferContextFromKey(cloudKey)
logger.info(`Serving public ${context}:`, { cloudKey })
if (isUsingCloudStorage() || isCloudPath) {
return await handleCloudProxyPublic(cloudKey, context)
}
return await handleLocalFilePublic(fullPath)
}
const query = fileServeQuerySchema.parse({
raw: request.nextUrl.searchParams.get('raw'),
v: request.nextUrl.searchParams.get('v'),
})
const raw = query.raw === '1'
const versioned = query.v != null
const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
if (!authResult.success || !authResult.userId) {
logger.warn('Unauthorized file access attempt', {
path,
error: authResult.error || 'Missing userId',
})
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const userId = authResult.userId
if (isUsingCloudStorage()) {
return await handleCloudProxy(cloudKey, userId, raw, versioned, request.signal)
}
return await handleLocalFile(cloudKey, userId, raw, versioned, request.signal)
} catch (error) {
// An in-progress/incomplete doc source fails to compile — this is expected
// mid-generation, not a server fault. Return 409 (not 500) so it isn't an
// alarming error; the client re-fetches once the doc finishes (the serve
// URL is busted on the file's updatedAt).
if (error instanceof DocCompileUserError) {
logger.info('Serve: document still compiling, returning 409', {
message: error.message,
})
return NextResponse.json({ error: 'Document is still being generated' }, { status: 409 })
}
logger.error('Error serving file:', error)
if (error instanceof FileNotFoundError) {
return createErrorResponse(error)
}
return createErrorResponse(error instanceof Error ? error : new Error('Failed to serve file'))
}
}
)
async function handleLocalFile(
filename: string,
userId: string,
raw: boolean,
versioned: boolean,
signal: AbortSignal | undefined
): Promise<NextResponse> {
const ownerKey = `user:${userId}`
try {
const contextParam: StorageContext | undefined = inferContextFromKey(filename) as
| StorageContext
| undefined
const hasAccess = await verifyFileAccess(
filename,
userId,
undefined, // customConfig
contextParam, // context
true // isLocal
)
if (!hasAccess) {
logger.warn('Unauthorized local file access attempt', { userId, filename })
throw new FileNotFoundError(`File not found: ${filename}`)
}
const filePath = await findLocalFile(filename)
if (!filePath) {
throw new FileNotFoundError(`File not found: ${filename}`)
}
const rawBuffer = await readFile(filePath)
const segment = filename.split('/').pop() || filename
const displayName = stripStorageKeyPrefix(segment)
const workspaceId = getWorkspaceIdForCompile(filename)
const { buffer: fileBuffer, contentType } = await compileDocumentIfNeeded(
rawBuffer,
displayName,
workspaceId,
raw,
ownerKey,
signal
)
logger.info('Local file served', { userId, filename, size: fileBuffer.length })
return createFileResponse({
buffer: fileBuffer,
contentType,
filename: displayName,
cacheControl: resolveServeCacheControl(versioned, contextParam),
})
} catch (error) {
logger.error('Error reading local file:', error)
throw error
}
}
async function handleCloudProxy(
cloudKey: string,
userId: string,
raw = false,
versioned = false,
signal: AbortSignal | undefined = undefined
): Promise<NextResponse> {
const ownerKey = `user:${userId}`
try {
const context = inferContextFromKey(cloudKey)
logger.info(`Inferred context: ${context} from key pattern: ${cloudKey}`)
const hasAccess = await verifyFileAccess(
cloudKey,
userId,
undefined, // customConfig
context, // context
false // isLocal
)
if (!hasAccess) {
logger.warn('Unauthorized cloud file access attempt', { userId, key: cloudKey, context })
throw new FileNotFoundError(`File not found: ${cloudKey}`)
}
let rawBuffer: Buffer
if (context === 'copilot') {
rawBuffer = await CopilotFiles.downloadCopilotFile(cloudKey)
} else {
rawBuffer = await downloadFile({
key: cloudKey,
context,
})
}
const segment = cloudKey.split('/').pop() || 'download'
const displayName = stripStorageKeyPrefix(segment)
const workspaceId = getWorkspaceIdForCompile(cloudKey)
const { buffer: fileBuffer, contentType } = await compileDocumentIfNeeded(
rawBuffer,
displayName,
workspaceId,
raw,
ownerKey,
signal
)
logger.info('Cloud file served', {
userId,
key: cloudKey,
size: fileBuffer.length,
context,
})
return createFileResponse({
buffer: fileBuffer,
contentType,
filename: displayName,
cacheControl: resolveServeCacheControl(versioned, context),
})
} catch (error) {
logger.error('Error downloading from cloud storage:', error)
throw error
}
}
async function handleCloudProxyPublic(
cloudKey: string,
context: StorageContext
): Promise<NextResponse> {
try {
let fileBuffer: Buffer
if (context === 'copilot') {
fileBuffer = await CopilotFiles.downloadCopilotFile(cloudKey)
} else {
fileBuffer = await downloadFile({
key: cloudKey,
context,
})
}
const filename = cloudKey.split('/').pop() || 'download'
const contentType = getContentType(filename)
logger.info('Public cloud file served', {
key: cloudKey,
size: fileBuffer.length,
context,
})
return createFileResponse({
buffer: fileBuffer,
contentType,
filename,
})
} catch (error) {
logger.error('Error serving public cloud file:', error)
throw error
}
}
async function handleLocalFilePublic(filename: string): Promise<NextResponse> {
try {
const filePath = await findLocalFile(filename)
if (!filePath) {
throw new FileNotFoundError(`File not found: ${filename}`)
}
const fileBuffer = await readFile(filePath)
const contentType = getContentType(filename)
logger.info('Public local file served', { filename, size: fileBuffer.length })
return createFileResponse({
buffer: fileBuffer,
contentType,
filename,
})
} catch (error) {
logger.error('Error reading public local file:', error)
throw error
}
}