{"version":3,"sources":["../../src/lib/server/googleReviews.ts","../../src/server/googleReviewsHandler.ts"],"names":["kv"],"mappings":"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","file":"googleReviewsHandler.cjs","sourcesContent":["import { kv } from '@vercel/kv';\n\nconst env = import.meta.env;\n\nif (!process.env.GOOGLE_PLACES_API_KEY && env.GOOGLE_PLACES_API_KEY) {\n\tprocess.env.GOOGLE_PLACES_API_KEY = env.GOOGLE_PLACES_API_KEY;\n}\nif (!process.env.GOOGLE_PLACES_DEFAULT_PLACE_ID && env.GOOGLE_PLACES_DEFAULT_PLACE_ID) {\n\tprocess.env.GOOGLE_PLACES_DEFAULT_PLACE_ID = env.GOOGLE_PLACES_DEFAULT_PLACE_ID;\n}\nif (!process.env.KV_REST_API_URL && env.KV_REST_API_URL) {\n\tprocess.env.KV_REST_API_URL = env.KV_REST_API_URL;\n}\nif (!process.env.KV_REST_API_TOKEN && env.KV_REST_API_TOKEN) {\n\tprocess.env.KV_REST_API_TOKEN = env.KV_REST_API_TOKEN;\n}\nif (!process.env.KV_REST_API_READ_ONLY_TOKEN && env.KV_REST_API_READ_ONLY_TOKEN) {\n\tprocess.env.KV_REST_API_READ_ONLY_TOKEN = env.KV_REST_API_READ_ONLY_TOKEN;\n}\nconst CACHE_PREFIX = 'google-reviews:';\nconst DEFAULT_TTL_SECONDS =\n\tNumber.parseInt(env.GOOGLE_REVIEWS_CACHE_TTL ?? process.env.GOOGLE_REVIEWS_CACHE_TTL ?? '', 10) ||\n\t60 * 60 * 24; // 24h\nconst CACHE_EXPIRY_BUFFER = DEFAULT_TTL_SECONDS * 2;\nconst GOOGLE_API_BASE = 'https://places.googleapis.com/v1';\nconst FIELD_MASK = 'rating,userRatingCount,displayName';\nconst isKvConfigured = Boolean(\n\t(env.KV_REST_API_URL || process.env.KV_REST_API_URL) &&\n\t\t(env.KV_REST_API_TOKEN || process.env.KV_REST_API_TOKEN) &&\n\t\t(env.KV_REST_API_READ_ONLY_TOKEN || process.env.KV_REST_API_READ_ONLY_TOKEN),\n);\n\ntype CachePayload = {\n\tplaceId: string;\n\tbusinessName?: string;\n\tlanguageCode: string;\n\trating: number;\n\treviewCount: number;\n\treviewsUrl: string;\n\tupdatedAt: string;\n};\n\nexport type GoogleReviewSnapshot = CachePayload & {\n\tsource: 'fresh' | 'cache' | 'fallback';\n};\n\ntype InMemoryCacheEntry = {\n\tpayload: CachePayload;\n\texpiresAt: number;\n};\n\ndeclare global {\n\t// eslint-disable-next-line no-var\n\tvar __GOOGLE_REVIEWS_CACHE__: Map<string, InMemoryCacheEntry> | undefined;\n}\n\nconst inMemoryCache =\n\tglobalThis.__GOOGLE_REVIEWS_CACHE__ ??\n\t(globalThis.__GOOGLE_REVIEWS_CACHE__ = new Map<string, InMemoryCacheEntry>());\n\nconst normalizeLanguageCode = (languageCode?: string) => {\n\tconst normalized = languageCode?.trim().toLowerCase();\n\treturn normalized?.length ? normalized : 'en';\n};\nconst getCacheKey = (placeId: string, languageCode?: string) =>\n\t`${CACHE_PREFIX}${placeId}:${normalizeLanguageCode(languageCode)}`;\n\nasync function readCache(placeId: string, languageCode?: string): Promise<CachePayload | null> {\n\tconst key = getCacheKey(placeId, languageCode);\n\n\tif (isKvConfigured) {\n\t\tconst value = await kv.get<CachePayload>(key);\n\t\treturn value ?? null;\n\t}\n\n\tconst entry = inMemoryCache.get(key);\n\tif (!entry) {\n\t\treturn null;\n\t}\n\n\tif (entry.expiresAt <= Date.now()) {\n\t\tinMemoryCache.delete(key);\n\t\treturn null;\n\t}\n\n\treturn entry.payload;\n}\n\nasync function writeCache(placeId: string, payload: CachePayload): Promise<void> {\n\tconst key = getCacheKey(placeId, payload.languageCode);\n\n\tif (isKvConfigured) {\n\t\tawait kv.set(key, payload, { ex: CACHE_EXPIRY_BUFFER });\n\t\treturn;\n\t}\n\n\tinMemoryCache.set(key, {\n\t\tpayload,\n\t\texpiresAt: Date.now() + CACHE_EXPIRY_BUFFER * 1000,\n\t});\n}\n\ntype FetchOptions = {\n\tplaceId: string;\n\tlanguageCode: string;\n\tbusinessName?: string;\n};\n\nasync function fetchFromGoogle({ placeId, languageCode, businessName }: FetchOptions): Promise<CachePayload> {\n\tconst apiKey = env.GOOGLE_PLACES_API_KEY ?? process.env.GOOGLE_PLACES_API_KEY;\n\n\tif (!apiKey) {\n\t\tthrow new Error('GOOGLE_PLACES_API_KEY is not set');\n\t}\n\n\tif (!placeId) {\n\t\tthrow new Error('A Google placeId is required');\n\t}\n\n\tconst url = new URL(`${GOOGLE_API_BASE}/places/${placeId}`);\n\turl.searchParams.set('languageCode', languageCode);\n\n\tconst response = await fetch(url, {\n\t\theaders: {\n\t\t\t'X-Goog-Api-Key': apiKey,\n\t\t\t'X-Goog-FieldMask': FIELD_MASK,\n\t\t},\n\t});\n\n\tconst responseText = await response.text();\n\tif (!response.ok) {\n\t\tlet errorDetail = response.statusText;\n\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(responseText);\n\t\t\terrorDetail = parsed.error?.message ?? response.statusText;\n\t\t} catch {\n\t\t\t// Keep default message\n\t\t}\n\n\t\tthrow new Error(`Google Places API error (${response.status}): ${errorDetail}`);\n\t}\n\n\tconst data = JSON.parse(responseText);\n\tconst rating = typeof data.rating === 'number' ? data.rating : 0;\n\tconst reviewCount = typeof data.userRatingCount === 'number' ? data.userRatingCount : 0;\n\tconst displayName = data.displayName?.text as string | undefined;\n\tconst normalizedDisplayName = displayName?.trim().length ? displayName.trim() : undefined;\n\tconst normalizedInputName = businessName?.trim().length ? businessName.trim() : undefined;\n\tconst resolvedBusinessName = normalizedDisplayName ?? normalizedInputName;\n\n\treturn {\n\t\tplaceId,\n\t\tbusinessName: resolvedBusinessName,\n\t\tlanguageCode,\n\t\trating,\n\t\treviewCount,\n\t\treviewsUrl: buildReviewsUrl(placeId),\n\t\tupdatedAt: new Date().toISOString(),\n\t};\n}\n\nconst buildReviewsUrl = (placeId: string) =>\n\t`https://search.google.com/local/reviews?placeid=${encodeURIComponent(placeId)}`;\n\ntype SnapshotOptions = {\n\tlanguageCode?: string;\n\tbusinessName?: string;\n\tforceRefresh?: boolean;\n};\n\nexport async function getGoogleReviewSnapshot(\n\tplaceId: string,\n\toptions: SnapshotOptions = {},\n): Promise<{ data: GoogleReviewSnapshot; error?: string }> {\n\tconst languageCode = normalizeLanguageCode(options.languageCode);\n\tconst cached = await readCache(placeId, languageCode);\n\tconst now = Date.now();\n\tconst staleThreshold = DEFAULT_TTL_SECONDS * 1000;\n\tconst isCachedFresh = cached ? now - new Date(cached.updatedAt).getTime() <= staleThreshold : false;\n\n\tif (!options.forceRefresh && cached && isCachedFresh) {\n\t\treturn {\n\t\t\tdata: {\n\t\t\t\t...cached,\n\t\t\t\tsource: 'cache',\n\t\t\t},\n\t\t};\n\t}\n\n\ttry {\n\t\tconst fresh = await fetchFromGoogle({\n\t\t\tplaceId,\n\t\t\tlanguageCode,\n\t\t\tbusinessName: options.businessName ?? cached?.businessName,\n\t\t});\n\n\t\tawait writeCache(placeId, fresh);\n\n\t\treturn {\n\t\t\tdata: {\n\t\t\t\t...fresh,\n\t\t\t\tsource: 'fresh',\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : 'Unknown error talking to Google Places API';\n\n\t\tif (cached) {\n\t\t\treturn {\n\t\t\t\tdata: {\n\t\t\t\t\t...cached,\n\t\t\t\t\tsource: 'fallback',\n\t\t\t\t},\n\t\t\t\terror: message,\n\t\t\t};\n\t\t}\n\n\t\tthrow error instanceof Error ? error : new Error(message);\n\t}\n}\n","import type { APIRoute } from 'astro';\nimport { getGoogleReviewSnapshot } from '../lib/server/googleReviews';\n\nconst CACHE_HEADERS = {\n\t'Content-Type': 'application/json',\n\t'Cache-Control': 'no-store',\n\t'Access-Control-Allow-Origin': '*',\n};\n\nexport const googleReviewsHandler: APIRoute = async ({ request }) => {\n\tconst url = new URL(request.url);\n\tconst placeIdParam = url.searchParams.get('placeId');\n\tconst placeId = typeof placeIdParam === 'string' && placeIdParam.trim().length > 0 ? placeIdParam.trim() : null;\n\tconst languageCode = url.searchParams.get('languageCode')?.trim() || 'en';\n\tconst businessNameParam = url.searchParams.get('businessName');\n\tconst businessName = businessNameParam && businessNameParam.trim().length > 0 ? businessNameParam.trim() : undefined;\n\tconst forceParam = url.searchParams.get('force');\n\tconst cronSecret = process.env.CRON_SECRET;\n\tconst authHeader = request.headers.get('authorization') ?? '';\n\tconst bearerToken = authHeader.startsWith('Bearer ') ? authHeader.slice(7).trim() : null;\n\n\tif (!placeId) {\n\t\treturn new Response(JSON.stringify({ error: 'Missing placeId query parameter.' }), {\n\t\t\tstatus: 400,\n\t\t\theaders: CACHE_HEADERS,\n\t\t});\n\t}\n\n\tconst wantsForce = forceParam === 'true' || forceParam === '1';\n\tconst hasCronSecret = Boolean(cronSecret);\n\tconst forceRefresh = Boolean(wantsForce && hasCronSecret && bearerToken === cronSecret);\n\n\tif (wantsForce && (!cronSecret || !forceRefresh)) {\n\t\tconst message = cronSecret\n\t\t\t? 'Missing or invalid Authorization header for forced refresh.'\n\t\t\t: 'CRON_SECRET is not configured, forced refresh is disabled.';\n\t\treturn new Response(JSON.stringify({ error: message }), {\n\t\t\tstatus: 401,\n\t\t\theaders: CACHE_HEADERS,\n\t\t});\n\t}\n\n\ttry {\n\t\tconst { data, error } = await getGoogleReviewSnapshot(placeId, {\n\t\t\tlanguageCode,\n\t\t\tbusinessName,\n\t\t\tforceRefresh,\n\t\t});\n\n\t\treturn new Response(\n\t\t\tJSON.stringify({\n\t\t\t\tplaceId: data.placeId,\n\t\t\t\trating: data.rating,\n\t\t\t\treviewCount: data.reviewCount,\n\t\t\t\tbusinessName: data.businessName,\n\t\t\t\treviewsUrl: data.reviewsUrl,\n\t\t\t\tlanguageCode: data.languageCode,\n\t\t\t\tupdatedAt: data.updatedAt,\n\t\t\t\tsource: data.source,\n\t\t\t\tmeta: {\n\t\t\t\t\tcacheTtlSeconds: Number.parseInt(process.env.GOOGLE_REVIEWS_CACHE_TTL ?? '', 10) || 60 * 60 * 24,\n\t\t\t\t\tfetchError: error ?? null,\n\t\t\t\t},\n\t\t\t}),\n\t\t\t{\n\t\t\t\tstatus: 200,\n\t\t\t\theaders: CACHE_HEADERS,\n\t\t\t},\n\t\t);\n\t} catch (error) {\n\t\tconst message =\n\t\t\terror instanceof Error ? error.message : 'Failed to fetch Google Reviews and no cached value is available.';\n\n\t\treturn new Response(JSON.stringify({ error: message }), {\n\t\t\tstatus: 502,\n\t\t\theaders: CACHE_HEADERS,\n\t\t});\n\t}\n};\n\nexport default googleReviewsHandler;\n"]}