/** * Sucht die Place-ID eines Google-Maps-Eintrags. * * Die Place-ID steht NICHT in der Google Cloud Console — dort gibt es nur den * API-Schluessel. Sie gehoert zum Eintrag bei Google Maps und muss ueber die * Places API nachgeschlagen werden. Genau das tut dieses Skript. * * Es ist zugleich der schnellste Weg, die Einrichtung zu pruefen: kommt hier * eine ID heraus, sind API, Abrechnung und Schluessel richtig gesetzt und die * Startseite kann die Rezensionen abrufen. Kommt stattdessen eine * Fehlermeldung, benennt sie die Ursache — anders als die Meldung der alten * API, die bei nicht aktivierter Schnittstelle behauptet, der Schluessel sei * ungueltig. * * Aufruf: * pnpm places:find "Wabenhain Altenmünster" */ import "dotenv/config"; const SEARCH_URL = "https://places.googleapis.com/v1/places:searchText"; const TIMEOUT_MS = 10_000; interface SearchResponse { places?: { id?: string; displayName?: { text?: string }; formattedAddress?: string; }[]; error?: { message?: string; status?: string }; } function fail(message: string): never { console.error(`\n✖ ${message}\n`); process.exit(1); } async function main(): Promise { const query = process.argv.slice(2).join(" ").trim(); if (!query) { fail('Bitte einen Suchbegriff angeben, z.B.: pnpm places:find "Wabenhain Altenmünster"'); } const apiKey = process.env.GOOGLE_PLACES_API_KEY?.trim(); if (!apiKey) { fail( "GOOGLE_PLACES_API_KEY ist nicht gesetzt. Den API-Schluessel aus der Google Cloud Console\n" + " in die .env eintragen (Anmeldedaten → API-Schluessel, NICHT OAuth-Client)." ); } const response = await fetch(SEARCH_URL, { method: "POST", headers: { "Content-Type": "application/json", "X-Goog-Api-Key": apiKey, // Nur die drei Felder, die unten ausgegeben werden — die API rechnet // nach angeforderten Feldern ab. "X-Goog-FieldMask": "places.id,places.displayName,places.formattedAddress", }, body: JSON.stringify({ textQuery: query, languageCode: "de" }), signal: AbortSignal.timeout(TIMEOUT_MS), }); const data = (await response.json().catch(() => null)) as SearchResponse | null; if (!response.ok || data?.error) { const detail = data?.error?.message ?? `HTTP ${response.status}`; fail( `Google hat die Anfrage abgelehnt: ${detail}\n\n` + " Die drei haeufigsten Ursachen, in dieser Reihenfolge:\n" + ' 1. "Places API (New)" ist im Projekt nicht aktiviert (die gleichnamige ALTE\n' + " API reicht nicht — sie laesst sich in neuen Projekten gar nicht mehr\n" + " aktivieren).\n" + " 2. Am Projekt ist keine Abrechnung hinterlegt.\n" + " 3. Der Wert ist ein OAuth-Client statt eines API-Schluessels, oder der\n" + " Schluessel traegt eine Referrer-Beschraenkung — dieser Abruf laeuft\n" + " serverseitig und hat keinen Referrer." ); } const places = data?.places ?? []; if (places.length === 0) { fail(`Kein Eintrag zu "${query}" gefunden. Genauer suchen, z.B. mit Ort oder Strasse.`); } console.log(`\nTreffer für "${query}":\n`); for (const place of places) { console.log(` ${place.displayName?.text ?? "(ohne Namen)"}`); console.log(` ${place.formattedAddress ?? "(ohne Adresse)"}`); console.log(` GOOGLE_PLACE_ID=${place.id ?? "(ohne ID)"}\n`); } console.log( "Den passenden Wert als GOOGLE_PLACE_ID eintragen — in der .env fuer lokal,\n" + "in k8s/01-configmap.yaml fuer den Cluster.\n" ); } main().catch((error: unknown) => { // Netzwerkfehler und Zeitueberschreitungen landen hier; alles Fachliche ist // oben bereits mit einer deutlicheren Meldung abgefangen. fail(`Abruf fehlgeschlagen: ${error instanceof Error ? error.message : "Unbekannter Fehler"}`); });