From be85fec1f3c719dbca2bc4a6b5ede7a91a07021d Mon Sep 17 00:00:00 2001 From: mkue Date: Sun, 29 Dec 2024 21:23:32 +0100 Subject: [PATCH] Functions: Update firebase-functions dependency --- functions/package.json | 2 +- .../FirestoreAuditor.test.ts | 30 +++---- .../src/firestore/firestore-auditor/index.ts | 27 +++--- functions/src/firestore/index.ts | 4 +- .../webhooks/admin/payment-process/index.ts | 12 +-- .../tasks/PaymentCSVTask.test.ts | 9 +- .../tasks/RegistrationCSVTask.test.ts | 9 +- .../tasks/UpdateDatabaseEntriesTask.test.ts | 20 ++--- .../twilio/TwilioOutgoingMessageHandler.ts | 4 +- package-lock.json | 88 ++++++------------- package.json | 4 - 11 files changed, 83 insertions(+), 126 deletions(-) diff --git a/functions/package.json b/functions/package.json index 95e5fcf7e..e756985e6 100644 --- a/functions/package.json +++ b/functions/package.json @@ -36,7 +36,7 @@ "axios": "^1.7.7", "dotenv": "^16.4.5", "firebase-admin": "^12.7.0", - "firebase-functions": "5.1.1", + "firebase-functions": "^6.2.0", "handlebars": "^4.7.8", "handlebars-i18next": "^1.0.3", "i18next-resources-to-backend": "^1.2.1", diff --git a/functions/src/firestore/firestore-auditor/FirestoreAuditor.test.ts b/functions/src/firestore/firestore-auditor/FirestoreAuditor.test.ts index d8aded2f2..938ee4876 100644 --- a/functions/src/firestore/firestore-auditor/FirestoreAuditor.test.ts +++ b/functions/src/firestore/firestore-auditor/FirestoreAuditor.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, test } from '@jest/globals'; import functionsTest from 'firebase-functions-test'; -import auditCollectionTriggerFunction from '.'; +import auditFirestore from '.'; import { FirestoreAdmin } from '../../../../shared/src/firebase/admin/FirestoreAdmin'; import { getOrInitializeFirebaseAdmin } from '../../../../shared/src/firebase/admin/app'; @@ -35,9 +35,9 @@ describe('FirestoreAuditor', () => { await testDoc.set({ foo: 'bar' }); const snap = testEnv.firestore.makeDocumentSnapshot({ foo: 'bar' }, testDoc.path); const change = testEnv.makeChange(null, snap); - const wrapped = testEnv.wrap(auditCollectionTriggerFunction); + const wrapped = testEnv.wrap(auditFirestore); // call the trigger with the simulated change - await wrapped(change); + await wrapped({ data: change }); // retrieve the doc. Test that last_updated_at is set const updatedDoc = await testDoc.get(); expect(updatedDoc.data()!.foo).toEqual('bar'); @@ -53,9 +53,9 @@ describe('FirestoreAuditor', () => { await testSubColDoc.set({ foo: 'bar' }); const snap = testEnv.firestore.makeDocumentSnapshot({ foo: 'bar' }, testSubColDoc.path); const change = testEnv.makeChange(null, snap); - const wrapped = testEnv.wrap(auditCollectionTriggerFunction); + const wrapped = testEnv.wrap(auditFirestore); // call the trigger with the simulated change - await wrapped(change); + await wrapped({ data: change }); // retrieve the doc. Test that last_updated_at is set const updatedDoc = await testSubColDoc.get(); expect(updatedDoc.data()!.foo).toEqual('bar'); @@ -76,9 +76,9 @@ describe('FirestoreAuditor', () => { const snapBefore = testEnv.firestore.makeDocumentSnapshot({ foo: 'before', last_updated_at: 123 }, testDoc.path); const snapAfter = testEnv.firestore.makeDocumentSnapshot({ foo: 'after' }, testDoc.path); const change = testEnv.makeChange(snapBefore, snapAfter); - const wrapped = testEnv.wrap(auditCollectionTriggerFunction); + const wrapped = testEnv.wrap(auditFirestore); // call the trigger with the simulated change - await wrapped(change); + await wrapped({ data: change }); // retrieve the doc. Test that last_updated_at is updated const updatedDoc = await testDoc.get(); expect(updatedDoc.data()!.foo).toEqual('after'); @@ -101,9 +101,9 @@ describe('FirestoreAuditor', () => { ); const snapAfter = testEnv.firestore.makeDocumentSnapshot({ foo: 'after' }, testSubColDoc.path); const change = testEnv.makeChange(snapBefore, snapAfter); - const wrapped = testEnv.wrap(auditCollectionTriggerFunction); + const wrapped = testEnv.wrap(auditFirestore); // call the trigger with the simulated change - await wrapped(change); + await wrapped({ data: change }); // retrieve the doc. Test that last_updated_at is updated const updatedDoc = await testSubColDoc.get(); expect(updatedDoc.data()!.foo).toEqual('after'); @@ -121,9 +121,9 @@ describe('FirestoreAuditor', () => { // setup simulated deletion const snapBefore = testEnv.firestore.makeDocumentSnapshot({ foo: 'before', last_updated_at: 123 }, testDoc.path); const change = testEnv.makeChange(snapBefore, null); - const wrapped = testEnv.wrap(auditCollectionTriggerFunction); + const wrapped = testEnv.wrap(auditFirestore); // call the trigger with the simulated change - await wrapped(change); + await wrapped({ data: change }); // test that the history is populated const history = await testColHistoryEntry.get(); @@ -140,9 +140,9 @@ describe('FirestoreAuditor', () => { testSubColDoc.path, ); const change = testEnv.makeChange(snapBefore, null); - const wrapped = testEnv.wrap(auditCollectionTriggerFunction); + const wrapped = testEnv.wrap(auditFirestore); // call the trigger with the simulated change - await wrapped(change); + await wrapped({ data: change }); // test that the history is populated const history = await testSubColHistoryEntry.get(); @@ -159,9 +159,9 @@ describe('FirestoreAuditor', () => { await testHistoryEntry.set({ foo: 'bar' }); const snap = testEnv.firestore.makeDocumentSnapshot({ foo: 'bar' }, testHistoryEntry.path); const change = testEnv.makeChange(null, snap); - const wrapped = testEnv.wrap(auditCollectionTriggerFunction); + const wrapped = testEnv.wrap(auditFirestore); // call the trigger with the simulated change - await wrapped(change); + await wrapped({ data: change }); // retrieve the doc. Test that last_updated_at is not set const updatedDoc = await testHistoryEntry.get(); expect(updatedDoc.data()!.foo).toEqual('bar'); diff --git a/functions/src/firestore/firestore-auditor/index.ts b/functions/src/firestore/firestore-auditor/index.ts index 24df99df7..f806dd7d5 100644 --- a/functions/src/firestore/firestore-auditor/index.ts +++ b/functions/src/firestore/firestore-auditor/index.ts @@ -1,18 +1,15 @@ -import { DocumentSnapshot } from '@google-cloud/firestore'; -import * as functions from 'firebase-functions'; -import { Change, EventContext } from 'firebase-functions'; +import { logger } from 'firebase-functions'; +import { onDocumentWritten } from 'firebase-functions/v2/firestore'; import { DateTime } from 'luxon'; -import { DEFAULT_REGION } from '../../../../shared/src/firebase'; import { FirestoreAuditor } from './FirestoreAuditor'; -/** - * Triggers changes to documents of root collections - */ -export default functions - // Using v1 functions because tests are not supported with v2 yet: https://github.com/firebase/firebase-functions-test/issues/163 - .region(DEFAULT_REGION) - .firestore.document('{collectionId}/{document=**}') - .onWrite(async (change: Change, context: EventContext) => { - const firestoreAuditor = new FirestoreAuditor(); - return firestoreAuditor.auditFirestore(change, DateTime.fromISO(context.timestamp)); - }); +const auditFirestore = onDocumentWritten({ document: '{collectionId}/{document=**}' }, async (event) => { + const firestoreAuditor = new FirestoreAuditor(); + if (!event.data) { + logger.warn('No data in event'); + return; + } + await firestoreAuditor.auditFirestore(event.data, DateTime.fromISO(event.time)); +}); + +export default auditFirestore; diff --git a/functions/src/firestore/index.ts b/functions/src/firestore/index.ts index 14095ce11..6e646024e 100644 --- a/functions/src/firestore/index.ts +++ b/functions/src/firestore/index.ts @@ -1,3 +1,3 @@ -import auditCollectionTriggerFunction from './firestore-auditor'; +import auditFirestore from './firestore-auditor'; -export const auditCollectionTrigger = auditCollectionTriggerFunction; +export const auditCollectionTrigger = auditFirestore; diff --git a/functions/src/webhooks/admin/payment-process/index.ts b/functions/src/webhooks/admin/payment-process/index.ts index 6298a6a18..8037c2ed6 100644 --- a/functions/src/webhooks/admin/payment-process/index.ts +++ b/functions/src/webhooks/admin/payment-process/index.ts @@ -1,6 +1,6 @@ import * as functions from 'firebase-functions'; +import { onCall } from 'firebase-functions/v2/https'; import { DateTime } from 'luxon'; -import { DEFAULT_REGION } from '../../../../../shared/src/firebase'; import { FirestoreAdmin } from '../../../../../shared/src/firebase/admin/FirestoreAdmin'; import { PaymentProcessTaskType } from '../../../../../shared/src/types/payment'; import { toPaymentDate } from '../../../../../shared/src/types/recipient'; @@ -15,10 +15,9 @@ export interface PaymentProcessProps { timestamp: number; // seconds } -export default functions - // Using v1 functions because tests are not supported with v2 yet: https://github.com/firebase/firebase-functions-test/issues/163 - .region(DEFAULT_REGION) - .https.onCall(async ({ type, timestamp }: PaymentProcessProps, { auth }) => { +export default onCall>( + { memory: '2GiB' }, + async ({ auth, data: { timestamp, type } }) => { const firestoreAdmin = new FirestoreAdmin(); await firestoreAdmin.assertGlobalAdmin(auth?.token?.email); const paymentDate = toPaymentDate(DateTime.fromSeconds(timestamp, { zone: 'utc' })); @@ -42,4 +41,5 @@ export default functions } return await task.run(paymentDate); - }); + }, +); diff --git a/functions/src/webhooks/admin/payment-process/tasks/PaymentCSVTask.test.ts b/functions/src/webhooks/admin/payment-process/tasks/PaymentCSVTask.test.ts index dcdbf7a84..6561d9abb 100644 --- a/functions/src/webhooks/admin/payment-process/tasks/PaymentCSVTask.test.ts +++ b/functions/src/webhooks/admin/payment-process/tasks/PaymentCSVTask.test.ts @@ -20,10 +20,11 @@ afterEach(async () => { }); test('GetPaymentCSV', async () => { - const result = await triggerFunction( - { type: PaymentProcessTaskType.GetPaymentCSV, timestamp: paymentDate.toSeconds() }, - { auth: { token: { email: 'admin@socialincome.org' } } }, - ); + const result = await triggerFunction({ + data: { type: PaymentProcessTaskType.GetPaymentCSV, timestamp: paymentDate.toSeconds() }, + // @ts-ignore + auth: { token: { email: 'admin@socialincome.org' } }, + }); const rows = result.split('\n').map((row: string) => row.split(',')); diff --git a/functions/src/webhooks/admin/payment-process/tasks/RegistrationCSVTask.test.ts b/functions/src/webhooks/admin/payment-process/tasks/RegistrationCSVTask.test.ts index 2142ec0f8..e9a112c2d 100644 --- a/functions/src/webhooks/admin/payment-process/tasks/RegistrationCSVTask.test.ts +++ b/functions/src/webhooks/admin/payment-process/tasks/RegistrationCSVTask.test.ts @@ -20,10 +20,11 @@ afterEach(async () => { }); test('GetRegistrationCSV', async () => { - const result = await triggerFunction( - { type: PaymentProcessTaskType.GetRegistrationCSV, timestamp: paymentDate.toSeconds() }, - { auth: { token: { email: 'admin@socialincome.org' } } }, - ); + const result = await triggerFunction({ + data: { type: PaymentProcessTaskType.GetRegistrationCSV, timestamp: paymentDate.toSeconds() }, + // @ts-ignore + auth: { token: { email: 'admin@socialincome.org' } }, + }); const rows = result.split('\n').map((row: string) => row.split(',')); expect(rows).toHaveLength(4); diff --git a/functions/src/webhooks/admin/payment-process/tasks/UpdateDatabaseEntriesTask.test.ts b/functions/src/webhooks/admin/payment-process/tasks/UpdateDatabaseEntriesTask.test.ts index c981e9491..be17a0b5c 100644 --- a/functions/src/webhooks/admin/payment-process/tasks/UpdateDatabaseEntriesTask.test.ts +++ b/functions/src/webhooks/admin/payment-process/tasks/UpdateDatabaseEntriesTask.test.ts @@ -34,12 +34,11 @@ afterEach(async () => { }); test('CreatePayments', async () => { - const result = await triggerFunction( - { type: PaymentProcessTaskType.CreatePayments, timestamp: paymentDate.toSeconds() }, - { - auth: { token: { email: 'admin@socialincome.org' } }, - }, - ); + const result = await triggerFunction({ + data: { type: PaymentProcessTaskType.CreatePayments, timestamp: paymentDate.toSeconds() }, + // @ts-ignore + auth: { token: { email: 'admin@socialincome.org' } }, + }); expect(result).toEqual( 'Set status of 3 payments to paid and created 3 payments for next month. Set status to "former" for 0 recipients and status to active for 1 recipients.', ); @@ -81,10 +80,11 @@ test('CreatePayments', async () => { expect(nextPayment.currency).toEqual('SLE'); } - const secondExecutionResult = await triggerFunction( - { type: PaymentProcessTaskType.CreatePayments, timestamp: paymentDate.toSeconds() }, - { auth: { token: { email: 'admin@socialincome.org' } } }, - ); + const secondExecutionResult = await triggerFunction({ + data: { type: PaymentProcessTaskType.CreatePayments, timestamp: paymentDate.toSeconds() }, + // @ts-ignore + auth: { token: { email: 'admin@socialincome.org' } }, + }); expect(secondExecutionResult).toEqual( 'Set status of 0 payments to paid and created 0 payments for next month. Set status to "former" for 0 recipients and status to active for 0 recipients.', ); diff --git a/functions/src/webhooks/twilio/TwilioOutgoingMessageHandler.ts b/functions/src/webhooks/twilio/TwilioOutgoingMessageHandler.ts index 800f50943..6bd47d179 100644 --- a/functions/src/webhooks/twilio/TwilioOutgoingMessageHandler.ts +++ b/functions/src/webhooks/twilio/TwilioOutgoingMessageHandler.ts @@ -1,4 +1,4 @@ -import * as functions from 'firebase-functions'; +import { onCall } from 'firebase-functions/v2/https'; import { MessageInstance } from 'twilio/lib/rest/api/v2010/account/message'; import { FirestoreAdmin } from '../../../../shared/src/firebase/admin/FirestoreAdmin'; import { Entity } from '../../../../shared/src/types'; @@ -20,7 +20,7 @@ export class TwilioOutgoingMessageHandler { } getFunction = () => - functions.https.onCall(async ({ recipients, template }: TwilioOutgoingMessageFunctionProps, { auth }) => { + onCall(async ({ data: { template, recipients }, auth }) => { await this.firestoreAdmin.assertGlobalAdmin(auth?.token?.email); let [successCount, skippedCount] = [0, 0]; for await (const { values: recipient, id } of recipients) { diff --git a/package-lock.json b/package-lock.json index da552b1ad..f6c74f2cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,10 +14,6 @@ "ui", "website" ], - "dependencies": { - "@types/react": "^18.3.18", - "@types/react-dom": "^18.2.25" - }, "devDependencies": { "firebase-tools": "13.29.1", "prettier": "3.4.2", @@ -94,7 +90,7 @@ "axios": "^1.7.7", "dotenv": "^16.4.5", "firebase-admin": "^12.7.0", - "firebase-functions": "5.1.1", + "firebase-functions": "^6.2.0", "handlebars": "^4.7.8", "handlebars-i18next": "^1.0.3", "i18next-resources-to-backend": "^1.2.1", @@ -10369,13 +10365,14 @@ "license": "MIT" }, "node_modules/@types/express": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz", - "integrity": "sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "license": "MIT", "dependencies": { "@types/body-parser": "*", - "@types/express-serve-static-core": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", "@types/serve-static": "*" } }, @@ -10391,6 +10388,18 @@ "@types/send": "*" } }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -10713,6 +10722,7 @@ "version": "18.3.5", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", + "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "^18.0.0" @@ -17704,15 +17714,15 @@ } }, "node_modules/firebase-functions": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-5.1.1.tgz", - "integrity": "sha512-KkyKZE98Leg/C73oRyuUYox04PQeeBThdygMfeX+7t1cmKWYKa/ZieYa89U8GHgED+0mF7m7wfNZOfbURYxIKg==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-6.2.0.tgz", + "integrity": "sha512-vfyyVHS8elxplzEQ9To+NaINRPFUsDasQrasTa2eFJBYSPzdhkw6rwLmvwyYw622+ze+g4sDIb14VZym+afqXQ==", "license": "MIT", "dependencies": { "@types/cors": "^2.8.5", - "@types/express": "4.17.3", + "@types/express": "^4.17.21", "cors": "^2.8.5", - "express": "^4.17.1", + "express": "^4.21.0", "protobufjs": "^7.2.2" }, "bin": { @@ -17722,7 +17732,7 @@ "node": ">=14.10.0" }, "peerDependencies": { - "firebase-admin": "^11.10.0 || ^12.0.0" + "firebase-admin": "^11.10.0 || ^12.0.0 || ^13.0.0" } }, "node_modules/firebase-functions-test": { @@ -22974,30 +22984,6 @@ "node": ">=14" } }, - "node_modules/jwks-rsa/node_modules/@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/jwks-rsa/node_modules/@types/express-serve-static-core": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, "node_modules/jwks-rsa/node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -35182,30 +35168,6 @@ } } }, - "node_modules/webpack-dev-server/node_modules/@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/webpack-dev-server/node_modules/@types/express-serve-static-core": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, "node_modules/webpack-dev-server/node_modules/ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", diff --git a/package.json b/package.json index 84c658d4a..2e97eaef7 100644 --- a/package.json +++ b/package.json @@ -50,9 +50,5 @@ "prettier-plugin-organize-imports": "4.1.0", "prettier-plugin-tailwindcss": "0.6.9", "typescript": "^5.7.2" - }, - "dependencies": { - "@types/react": "^18.3.18", - "@types/react-dom": "^18.2.25" } }