diff --git a/server/feeds/location.ts b/server/feeds/location.ts index 40e025c..fc95901 100644 --- a/server/feeds/location.ts +++ b/server/feeds/location.ts @@ -101,29 +101,50 @@ export default defineFeed({ const interpClauses: string[] = []; for (const interp of interps) { + // Staged locally: an interpretation that turns out to be unsatisfiable + // must not leave its bind values behind, or every later placeholder + // number would refer to the wrong parameter. const matches: string[] = []; + const staged: any[] = []; + let next = p; + let usable = true; + if (interp.locality) { - matches.push(`UPPER(json_extract(t.address, '$.locality')) = UPPER($${p++})`); - params.push(interp.locality); + matches.push(`UPPER(json_extract(t.address, '$.locality')) = UPPER($${next++})`); + staged.push(interp.locality); } if (interp.region) { - matches.push(`UPPER(json_extract(t.address, '$.region')) = UPPER($${p++})`); - params.push(interp.region); + matches.push(`UPPER(json_extract(t.address, '$.region')) = UPPER($${next++})`); + staged.push(interp.region); } if (interp.country) { - // Expand "US"/"USA"/etc. all together. + // Expand "US"/"USA"/etc. all together. A single-word display name is + // tried as a country first ("Greece"), so this is routinely handed + // something that is not one ("Waldport") and gets back no aliases. + // Emitting `IN ()` is a SQLite syntax error that fails the entire + // query — including the sibling locality interpretation that would + // have matched — so drop this interpretation instead. const aliases = expandCountryAliases(interp.country); - const placeholders = aliases.map(() => `$${p++}`).join(","); - matches.push(`UPPER(json_extract(t.address, '$.country')) IN (${placeholders})`); - params.push(...aliases); + if (aliases.length === 0) { + usable = false; + } else { + const placeholders = aliases.map(() => `$${next++}`).join(","); + matches.push(`UPPER(json_extract(t.address, '$.country')) IN (${placeholders})`); + staged.push(...aliases); + } } if (interp.regionNullOrEqual) { matches.push( - `(json_extract(t.address, '$.region') IS NULL OR UPPER(json_extract(t.address, '$.region')) = UPPER($${p++}))`, + `(json_extract(t.address, '$.region') IS NULL OR UPPER(json_extract(t.address, '$.region')) = UPPER($${next++}))`, ); - params.push(interp.regionNullOrEqual); + staged.push(interp.regionNullOrEqual); + } + + if (usable && matches.length) { + interpClauses.push(`(${matches.join(" AND ")})`); + params.push(...staged); + p = next; } - if (matches.length) interpClauses.push(`(${matches.join(" AND ")})`); } // Also match records whose raw location.name equals the requested name — diff --git a/server/helpers/country.ts b/server/helpers/country.ts index a712faa..8f4523d 100644 --- a/server/helpers/country.ts +++ b/server/helpers/country.ts @@ -73,7 +73,11 @@ export function expandCountryAliases(raw: string): string[] { for (const [alias, c] of Object.entries(COUNTRY_ALIASES)) { if (c === canon) set.add(alias); } - const fullName = regionNames?.of(canon); - if (fullName) set.add(fullName.toUpperCase()); + try { + const fullName = regionNames?.of(canon); + if (fullName) set.add(fullName.toUpperCase()); + } catch { + // Not a region code — the canonical string alone is all we can offer. + } return [...set]; } diff --git a/server/xrpc/getGallery.ts b/server/xrpc/getGallery.ts index 6e2de3f..c9e41a1 100644 --- a/server/xrpc/getGallery.ts +++ b/server/xrpc/getGallery.ts @@ -4,51 +4,16 @@ import { hydrateGalleries } from "../hydrate/galleries.ts"; import { resolveAtUri } from "../helpers/resolveHandle.ts"; export default defineQuery("social.grain.unspecced.getGallery", async (ctx) => { - const { ok, params, db } = ctx; + const { ok, params, db, getRecords } = ctx; let { gallery: galleryUri } = params; // Resolve handle in AT URI if needed const resolved = await resolveAtUri(db, galleryUri); if (resolved) galleryUri = resolved; - const rows = (await db.query(`SELECT * FROM "social.grain.gallery" WHERE uri = $1`, [ - galleryUri, - ])) as Array<{ - uri: string; - did: string; - cid: string; - handle: string | null; - indexed_at: string | null; - title: string; - description: string | null; - facets: string | null; - labels: string | null; - location: string | null; - updated_at: string | null; - created_at: string; - }>; - - const row = rows[0]; - if (!row) throw new InvalidRequestError("Gallery not found"); - - const record = { - title: row.title, - description: row.description ?? undefined, - facets: row.facets ? JSON.parse(row.facets) : undefined, - labels: row.labels ? JSON.parse(row.labels) : undefined, - location: row.location ? JSON.parse(row.location) : undefined, - updatedAt: row.updated_at ?? undefined, - createdAt: row.created_at, - }; - - const galleryRow = { - uri: row.uri, - did: row.did, - cid: row.cid, - handle: row.handle ?? undefined, - indexed_at: row.indexed_at ?? undefined, - value: record as Gallery, - }; + const recordsMap = await getRecords("social.grain.gallery", [galleryUri]); + const galleryRow = recordsMap.get(galleryUri); + if (!galleryRow) throw new InvalidRequestError("Gallery not found"); const [galleryView] = await hydrateGalleries(ctx, [galleryRow]);