fix: restore list map previews (batch geojson query)

Map previews were missing on every list view (activities, routes, home,
profile, feed): the batch geojson helpers used `WHERE id = ANY(${ids}::text[])`,
but drizzle expands a JS array in a sql template to `($1,$2,...)`, producing the
invalid `ANY((...)::text[])`. That throws, and the helpers' `try/catch` swallowed
it — so every card fell back to "No map preview".

Rewrite getSimplifiedActivityGeojsonBatch (activities) and getSimplifiedGeojsonBatch
(routes) to use the query builder: `inArray(table.id, ids)` for the id list +
a raw `ST_AsGeoJSON(ST_Simplify(geom, 0.001))` select column. Same result, valid
SQL.

E2E: list-map-previews.test.ts creates an activity with geometry and asserts a
Leaflet preview renders on /activities (no "No map preview"). Registered in
playwright.config. typecheck + lint + unit (journal 315) green; verified in the
browser.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-06-12 22:53:14 +02:00
parent 8cacc7d91e
commit 0ee4daaed1
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9
4 changed files with 62 additions and 14 deletions

View file

@ -1,5 +1,5 @@
import { randomUUID } from "node:crypto";
import { eq, desc, and, isNotNull, sql } from "drizzle-orm";
import { eq, desc, and, isNotNull, inArray, sql } from "drizzle-orm";
import { unionAll } from "drizzle-orm/pg-core";
import { getDb } from "./db.ts";
import { activities, routes, syncImports, users, follows, remoteActors } from "@trails-cool/db/schema/journal";
@ -376,12 +376,17 @@ async function getSimplifiedActivityGeojsonBatch(ids: string[]): Promise<Map<str
if (ids.length === 0) return map;
try {
const db = getDb();
const result = await db.execute(
sql`SELECT id, ST_AsGeoJSON(ST_Simplify(geom, 0.001)) as geojson
FROM journal.activities
WHERE id = ANY(${ids}::text[]) AND geom IS NOT NULL`,
);
for (const row of result as unknown as Array<{ id: string; geojson: string }>) {
// Use the query builder for the id list: a raw `ANY(${ids}::text[])`
// makes drizzle expand the array to `($1,$2,...)`, yielding the invalid
// `ANY((...)::text[])` — which throws and silently drops every preview.
const rows = await db
.select({
id: activities.id,
geojson: sql<string | null>`ST_AsGeoJSON(ST_Simplify(${activities.geom}, 0.001))`,
})
.from(activities)
.where(and(inArray(activities.id, ids), isNotNull(activities.geom)));
for (const row of rows) {
if (row.geojson) map.set(row.id, row.geojson);
}
} catch {

View file

@ -1,5 +1,5 @@
import { randomUUID } from "node:crypto";
import { eq, desc, and } from "drizzle-orm";
import { eq, desc, and, inArray, isNotNull } from "drizzle-orm";
import { getDb } from "./db.ts";
import { routes, routeVersions } from "@trails-cool/db/schema/journal";
import type { Visibility } from "@trails-cool/db/schema/journal";
@ -209,12 +209,17 @@ async function getSimplifiedGeojsonBatch(ids: string[]): Promise<Map<string, str
if (ids.length === 0) return map;
try {
const db = getDb();
const result = await db.execute(
sql`SELECT id, ST_AsGeoJSON(ST_Simplify(geom, 0.001)) as geojson
FROM journal.routes
WHERE id = ANY(${ids}::text[]) AND geom IS NOT NULL`,
);
for (const row of result as unknown as Array<{ id: string; geojson: string }>) {
// Query-builder id list: a raw `ANY(${ids}::text[])` makes drizzle expand
// the array to `($1,$2,...)`, yielding the invalid `ANY((...)::text[])`
// — it throws and silently drops every route preview.
const rows = await db
.select({
id: routes.id,
geojson: sql<string | null>`ST_AsGeoJSON(ST_Simplify(${routes.geom}, 0.001))`,
})
.from(routes)
.where(and(inArray(routes.id, ids), isNotNull(routes.geom)));
for (const row of rows) {
if (row.geojson) map.set(row.id, row.geojson);
}
} catch {