Files
Nick SzaunigandClaude Opus 5 29f59adc6b
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 3m37s
feat: Kommando zum Nachschlagen der Google-Place-ID
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.

  pnpm places:find "Wabenhain Altenmünster"

gibt Name, Anschrift und GOOGLE_PLACE_ID je Treffer aus.

Das Kommando ist zugleich die schnellste Probe auf die Einrichtung:
kommt eine ID heraus, sind "Places API (New)", Abrechnung und Schluessel
richtig gesetzt und die Startseite kann die Rezensionen abrufen. Kommt
eine Ablehnung, nennt sie die drei tatsaechlich moeglichen Ursachen
(API nicht aktiviert, keine Abrechnung, OAuth-Client statt
API-Schluessel bzw. Referrer-Beschraenkung) — statt wie Google pauschal
auf den Schluessel zu zeigen.

Die Feldmaske fordert nur id, displayName und formattedAddress an: die
API rechnet nach angeforderten Feldern ab.

tsc: 0 Fehler. eslint: 0 Warnungen.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-19 06:32:05 +02:00

103 lines
3.8 KiB
TypeScript

/**
* 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<void> {
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"}`);
});