diff --git a/packages/storage-vercel-blob/src/adapter.ts b/packages/storage-vercel-blob/src/adapter.ts index a74fcb5965b..393a0c1c40a 100644 --- a/packages/storage-vercel-blob/src/adapter.ts +++ b/packages/storage-vercel-blob/src/adapter.ts @@ -12,6 +12,7 @@ import { uploadFile } from './uploadFile.js' interface CreateVercelBlobAdapterArgs { access: 'public' addRandomSuffix?: boolean + allowOverwrite?: boolean baseUrl: string cacheControlMaxAge: number clientUploads?: ClientUploadsConfig @@ -22,6 +23,7 @@ interface CreateVercelBlobAdapterArgs { export function createVercelBlobAdapter({ access, addRandomSuffix, + allowOverwrite, baseUrl, cacheControlMaxAge, clientUploads, @@ -55,6 +57,7 @@ export function createVercelBlobAdapter({ const result = await uploadFile({ access, addRandomSuffix, + allowOverwrite, buffer, cacheControlMaxAge, collectionPrefix: prefix, diff --git a/packages/storage-vercel-blob/src/client/VercelBlobClientUploadHandler.ts b/packages/storage-vercel-blob/src/client/VercelBlobClientUploadHandler.ts index 0d07f9b8fbd..234a816cca5 100644 --- a/packages/storage-vercel-blob/src/client/VercelBlobClientUploadHandler.ts +++ b/packages/storage-vercel-blob/src/client/VercelBlobClientUploadHandler.ts @@ -6,6 +6,7 @@ import { formatAdminURL } from 'payload/shared' export type VercelBlobClientUploadHandlerExtra = { addRandomSuffix: boolean + allowOverwrite: boolean useCompositePrefixes: boolean } @@ -22,7 +23,7 @@ export const VercelBlobClientUploadHandler = apiRoute, collectionSlug, docPrefix, - extra: { addRandomSuffix, useCompositePrefixes = false }, + extra: { addRandomSuffix, allowOverwrite, useCompositePrefixes = false }, file, prefix, serverHandlerPath, diff --git a/packages/storage-vercel-blob/src/getClientUploadRoute.ts b/packages/storage-vercel-blob/src/getClientUploadRoute.ts index 0dc2d6434f6..259093e9e85 100644 --- a/packages/storage-vercel-blob/src/getClientUploadRoute.ts +++ b/packages/storage-vercel-blob/src/getClientUploadRoute.ts @@ -9,6 +9,7 @@ type Args = { req: PayloadRequest }) => boolean | Promise addRandomSuffix?: boolean + allowOverwrite?: boolean cacheControlMaxAge?: number token: string } @@ -16,7 +17,13 @@ type Args = { const defaultAccess: Args['access'] = ({ req }) => !!req.user export const getClientUploadRoute = - ({ access = defaultAccess, addRandomSuffix, cacheControlMaxAge, token }: Args): PayloadHandler => + ({ + access = defaultAccess, + addRandomSuffix, + allowOverwrite, + cacheControlMaxAge, + token, + }: Args): PayloadHandler => async (req) => { const body = (await req.json!()) as HandleUploadBody @@ -34,6 +41,7 @@ export const getClientUploadRoute = return Promise.resolve({ addRandomSuffix, + allowOverwrite, cacheControlMaxAge, }) }, diff --git a/packages/storage-vercel-blob/src/index.ts b/packages/storage-vercel-blob/src/index.ts index 951f81fe99b..9ae5c8b5063 100644 --- a/packages/storage-vercel-blob/src/index.ts +++ b/packages/storage-vercel-blob/src/index.ts @@ -29,6 +29,16 @@ export type VercelBlobStorageOptions = { */ addRandomSuffix?: boolean + /** + * Allow overwriting an existing blob with the same key. Passed through to + * `@vercel/blob`'s `put()`. Useful when `addRandomSuffix: false` is combined + * with `clientUploads: true`, where a failed upload can leave an orphan blob + * that blocks a retry with `"This blob already exists"`. + * + * @default false + */ + allowOverwrite?: boolean + /** * When enabled, fields (like the prefix field) will always be inserted into * the collection schema regardless of whether the plugin is enabled. This @@ -91,6 +101,7 @@ export type VercelBlobStorageOptions = { const defaultUploadOptions: Partial = { access: 'public', addRandomSuffix: false, + allowOverwrite: false, cacheControlMaxAge: 60 * 60 * 24 * 365, // 1 year enabled: true, } @@ -134,12 +145,14 @@ export const vercelBlobStorage: VercelBlobStoragePlugin = enabled: !isPluginDisabled && Boolean(options.clientUploads), extraClientHandlerProps: () => ({ addRandomSuffix: !!optionsWithDefaults.addRandomSuffix, + allowOverwrite: !!optionsWithDefaults.allowOverwrite, useCompositePrefixes: !!options.useCompositePrefixes, }), serverHandler: getClientUploadRoute({ access: typeof options.clientUploads === 'object' ? options.clientUploads.access : undefined, addRandomSuffix: optionsWithDefaults.addRandomSuffix, + allowOverwrite: optionsWithDefaults.allowOverwrite, cacheControlMaxAge: options.cacheControlMaxAge, token: options.token ?? '', }), @@ -171,6 +184,7 @@ export const vercelBlobStorage: VercelBlobStoragePlugin = const adapter = createVercelBlobAdapter({ access: optionsWithDefaults.access ?? 'public', addRandomSuffix: optionsWithDefaults.addRandomSuffix, + allowOverwrite: optionsWithDefaults.allowOverwrite, baseUrl, cacheControlMaxAge: optionsWithDefaults.cacheControlMaxAge ?? 60 * 60 * 24 * 365, clientUploads: optionsWithDefaults.clientUploads, diff --git a/packages/storage-vercel-blob/src/uploadFile.ts b/packages/storage-vercel-blob/src/uploadFile.ts index d568e4026bd..04362ae6332 100644 --- a/packages/storage-vercel-blob/src/uploadFile.ts +++ b/packages/storage-vercel-blob/src/uploadFile.ts @@ -5,6 +5,7 @@ import path from 'path' interface UploadFileArgs { access: 'public' addRandomSuffix?: boolean + allowOverwrite?: boolean buffer: Buffer cacheControlMaxAge?: number collectionPrefix?: string @@ -22,6 +23,7 @@ interface UploadFileResult { export async function uploadFile({ access, addRandomSuffix, + allowOverwrite, buffer, cacheControlMaxAge, collectionPrefix = '', @@ -41,6 +43,7 @@ export async function uploadFile({ const result = await put(fileKey, buffer, { access, addRandomSuffix, + allowOverwrite, cacheControlMaxAge, contentType: mimeType, token,