From 0d7441e7a32eb759f077fa4cd062e9aa306c45ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20K=C3=BCttner?= <4376726+hanneskuettner@users.noreply.github.com> Date: Mon, 29 Apr 2024 01:44:59 +0200 Subject: [PATCH] Fix failing file upload to Supabase (#22293) Co-authored-by: Pascal Jufer --- .changeset/green-sloths-sparkle.md | 5 + .../storage-driver-supabase/src/index.test.ts | 111 +++++++++--------- packages/storage-driver-supabase/src/index.ts | 22 ++-- 3 files changed, 67 insertions(+), 71 deletions(-) create mode 100644 .changeset/green-sloths-sparkle.md diff --git a/.changeset/green-sloths-sparkle.md b/.changeset/green-sloths-sparkle.md new file mode 100644 index 0000000000..a39eaae31f --- /dev/null +++ b/.changeset/green-sloths-sparkle.md @@ -0,0 +1,5 @@ +--- +'@directus/storage-driver-supabase': patch +--- + +Fixed file upload error for Supabase Storage servers hosted behind a Cloudflare Cache diff --git a/packages/storage-driver-supabase/src/index.test.ts b/packages/storage-driver-supabase/src/index.test.ts index bc04cc4e0a..222f7203e4 100644 --- a/packages/storage-driver-supabase/src/index.test.ts +++ b/packages/storage-driver-supabase/src/index.test.ts @@ -333,70 +333,69 @@ describe('#read', () => { }); }); -describe('#head', () => { - test('Returns object headers', async () => { - vi.mocked(fetch).mockReturnValue({ - status: 200, - headers: { 'content-length': sample.file.size, 'last-modified': sample.file.modified }, - } as unknown as Promise); - - const result = await driver.head(sample.path.input); - - expect(result).toStrictEqual({ 'content-length': sample.file.size, 'last-modified': sample.file.modified }); - }); - - test('Ensures input is passed to getAuthenticatedUrl', async () => { - vi.mocked(fetch).mockReturnValue({ - status: 200, - headers: { 'content-length': sample.file.size, 'last-modified': sample.file.modified }, - } as unknown as Promise); - - driver['getAuthenticatedUrl'] = vi.fn(); - - await driver.head(sample.path.input); - - expect(driver['getAuthenticatedUrl']).toHaveBeenCalledWith(sample.path.input); - }); - - test('Throws an error when a status >= 400 is sent', async () => { - vi.mocked(fetch).mockReturnValue({ - status: 400, - headers: { 'content-length': sample.file.size, 'last-modified': sample.file.modified }, - } as unknown as Promise); - - expect(driver.head(sample.path.input)).rejects.toThrowError(new Error(`File not found`)); - }); -}); - describe('#stat', () => { - test('Returns size/modified from returned send data', async () => { - vi.mocked(fetch).mockReturnValue({ - status: 200, - headers: { - get(key: any) { - if (key === 'content-length') { - return sample.file.size; - } else if (key === 'last-modified') { - return sample.file.modified; - } + test('Returns the size/modified from metadata', async () => { + driver['bucket'] = { + list: vi.fn().mockReturnValue({ + data: [{ metadata: { contentLength: sample.file.size, lastModified: sample.file.modified } }], + error: null, + }), + } as any; - return null; - }, - }, - } as unknown as Promise); + const stat = await driver.stat(sample.path.input); - const result = await driver.stat(sample.path.input); - - expect(result).toStrictEqual({ + expect(stat).toEqual({ size: sample.file.size, modified: sample.file.modified, }); + + expect(driver['bucket'].list).toHaveBeenCalledWith('', { + limit: 1, + search: sample.path.input, + }); }); - test('Throws an error when a status >= 400 is sent', async () => { - vi.mocked(fetch).mockReturnValue({ - status: 400, - } as unknown as Promise); + test('Uses the configured root directory', async () => { + driver['config'].root = 'root'; + + driver['bucket'] = { + list: vi.fn().mockReturnValue({ + data: [{ metadata: { contentLength: sample.file.size, lastModified: sample.file.modified } }], + error: null, + }), + } as any; + + const stat = await driver.stat(sample.path.input); + + expect(stat).toEqual({ + size: sample.file.size, + modified: sample.file.modified, + }); + + expect(driver['bucket'].list).toHaveBeenCalledWith('root', { + limit: 1, + search: sample.path.input, + }); + }); + + test('Throws an error no file is returned by list', async () => { + driver['bucket'] = { + list: vi.fn().mockReturnValue({ + data: [], + error: null, + }), + } as any; + + expect(driver.stat(sample.path.input)).rejects.toThrowError(new Error(`File not found`)); + }); + + test('Throws an error if storage error is returned', async () => { + driver['bucket'] = { + list: vi.fn().mockReturnValue({ + data: null, + error: true, + }), + } as any; expect(driver.stat(sample.path.input)).rejects.toThrowError(new Error(`File not found`)); }); diff --git a/packages/storage-driver-supabase/src/index.ts b/packages/storage-driver-supabase/src/index.ts index 1c17a54f18..cb4e394e85 100644 --- a/packages/storage-driver-supabase/src/index.ts +++ b/packages/storage-driver-supabase/src/index.ts @@ -88,27 +88,19 @@ export class DriverSupabase implements Driver { return Readable.fromWeb(response.body); } - async head(filepath: string) { - const response = await fetch(this.getAuthenticatedUrl(filepath), { - method: 'HEAD', - headers: { - Authorization: `Bearer ${this.config.serviceRole}`, - }, + async stat(filepath: string) { + const { data, error } = await this.bucket.list(this.config.root, { + search: filepath, + limit: 1, }); - if (response.status >= 400) { + if (error || data.length === 0) { throw new Error('File not found'); } - return response.headers; - } - - async stat(filepath: string) { - const headers = await this.head(filepath); - return { - size: parseInt(headers.get('content-length') || ''), - modified: new Date(headers.get('last-modified') || ''), + size: data[0]?.metadata['contentLength'] ?? 0, + modified: new Date(data[0]?.metadata['lastModified'] || null), }; }