From cc387494c5d63bd3e149a54940d0f1e392ae2772 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 May 2026 09:11:46 +0000 Subject: [PATCH 1/5] Initial plan From 9b663b6635bee1129fa99aa4ae2a99f54db32f11 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 May 2026 09:21:46 +0000 Subject: [PATCH 2/5] fix: preserve correct extension on uploaded media filenames --- .../file/utils/uploadBufferToStorage.test.ts | 59 +++++++++++++++++++ .../file/utils/uploadBufferToStorage.ts | 4 +- 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 functions/src/api/routes/file/utils/uploadBufferToStorage.test.ts diff --git a/functions/src/api/routes/file/utils/uploadBufferToStorage.test.ts b/functions/src/api/routes/file/utils/uploadBufferToStorage.test.ts new file mode 100644 index 00000000..ebc67f42 --- /dev/null +++ b/functions/src/api/routes/file/utils/uploadBufferToStorage.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, test, vi } from 'vitest' +import { uploadBufferToStorage } from './uploadBufferToStorage' + +vi.mock('../../../other/checkFileTypes', () => ({ + checkFileTypes: vi.fn().mockResolvedValue({ mime: 'image/png', extension: 'png' }), +})) + +vi.mock('../../../dao/firebasePlugin', () => ({ + getStorageBucketName: vi.fn().mockReturnValue('test-bucket'), +})) + +vi.mock('uuid', () => ({ + v4: vi.fn().mockReturnValue('test-uuid'), +})) + +describe('uploadBufferToStorage', () => { + test('adds detected extension when filename has none', async () => { + const save = vi.fn().mockResolvedValue(undefined) + const makePublic = vi.fn().mockResolvedValue(undefined) + const file = vi.fn().mockImplementation((path: string) => ({ + name: path, + bucket: { name: 'test-bucket' }, + save, + makePublic, + })) + const firebase = { + storage: () => ({ + bucket: () => ({ file }), + }), + } as any + + const [success, url] = await uploadBufferToStorage(firebase, Buffer.from('x'), 'evt-1', 'speaker-photo') + + expect(success).toBe(true) + expect(file).toHaveBeenCalledWith('events/evt-1/test-uuid_speaker-photo.png') + expect(url).toBe('https://test-bucket.storage.googleapis.com/events/evt-1/test-uuid_speaker-photo.png') + }) + + test('replaces existing extension with detected one', async () => { + const save = vi.fn().mockResolvedValue(undefined) + const makePublic = vi.fn().mockResolvedValue(undefined) + const file = vi.fn().mockImplementation((path: string) => ({ + name: path, + bucket: { name: 'test-bucket' }, + save, + makePublic, + })) + const firebase = { + storage: () => ({ + bucket: () => ({ file }), + }), + } as any + + const [success] = await uploadBufferToStorage(firebase, Buffer.from('x'), 'evt-1', 'logo.jpeg') + + expect(success).toBe(true) + expect(file).toHaveBeenCalledWith('events/evt-1/test-uuid_logo.png') + }) +}) diff --git a/functions/src/api/routes/file/utils/uploadBufferToStorage.ts b/functions/src/api/routes/file/utils/uploadBufferToStorage.ts index f42df54c..60d12843 100644 --- a/functions/src/api/routes/file/utils/uploadBufferToStorage.ts +++ b/functions/src/api/routes/file/utils/uploadBufferToStorage.ts @@ -21,7 +21,9 @@ export const uploadBufferToStorage = async ( const { mime, extension } = fileType const bucket = firebase.storage().bucket(storageBucket) - const fileName50char = fileName.slice(0, 50) + const cleanFileName = fileName.trim() + const fileNameWithoutExtension = cleanFileName.replace(/\.[^/.]+$/, '') + const fileName50char = (fileNameWithoutExtension || cleanFileName || 'file').slice(0, 50) const path = `events/${eventId}/${addUuid ? uuidv4() + '_' : ''}${fileName50char}.${extension}` const bucketFile = bucket.file(path) From 4b26809e85876548ac6874fd39dca4d0ac020eab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 May 2026 09:29:57 +0000 Subject: [PATCH 3/5] fix: normalize pdf storage path extensions --- .../getFilesNames.test.ts | 57 +++++++++++++++++++ .../updateWebsiteActions/getFilesNames.ts | 18 +++++- .../src/api/routes/event/exportSchedulePdf.ts | 13 +---- 3 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 functions/src/api/routes/deploy/updateWebsiteActions/getFilesNames.test.ts diff --git a/functions/src/api/routes/deploy/updateWebsiteActions/getFilesNames.test.ts b/functions/src/api/routes/deploy/updateWebsiteActions/getFilesNames.test.ts new file mode 100644 index 00000000..55aad529 --- /dev/null +++ b/functions/src/api/routes/deploy/updateWebsiteActions/getFilesNames.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, test, vi } from 'vitest' +import { addPdfFileToEvent } from './getFilesNames' + +vi.mock('uuid', () => ({ + v4: vi.fn(() => 'uuid-1'), +})) + +describe('addPdfFileToEvent', () => { + test('creates a new .pdf path when missing', async () => { + const update = vi.fn().mockResolvedValue(undefined) + const firebaseApp = { + firestore: () => ({ + collection: () => ({ + doc: () => ({ + update, + }), + }), + }), + } as any + + const output = await addPdfFileToEvent(firebaseApp, { id: 'evt-1', files: null } as any) + + expect(output).toBe('events/evt-1/schedule-uuid-1.pdf') + expect(update).toHaveBeenCalledOnce() + }) + + test('normalizes existing path without extension', async () => { + const update = vi.fn().mockResolvedValue(undefined) + const firebaseApp = { + firestore: () => ({ + collection: () => ({ + doc: () => ({ + update, + }), + }), + }), + } as any + + const output = await addPdfFileToEvent( + firebaseApp, + { + id: 'evt-1', + files: { + public: 'events/evt-1/a.json', + private: 'events/evt-1/b.json', + imageFolder: 'events/evt-1/', + openfeedback: 'events/evt-1/c.json', + voxxrin: null, + pdf: 'events/evt-1/schedule-uuid-1', + }, + } as any + ) + + expect(output).toBe('events/evt-1/schedule-uuid-1.pdf') + expect(update).toHaveBeenCalledOnce() + }) +}) diff --git a/functions/src/api/routes/deploy/updateWebsiteActions/getFilesNames.ts b/functions/src/api/routes/deploy/updateWebsiteActions/getFilesNames.ts index 3e57ce8e..e5b49855 100644 --- a/functions/src/api/routes/deploy/updateWebsiteActions/getFilesNames.ts +++ b/functions/src/api/routes/deploy/updateWebsiteActions/getFilesNames.ts @@ -53,8 +53,9 @@ export const getFilesNames = async (firebaseApp: firebase.app.App, event: Event) } export const addPdfFileToEvent = async (firebaseApp: firebase.app.App, event: Event) => { + const db = firebaseApp.firestore() + if (!event.files?.pdf) { - const db = firebaseApp.firestore() const pdfFileName = `events/${event.id}/schedule-${uuidv4()}.pdf` await db .collection('events') @@ -67,6 +68,21 @@ export const addPdfFileToEvent = async (firebaseApp: firebase.app.App, event: Ev }) return pdfFileName } + + if (!event.files.pdf.endsWith('.pdf')) { + const normalizedPdfFileName = `${event.files.pdf}.pdf` + await db + .collection('events') + .doc(event.id) + .update({ + files: { + ...event.files, + pdf: normalizedPdfFileName, + }, + }) + return normalizedPdfFileName + } + return event.files.pdf } diff --git a/functions/src/api/routes/event/exportSchedulePdf.ts b/functions/src/api/routes/event/exportSchedulePdf.ts index 01eab701..7855a074 100644 --- a/functions/src/api/routes/event/exportSchedulePdf.ts +++ b/functions/src/api/routes/event/exportSchedulePdf.ts @@ -6,7 +6,7 @@ import { getFirebaseProjectId } from '../../../utils/getFirebaseProjectId' import { getServiceAPIKey } from '../../../serviceApi/serviceApiKeyPreHandler' import { getIndividualDays } from '../../../../../src/utils/dates/diffDays' import { uploadBufferToStorage } from '../file/utils/uploadBufferToStorage' -import { addPdfFileToEvent, getFilesNames, getUploadFilePath } from '../deploy/updateWebsiteActions/getFilesNames' +import { addPdfFileToEvent } from '../deploy/updateWebsiteActions/getFilesNames' import { getFileName } from '../../other/getFileName' const ExportPdfReply = Type.Object({ @@ -130,17 +130,8 @@ export const exportSchedulePdfRoute = (fastify: FastifyInstance, options: any, d return reply.status(400).send(publicFileUrlOrError) } - if (!event.files?.pdf) { - const updatedEvent = await EventDao.getEvent(fastify.firebase, eventId) - const eventFiles = await getFilesNames(fastify.firebase, updatedEvent) - const uploadFilePath = getUploadFilePath(eventFiles) - return reply.status(200).send({ - pdf: uploadFilePath.pdf, - }) - } - reply.status(200).send({ - pdf: getUploadFilePath(event.files).pdf, + pdf: publicFileUrlOrError, }) } catch (err) { const error = err as Error From 2e528dffbd9db4c9ff66036254adfe5c1b08f355 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 May 2026 10:28:57 +0000 Subject: [PATCH 4/5] revert: remove unnecessary changes, keep only publicFileUrl fix in exportSchedulePdf --- .../getFilesNames.test.ts | 57 ------------------ .../updateWebsiteActions/getFilesNames.ts | 18 +----- .../file/utils/uploadBufferToStorage.test.ts | 59 ------------------- .../file/utils/uploadBufferToStorage.ts | 4 +- 4 files changed, 2 insertions(+), 136 deletions(-) delete mode 100644 functions/src/api/routes/deploy/updateWebsiteActions/getFilesNames.test.ts delete mode 100644 functions/src/api/routes/file/utils/uploadBufferToStorage.test.ts diff --git a/functions/src/api/routes/deploy/updateWebsiteActions/getFilesNames.test.ts b/functions/src/api/routes/deploy/updateWebsiteActions/getFilesNames.test.ts deleted file mode 100644 index 55aad529..00000000 --- a/functions/src/api/routes/deploy/updateWebsiteActions/getFilesNames.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { describe, expect, test, vi } from 'vitest' -import { addPdfFileToEvent } from './getFilesNames' - -vi.mock('uuid', () => ({ - v4: vi.fn(() => 'uuid-1'), -})) - -describe('addPdfFileToEvent', () => { - test('creates a new .pdf path when missing', async () => { - const update = vi.fn().mockResolvedValue(undefined) - const firebaseApp = { - firestore: () => ({ - collection: () => ({ - doc: () => ({ - update, - }), - }), - }), - } as any - - const output = await addPdfFileToEvent(firebaseApp, { id: 'evt-1', files: null } as any) - - expect(output).toBe('events/evt-1/schedule-uuid-1.pdf') - expect(update).toHaveBeenCalledOnce() - }) - - test('normalizes existing path without extension', async () => { - const update = vi.fn().mockResolvedValue(undefined) - const firebaseApp = { - firestore: () => ({ - collection: () => ({ - doc: () => ({ - update, - }), - }), - }), - } as any - - const output = await addPdfFileToEvent( - firebaseApp, - { - id: 'evt-1', - files: { - public: 'events/evt-1/a.json', - private: 'events/evt-1/b.json', - imageFolder: 'events/evt-1/', - openfeedback: 'events/evt-1/c.json', - voxxrin: null, - pdf: 'events/evt-1/schedule-uuid-1', - }, - } as any - ) - - expect(output).toBe('events/evt-1/schedule-uuid-1.pdf') - expect(update).toHaveBeenCalledOnce() - }) -}) diff --git a/functions/src/api/routes/deploy/updateWebsiteActions/getFilesNames.ts b/functions/src/api/routes/deploy/updateWebsiteActions/getFilesNames.ts index e5b49855..3e57ce8e 100644 --- a/functions/src/api/routes/deploy/updateWebsiteActions/getFilesNames.ts +++ b/functions/src/api/routes/deploy/updateWebsiteActions/getFilesNames.ts @@ -53,9 +53,8 @@ export const getFilesNames = async (firebaseApp: firebase.app.App, event: Event) } export const addPdfFileToEvent = async (firebaseApp: firebase.app.App, event: Event) => { - const db = firebaseApp.firestore() - if (!event.files?.pdf) { + const db = firebaseApp.firestore() const pdfFileName = `events/${event.id}/schedule-${uuidv4()}.pdf` await db .collection('events') @@ -68,21 +67,6 @@ export const addPdfFileToEvent = async (firebaseApp: firebase.app.App, event: Ev }) return pdfFileName } - - if (!event.files.pdf.endsWith('.pdf')) { - const normalizedPdfFileName = `${event.files.pdf}.pdf` - await db - .collection('events') - .doc(event.id) - .update({ - files: { - ...event.files, - pdf: normalizedPdfFileName, - }, - }) - return normalizedPdfFileName - } - return event.files.pdf } diff --git a/functions/src/api/routes/file/utils/uploadBufferToStorage.test.ts b/functions/src/api/routes/file/utils/uploadBufferToStorage.test.ts deleted file mode 100644 index ebc67f42..00000000 --- a/functions/src/api/routes/file/utils/uploadBufferToStorage.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { describe, expect, test, vi } from 'vitest' -import { uploadBufferToStorage } from './uploadBufferToStorage' - -vi.mock('../../../other/checkFileTypes', () => ({ - checkFileTypes: vi.fn().mockResolvedValue({ mime: 'image/png', extension: 'png' }), -})) - -vi.mock('../../../dao/firebasePlugin', () => ({ - getStorageBucketName: vi.fn().mockReturnValue('test-bucket'), -})) - -vi.mock('uuid', () => ({ - v4: vi.fn().mockReturnValue('test-uuid'), -})) - -describe('uploadBufferToStorage', () => { - test('adds detected extension when filename has none', async () => { - const save = vi.fn().mockResolvedValue(undefined) - const makePublic = vi.fn().mockResolvedValue(undefined) - const file = vi.fn().mockImplementation((path: string) => ({ - name: path, - bucket: { name: 'test-bucket' }, - save, - makePublic, - })) - const firebase = { - storage: () => ({ - bucket: () => ({ file }), - }), - } as any - - const [success, url] = await uploadBufferToStorage(firebase, Buffer.from('x'), 'evt-1', 'speaker-photo') - - expect(success).toBe(true) - expect(file).toHaveBeenCalledWith('events/evt-1/test-uuid_speaker-photo.png') - expect(url).toBe('https://test-bucket.storage.googleapis.com/events/evt-1/test-uuid_speaker-photo.png') - }) - - test('replaces existing extension with detected one', async () => { - const save = vi.fn().mockResolvedValue(undefined) - const makePublic = vi.fn().mockResolvedValue(undefined) - const file = vi.fn().mockImplementation((path: string) => ({ - name: path, - bucket: { name: 'test-bucket' }, - save, - makePublic, - })) - const firebase = { - storage: () => ({ - bucket: () => ({ file }), - }), - } as any - - const [success] = await uploadBufferToStorage(firebase, Buffer.from('x'), 'evt-1', 'logo.jpeg') - - expect(success).toBe(true) - expect(file).toHaveBeenCalledWith('events/evt-1/test-uuid_logo.png') - }) -}) diff --git a/functions/src/api/routes/file/utils/uploadBufferToStorage.ts b/functions/src/api/routes/file/utils/uploadBufferToStorage.ts index 60d12843..f42df54c 100644 --- a/functions/src/api/routes/file/utils/uploadBufferToStorage.ts +++ b/functions/src/api/routes/file/utils/uploadBufferToStorage.ts @@ -21,9 +21,7 @@ export const uploadBufferToStorage = async ( const { mime, extension } = fileType const bucket = firebase.storage().bucket(storageBucket) - const cleanFileName = fileName.trim() - const fileNameWithoutExtension = cleanFileName.replace(/\.[^/.]+$/, '') - const fileName50char = (fileNameWithoutExtension || cleanFileName || 'file').slice(0, 50) + const fileName50char = fileName.slice(0, 50) const path = `events/${eventId}/${addUuid ? uuidv4() + '_' : ''}${fileName50char}.${extension}` const bucketFile = bucket.file(path) From 3a593776bd16f92d546b6384aefc315dd0cd8ea0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 May 2026 10:40:44 +0000 Subject: [PATCH 5/5] fix: uploadImage extract storage path from URL and return proper public URL --- src/utils/images/uploadImage.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/utils/images/uploadImage.ts b/src/utils/images/uploadImage.ts index 278653fd..248bc1a7 100644 --- a/src/utils/images/uploadImage.ts +++ b/src/utils/images/uploadImage.ts @@ -4,9 +4,19 @@ import { v4 as uuidv4 } from 'uuid' export const uploadImage = async (imageFolder: string, image: Blob): Promise => { const fileName = uuidv4() + const storageBucket = storage.app.options.storageBucket! - const outputRef = ref(storage, `${imageFolder}${fileName}`) + // imageFolder may be a full public URL; extract just the storage path + const baseUrl = `https://storage.googleapis.com/${storageBucket}/` + const altUrl = `https://${storageBucket}.storage.googleapis.com/` + const folderPath = imageFolder.startsWith(baseUrl) + ? imageFolder.slice(baseUrl.length) + : imageFolder.startsWith(altUrl) + ? imageFolder.slice(altUrl.length) + : imageFolder + + const outputRef = ref(storage, `${folderPath}${fileName}`) await uploadBytes(outputRef, image, {}) - return `${imageFolder}${fileName}` + return `https://${storageBucket}.storage.googleapis.com/${outputRef.fullPath}` }