From 54c6585eb8aef15db4b1cb9d454872b373c6d9ec Mon Sep 17 00:00:00 2001 From: pedrofrxncx Date: Fri, 14 Aug 2026 09:54:53 -0300 Subject: [PATCH] fix(file-picker): apply imageOnly filtering server-side on the default list path listObjects already filters imageOnly server-side in the search scan path (matchScanPage), but the default (no-search) path -- the month-shard probes, the broad fallback, and the cursor page via finalize() -- accepted the imageOnly param and silently ignored it, returning every key regardless of type. The client (file-picker-dialog.tsx) re-filters with its own isImageKey before rendering, so this wasn't a correctness bug, but it meant an image-only picker's page of target keys could be mostly non-images that get thrown away client-side, undercounting real images per page and forcing extra "Load more" round trips for buckets with few images. Filter server-side in all three non-search code paths, same as the existing search path, so an image-only page actually returns up to target images. --- apps/api/src/file-storage/file-config-s3.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/api/src/file-storage/file-config-s3.ts b/apps/api/src/file-storage/file-config-s3.ts index d4ee94d22b..41f80bc51f 100644 --- a/apps/api/src/file-storage/file-config-s3.ts +++ b/apps/api/src/file-storage/file-config-s3.ts @@ -294,6 +294,7 @@ export async function listObjects(params: { target, false, monthShardPrefixesList, + params.imageOnly ?? false, ); } @@ -309,10 +310,12 @@ export async function listObjects(params: { ), ), ); + const imageOnly = params.imageOnly ?? false; const seen = new Map(); for (const res of monthResponses) { for (const obj of res.Contents ?? []) { if (!obj.Key || obj.Key.endsWith("/") || seen.has(obj.Key)) continue; + if (imageOnly && !isImageKey(obj.Key)) continue; seen.set(obj.Key, toListedObject(obj, params.ctx)); } } @@ -331,6 +334,7 @@ export async function listObjects(params: { if (!obj.Key || obj.Key.endsWith("/") || seen.has(obj.Key)) continue; // Month-shard keys were already pulled via dedicated probes; skip to avoid duping. if (monthShardPrefixesList.some((p) => obj.Key!.startsWith(p))) continue; + if (imageOnly && !isImageKey(obj.Key)) continue; seen.set(obj.Key, toListedObject(obj, params.ctx)); } const nextCursor = broadRes.IsTruncated @@ -507,13 +511,15 @@ function finalize( target: number, sort: boolean, skipPrefixes: readonly string[] = [], + imageOnly = false, ): ListObjectsResult { const items = (response.Contents ?? []) .filter( (obj) => obj.Key && !obj.Key.endsWith("/") && - !skipPrefixes.some((p) => obj.Key!.startsWith(p)), + !skipPrefixes.some((p) => obj.Key!.startsWith(p)) && + (!imageOnly || isImageKey(obj.Key)), ) .slice(0, target) .map((obj) => toListedObject(obj, ctx));