mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 06:11:45 +00:00
fix(maps): prevent server crash when legacy Google photo URLs are stored as placeIds
Migration 107 only rewrote image_url rows matching /places/%/photos/%; URLs using the /place-photos/ or /places/<opaque> paths survived the upgrade and were passed verbatim to the Places API, producing a malformed request whose empty/HTML response body threw SyntaxError before detailsRes.ok was checked. The resulting rejection was leaked by placePhotoCache.setInFlight via an unhandled .finally() chain, triggering Node 22's default unhandledRejection=throw and terminating the process. - placePhotoCache: add .catch() after .finally() to prevent unhandled rejection crash - mapsService: reject URL-shaped placeIds early; read response as text before JSON.parse - migrations: add migration to rewrite remaining googleusercontent/places.googleapis URLs - MapView/MapViewGL: prefer stable proxy URL form of image_url before google_place_id Fixes #770
This commit is contained in:
@@ -1906,6 +1906,27 @@ function runMigrations(db: Database.Database): void {
|
||||
CREATE INDEX IF NOT EXISTS idx_day_accommodations_end_day_id ON day_accommodations(end_day_id);
|
||||
`);
|
||||
},
|
||||
// Migration: backfill remaining legacy Google photo URLs missed by Migration 107.
|
||||
// Migration 107 matched /places/%/photos/% only; lh3.googleusercontent.com URLs use
|
||||
// /place-photos/ or /places/<opaque-id> paths and were skipped. Rewrite any remaining
|
||||
// google-hosted URL to the stable proxy form using the row's google_place_id.
|
||||
() => {
|
||||
db.exec(`
|
||||
UPDATE places
|
||||
SET image_url = '/api/maps/place-photo/' || google_place_id || '/bytes',
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE google_place_id IS NOT NULL
|
||||
AND image_url IS NOT NULL
|
||||
AND image_url != ''
|
||||
AND image_url NOT LIKE '/api/maps/place-photo/%'
|
||||
AND (
|
||||
image_url LIKE 'http://%googleusercontent.com/%'
|
||||
OR image_url LIKE 'https://%googleusercontent.com/%'
|
||||
OR image_url LIKE 'http://%places.googleapis.com/%'
|
||||
OR image_url LIKE 'https://%places.googleapis.com/%'
|
||||
)
|
||||
`);
|
||||
},
|
||||
];
|
||||
|
||||
if (currentVersion < migrations.length) {
|
||||
|
||||
@@ -648,6 +648,12 @@ export async function getPlacePhoto(
|
||||
return null;
|
||||
}
|
||||
|
||||
// Reject URL-shaped placeIds — legacy DBs may store raw photo URLs in image_url
|
||||
if (/^https?:\/\//i.test(placeId)) {
|
||||
placePhotoCache.markError(placeId);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Google Photos — fetch details to get photo name
|
||||
const detailsRes = await googleFetch(`https://places.googleapis.com/v1/places/${placeId}`, `getPlacePhoto/details(${placeId})`, {
|
||||
headers: {
|
||||
@@ -655,13 +661,15 @@ export async function getPlacePhoto(
|
||||
'X-Goog-FieldMask': 'photos',
|
||||
},
|
||||
});
|
||||
const details = await detailsRes.json() as GooglePlaceDetails & { error?: { message?: string } };
|
||||
|
||||
const body = await detailsRes.text();
|
||||
if (!detailsRes.ok) {
|
||||
console.error('Google Places photo details error:', details.error?.message || detailsRes.status);
|
||||
console.error('Google Places photo details error:', detailsRes.status, body.slice(0, 200));
|
||||
placePhotoCache.markError(placeId);
|
||||
return null;
|
||||
}
|
||||
let details: GooglePlaceDetails & { error?: { message?: string } };
|
||||
try { details = body ? JSON.parse(body) : { photos: [] }; }
|
||||
catch { placePhotoCache.markError(placeId); return null; }
|
||||
|
||||
if (!details.photos?.length) {
|
||||
placePhotoCache.markError(placeId);
|
||||
|
||||
@@ -96,7 +96,9 @@ export function getInFlight(placeId: string): Promise<{ filePath: string; attrib
|
||||
|
||||
export function setInFlight(placeId: string, promise: Promise<{ filePath: string; attribution: string | null } | null>): void {
|
||||
inFlight.set(placeId, promise);
|
||||
promise.finally(() => inFlight.delete(placeId));
|
||||
promise
|
||||
.finally(() => inFlight.delete(placeId))
|
||||
.catch(() => { /* awaiter logs; this .catch only prevents unhandledRejection */ });
|
||||
}
|
||||
|
||||
export function serveFilePath(placeId: string): string | null {
|
||||
|
||||
Reference in New Issue
Block a user