diff --git a/kits/delete-user-data/CHANGELOG.md b/kits/delete-user-data/CHANGELOG.md index 0e40c6a6f..e68e30e0e 100644 --- a/kits/delete-user-data/CHANGELOG.md +++ b/kits/delete-user-data/CHANGELOG.md @@ -1,3 +1,5 @@ +- fix: the functions deploy to `us-central1` by default again, as the extension did. The kit set no region, so the Firebase CLI deployed them to `us-east1`. The region is now read from `FUNCTION_DEFAULT_REGION` (default `us-central1`), the name `firebase ext:migrate` writes to `.env`, so each instance can deploy to its own region. `setGlobalOptions({ region })` no longer moves the functions; set `FUNCTION_DEFAULT_REGION` instead. + - fix: an Eventarc publish failure no longer fails the function. A channel that was deleted or is unreachable answers `PERMISSION_DENIED`, and the publish was awaited after the work, so the deletion completed but the invocation still failed. The publish failure is now logged as a warning and the invocation continues. Deploys also declare `eventarcpublishing.googleapis.com`, so the API is enabled on your project whether or not you use events. The failed invocation used to be redelivered, re-running a deletion that had already succeeded; it no longer is. - fix: `ENABLE_AUTO_DISCOVERY` accepts the extension's `yes` / `no` values again. The kit had declared it as a boolean param, which only counts the literal `true` as enabled, so a `.env` copied from an installed extension instance carried `yes` and silently left auto-discovery switched off. The param is now the extension's labeled `Yes` / `No` select and is read the same way the extension read it. diff --git a/kits/delete-user-data/README.md b/kits/delete-user-data/README.md index 465d7a7cd..9d7d64dda 100644 --- a/kits/delete-user-data/README.md +++ b/kits/delete-user-data/README.md @@ -115,6 +115,7 @@ overridden there. | `searchDepth` | `AUTO_DISCOVERY_SEARCH_DEPTH` | no | `3` | Discovery depth | | `searchFields` | `AUTO_DISCOVERY_SEARCH_FIELDS` | no | `id,uid,userId` | Fields treated as user ids | | `searchFunction` | `SEARCH_FUNCTION` | no | (empty) | URL of a custom search function that returns paths to delete | +| — | `FUNCTION_DEFAULT_REGION` | no | `us-central1` | Region the functions deploy to | | `discoveryTopicName` | `DISCOVERY_TOPIC_NAME` | no | `kit--discovery` | Pub/Sub discovery topic | | `deletionTopicName` | `DELETION_TOPIC_NAME` | no | `kit--deletion` | Pub/Sub deletion topic | @@ -187,11 +188,25 @@ You can also override both names with `DISCOVERY_TOPIC_NAME` and `DELETION_TOPIC_NAME`, which the extension did not allow. Change them together, since one function publishes to a topic the other is triggered by. -### Functions deploy to your default region +### Functions deploy to `FUNCTION_DEFAULT_REGION` -The extension deployed to the location you picked at install time. This kit -sets no region, so its functions deploy to your codebase's default -(`us-central1` unless you have changed it). +The extension deployed to the location you picked at install time +(`us-central1` by default). The kit reads the region from +`FUNCTION_DEFAULT_REGION` in each instance's `.env`, also defaulting to +`us-central1`, so instances can deploy to different regions. +`firebase ext:migrate` writes it with the region the extension ran in. + +```sh +# .env (or .env.) +FUNCTION_DEFAULT_REGION=us-central1 +``` + +Keep the functions in the same region as your Firestore database (for a `nam5` +database, `us-central1`), so user data is not read and deleted from another +region. + +The kit sets the region on each function, so `setGlobalOptions({ region })` +does not move them. Set `FUNCTION_DEFAULT_REGION` instead. ### All functions are 2nd gen diff --git a/kits/delete-user-data/src/config.ts b/kits/delete-user-data/src/config.ts index e172ae79e..dd6a5ff1d 100644 --- a/kits/delete-user-data/src/config.ts +++ b/kits/delete-user-data/src/config.ts @@ -163,6 +163,15 @@ const params = { }, }, }), + // The extension's install-time location, under the name the CLI's generated + // entry file and `ext:migrate` use. Defaulting here keeps the extension's + // `us-central1`; with no region set the CLI would deploy to `us-east1`. + functionRegion: defineString("FUNCTION_DEFAULT_REGION", { + label: "Cloud Functions location", + description: + "Where do you want to deploy the functions created for this kit? You usually want a location close to your database.", + default: "us-central1", + }), // Non-empty defaults so Pub/Sub trigger bindings resolve during deploy // discovery without freezing an empty topic name into the manifest. discoveryTopicName: defineString("DISCOVERY_TOPIC_NAME", { @@ -174,6 +183,7 @@ const params = { }; export const CONFIG_EXPRESSIONS = { + functionRegion: params.functionRegion, discoveryTopicName: params.discoveryTopicName, deletionTopicName: params.deletionTopicName, } as const; diff --git a/kits/delete-user-data/src/index.ts b/kits/delete-user-data/src/index.ts index cca4a43d0..ab566d447 100644 --- a/kits/delete-user-data/src/index.ts +++ b/kits/delete-user-data/src/index.ts @@ -109,19 +109,24 @@ function getContext(): HandlerContext { return ctx; } -export const clearData = onUserDeleted((event) => { - // The Auth event delivers no user record when the payload envelope is empty, - // so bail before getContext() rather than initialising the SDKs for nothing. - const uid = event.data?.uid; - if (!uid) { - logs.deletionEventMissingUid(event.id); - return; +export const clearData = onUserDeleted( + { region: CONFIG_EXPRESSIONS.functionRegion }, + (event) => { + // The Auth event delivers no user record when the payload envelope is + // empty, so bail before getContext() rather than initialising the SDKs for + // nothing. + const uid = event.data?.uid; + if (!uid) { + logs.deletionEventMissingUid(event.id); + return; + } + return handleClear(uid, getContext()); } - return handleClear(uid, getContext()); -}); +); export const handleSearch = onMessagePublished( { + region: CONFIG_EXPRESSIONS.functionRegion, topic: CONFIG_EXPRESSIONS.discoveryTopicName, }, (event) => runSearch(event.data.message.json, getContext()) @@ -129,6 +134,7 @@ export const handleSearch = onMessagePublished( export const handleDeletion = onMessagePublished( { + region: CONFIG_EXPRESSIONS.functionRegion, topic: CONFIG_EXPRESSIONS.deletionTopicName, }, (event) => runDeletion(event.data.message.json, getContext()) diff --git a/kits/delete-user-data/tests/region.test.ts b/kits/delete-user-data/tests/region.test.ts new file mode 100644 index 000000000..3cf0e63a1 --- /dev/null +++ b/kits/delete-user-data/tests/region.test.ts @@ -0,0 +1,37 @@ +/** + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, expect, test, vi } from "vitest"; + +const FUNCTIONS = ["clearData", "handleSearch", "handleDeletion"] as const; + +async function loadKit() { + vi.resetModules(); + return (await import("../src/index")) as Record; +} + +// The extension deployed to its install-time location, `us-central1` by +// default. With no region set the CLI would pick `us-east1` instead. +describe("function region", () => { + test("is read from FUNCTION_DEFAULT_REGION, defaulting to us-central1", async () => { + const kit = await loadKit(); + for (const name of FUNCTIONS) { + const region = kit[name].__endpoint.region; + expect(region.name).toBe("FUNCTION_DEFAULT_REGION"); + expect(region.options.default).toBe("us-central1"); + } + }); +});