From 8c5da027617626f9ca6b241adbe2f46489928f0f Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Tue, 23 Jun 2026 23:06:31 -0700 Subject: [PATCH] feat(file): include public share status in File read output (#5191) * feat(file): include public share status in File read output The read operation now attaches each workspace file's public share status as a "share" field (the share record, or null when not shared), batch-fetched via getSharesForResources to avoid N+1. Picker/upload input files have no canonical id and carry share: null. * refactor(file): read share status uses visibility vocabulary, no row internals Read's per-file "share" is now { visibility, url, allowedEmails } using the same visibility vocabulary as Manage Sharing: 'private' when not shared (url null, no config) instead of null, otherwise public/password/email/sso with the link. Drops row internals (id, token, resourceType, resourceId, isActive, hasPassword). * fix(file): mark picker files private without a share lookup Input (picker/upload) files only have a synthetic id (storage key/URL), so looking them up in the shares map could collide with a canonical file id and attach the wrong share. Give them an explicit private share instead. * docs(file): terser share-status output descriptions * chore(file): drop verbose comment in read share enrichment --- apps/sim/app/api/tools/file/manage/route.ts | 21 ++++++++++++++++++++- apps/sim/blocks/blocks/file.ts | 2 +- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/apps/sim/app/api/tools/file/manage/route.ts b/apps/sim/app/api/tools/file/manage/route.ts index 4fef2956e9..367a5db8cf 100644 --- a/apps/sim/app/api/tools/file/manage/route.ts +++ b/apps/sim/app/api/tools/file/manage/route.ts @@ -17,6 +17,7 @@ import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { isSupportedFileType, parseBuffer } from '@/lib/file-parsers' import { getShareForResource, + getSharesForResources, ShareValidationError, upsertFileShare, } from '@/lib/public-shares/share-manager' @@ -417,12 +418,30 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } + const shares = await getSharesForResources('file', selectedFileIds) + const privateReadShare = () => ({ + visibility: 'private' as const, + url: null, + allowedEmails: [] as string[], + }) + const toReadShare = (fileId: string) => { + const share = shares.get(fileId) + if (!share || !share.isActive) return privateReadShare() + return { + visibility: share.authType, + url: share.url, + allowedEmails: share.allowedEmails, + } + } const userFiles = files .map((file) => workspaceFileToUserFile(file)) .filter((file): file is NonNullable> => Boolean(file) ) - .concat(selectedInputFiles) + .map((file) => ({ ...file, share: toReadShare(file.id) })) + // Picker/upload entries have only a synthetic id (storage key/URL), so they + // never carry a canonical share — mark them private without a lookup. + .concat(selectedInputFiles.map((file) => ({ ...file, share: privateReadShare() }))) logger.info('Files retrieved', { count: userFiles.length, diff --git a/apps/sim/blocks/blocks/file.ts b/apps/sim/blocks/blocks/file.ts index f43294ad6e..49ea1361a8 100644 --- a/apps/sim/blocks/blocks/file.ts +++ b/apps/sim/blocks/blocks/file.ts @@ -1361,7 +1361,7 @@ export const FileV5Block: BlockConfig = { files: { type: 'file[]', description: - 'Workspace file objects (read), fetched file objects (fetch), the compressed archive (compress), or extracted files (decompress)', + 'Workspace file objects with share status (read), fetched file objects (fetch), the compressed archive (compress), or extracted files (decompress)', }, contents: { type: 'array',