diff --git a/.env.example b/.env.example index 11c6e12..c1157ca 100644 --- a/.env.example +++ b/.env.example @@ -43,8 +43,13 @@ MINISTRY_PLATFORM_DEV_CLIENT_SECRET= # Enables address autocomplete on the Address Line 1 field in the Add/Edit # Family tool (and any other tool that uses GooglePlacesService). # -# When unset, the tool falls back to a plain text input — no errors, no UI -# change beyond losing the suggestion dropdown. +# Key resolution order (first non-empty wins): +# 1. MinistryPlatform dp_Configuration_Settings +# (Application_Code='COMMON', Key_Name='GoogleMapsAPIKey') +# 2. This GOOGLE_PLACES_API_KEY environment variable +# 3. If neither is set, the feature is disabled — the tool falls back to +# a plain text input with no errors and no UI change beyond losing the +# suggestion dropdown. # # Get a key at https://console.cloud.google.com and enable the "Places API # (New)" — the v1 REST endpoints. Calls go through our server actions, so diff --git a/src/app/(web)/tools/addeditfamily/actions.ts b/src/app/(web)/tools/addeditfamily/actions.ts index d70d856..9fe97a5 100644 --- a/src/app/(web)/tools/addeditfamily/actions.ts +++ b/src/app/(web)/tools/addeditfamily/actions.ts @@ -100,7 +100,7 @@ export async function placeAutocomplete( await getSession(); if (input.trim().length < 3) return []; const service = await GooglePlacesService.getInstance(); - if (!service.isEnabled()) return []; + if (!(await service.isEnabled())) return []; return service.autocomplete(input, sessionToken); } diff --git a/src/services/googlePlacesService.ts b/src/services/googlePlacesService.ts index d8d47e4..1c3f8f1 100644 --- a/src/services/googlePlacesService.ts +++ b/src/services/googlePlacesService.ts @@ -1,29 +1,66 @@ +import { MPHelper } from "@/lib/providers/ministry-platform"; import { GooglePlacesProvider } from "@/lib/providers/google-places"; import type { PlacePrediction, PlaceDetails } from "@/lib/providers/google-places"; export class GooglePlacesService { private static instance: GooglePlacesService; + private mp: MPHelper | null = null; private provider: GooglePlacesProvider | null = null; + // undefined = not yet resolved; null = resolved with no key (feature disabled) + private resolvedKey: string | null | undefined = undefined; private constructor() {} public static async getInstance(): Promise { if (!GooglePlacesService.instance) { GooglePlacesService.instance = new GooglePlacesService(); + GooglePlacesService.instance.mp = new MPHelper(); } return GooglePlacesService.instance; } - public isEnabled(): boolean { - return Boolean(process.env.GOOGLE_PLACES_API_KEY); + /** + * Resolves the Google Places API key with this precedence: + * 1. MP dp_Configuration_Settings (Application_Code='COMMON', Key_Name='GoogleMapsAPIKey') + * 2. GOOGLE_PLACES_API_KEY environment variable + * 3. null (feature disabled) + * + * Cached on the singleton so MP is queried at most once per process lifetime. + */ + private async resolveApiKey(): Promise { + if (this.resolvedKey !== undefined) return this.resolvedKey; + + try { + const rows = await this.mp!.getTableRecords<{ Value: string | null }>({ + table: "dp_Configuration_Settings", + select: "Value", + filter: "Application_Code='COMMON' AND Key_Name='GoogleMapsAPIKey'", + top: 1, + }); + const mpKey = rows[0]?.Value?.trim(); + if (mpKey) { + this.resolvedKey = mpKey; + return this.resolvedKey; + } + } catch { + // Swallow lookup failures (e.g. table permissions) and fall through to env var. + } + + const envKey = process.env.GOOGLE_PLACES_API_KEY?.trim(); + this.resolvedKey = envKey ? envKey : null; + return this.resolvedKey; + } + + public async isEnabled(): Promise { + return (await this.resolveApiKey()) !== null; } - private getProvider(): GooglePlacesProvider { + private async getProvider(): Promise { if (this.provider) return this.provider; - const apiKey = process.env.GOOGLE_PLACES_API_KEY; + const apiKey = await this.resolveApiKey(); if (!apiKey) { throw new Error( - "GOOGLE_PLACES_API_KEY is not configured. Add it to .env.local to enable address autocomplete.", + "Google Places API key is not configured. Set the 'GoogleMapsAPIKey' setting in MinistryPlatform (Application_Code='COMMON') or define GOOGLE_PLACES_API_KEY in .env.local.", ); } this.provider = new GooglePlacesProvider(apiKey); @@ -31,10 +68,12 @@ export class GooglePlacesService { } async autocomplete(input: string, sessionToken: string): Promise { - return this.getProvider().autocomplete(input, sessionToken); + const provider = await this.getProvider(); + return provider.autocomplete(input, sessionToken); } async getPlaceDetails(placeId: string, sessionToken: string): Promise { - return this.getProvider().getPlaceDetails(placeId, sessionToken); + const provider = await this.getProvider(); + return provider.getPlaceDetails(placeId, sessionToken); } }