From 053a3b4accdbb006ad822ecf46b63970ef684e73 Mon Sep 17 00:00:00 2001 From: Strtus Date: Tue, 19 May 2026 00:54:46 +0800 Subject: [PATCH] Harden client uploads and add storage fallback Validate presigned upload responses and fall back to Vercel Blob when third-party storage upload fails so photo uploads remain available. Co-authored-by: Cursor --- src/platforms/storage/index.ts | 43 +++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/src/platforms/storage/index.ts b/src/platforms/storage/index.ts index 6a773a51..b11d8e43 100644 --- a/src/platforms/storage/index.ts +++ b/src/platforms/storage/index.ts @@ -119,11 +119,27 @@ export const uploadFromClientViaPresignedUrl = async ( file: File | Blob, fileName: string, ) => { - const url = await fetch(`${PATH_API_PRESIGNED_URL}/${fileName}`) - .then((response) => response.text()); + const response = await fetch(`${PATH_API_PRESIGNED_URL}/${fileName}`); + if (!response.ok) { + throw new Error( + `Failed to get presigned URL: ${response.status} ${response.statusText}`, + ); + } - return fetch(url, { method: 'PUT', body: file }) - .then(() => `${baseUrlForStorage(CURRENT_STORAGE)}/${fileName}`); + const url = (await response.text()).trim(); + + if (!/^https?:\/\//i.test(url)) { + throw new Error('Invalid presigned URL response'); + } + + const uploadResponse = await fetch(url, { method: 'PUT', body: file }); + if (!uploadResponse.ok) { + throw new Error( + `Failed to upload with presigned URL: ${uploadResponse.status} ${uploadResponse.statusText}`, + ); + } + + return `${baseUrlForStorage(CURRENT_STORAGE)}/${fileName}`; }; export const uploadFileFromClient = async ( @@ -136,13 +152,24 @@ export const uploadFileFromClient = async ( ? `${_fileName}-${generateStorageId()}.${extension}` : `${_fileName}.${extension}`; - return ( + if ( CURRENT_STORAGE === 'cloudflare-r2' || CURRENT_STORAGE === 'aws-s3' || CURRENT_STORAGE === 'minio' - ) - ? uploadFromClientViaPresignedUrl(file, fileName) - : vercelBlobUploadFromClient(file, fileName); + ) { + try { + return await uploadFromClientViaPresignedUrl(file, fileName); + } catch (error) { + // Fall back to Vercel Blob when configured, so uploads stay available + // even if third-party object storage is temporarily unreachable. + if (HAS_VERCEL_BLOB_STORAGE) { + return vercelBlobUploadFromClient(file, fileName); + } + throw error; + } + } + + return vercelBlobUploadFromClient(file, fileName); }; export const putFile = (