Covers routes/activities that enter the journal without BRouter waytags (imports, uploads, pre-existing rows): - overpass-ways.server.ts: server-side Overpass client (way[highway] + geom in a bbox, configurable OVERPASS_URLS, timeout + oversized-bbox guard). - surface-match.server.ts: pure nearest-way map-matcher → per-segment surface/highway (unmatched → unknown). Unit-tested. - surface-backfill pg-boss job: load geom → skip if breakdown exists → Overpass → match → computeSurfaceBreakdown → store → emit `surface_breakdown` SSE to the owner. Idempotent, retry-safe, best-effort; registered in server.ts. - Enqueued from createActivity (imports/uploads) + owner-on-open in the route & activity detail loaders (non-Planner routes, old rows), deduped via singletonKey. - useSurfaceBackfillUpdates: detail pages subscribe to /api/events and revalidate() when their row's backfill lands (live bars, no reload). - Privacy manifest updated (DE + EN) for the Overpass bbox lookup. Tests: matchSurfaces unit; surface-backfill job (mocked Overpass/db/events: store + emit, skip-if-present, skip-if-no-ways). Verified the real Overpass→match→breakdown pipeline against a live Berlin bbox (509 ways → plausible asphalt/paving_stones/footway mix). typecheck + lint + unit (journal 333) green. Note: the worker runs via server.ts (prod/staging), not `react-router dev`, so the live job + SSE exercise on a deployed instance. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
42 lines
1.7 KiB
TypeScript
42 lines
1.7 KiB
TypeScript
import { defineJob, type JobDefinition, type TypedJobDefinition } from "@trails-cool/jobs";
|
|
import type { DeliveryPayload } from "../lib/federation-delivery.server.ts";
|
|
import type { GarminImportData } from "../lib/connected-services/providers/garmin/import.server.ts";
|
|
|
|
/**
|
|
* Every journal job queue and its payload shape, in one place. The
|
|
* typed `enqueue` / `enqueueOptional` in boss.server.ts and the
|
|
* `defineJournalJob` helper below both key off this map, so an enqueue
|
|
* site and its handler cannot drift apart, and a queue-name typo is a
|
|
* compile error instead of an orphaned queue.
|
|
*
|
|
* `void` marks cron-only jobs that are never enqueued with data.
|
|
*/
|
|
export interface JobPayloads {
|
|
"backfill-user-keypairs": Record<string, never>;
|
|
"consumed-jti-sweep": void;
|
|
"deliver-activity": DeliveryPayload;
|
|
"demo-bot-generate": void;
|
|
"demo-bot-prune": void;
|
|
"federation-kv-sweep": void;
|
|
"garmin-import-activity": GarminImportData;
|
|
"import-batches-sweep": void;
|
|
"komoot-bulk-import": { batchId: string; userId: string; serviceId: string };
|
|
"notifications-fanout": { activityId: string };
|
|
"notifications-purge": void;
|
|
"poll-remote-actor": { actorIri: string };
|
|
"poll-remote-outboxes": void;
|
|
"send-welcome-email": { email: string; username: string };
|
|
"surface-backfill": { kind: "route" | "activity"; id: string };
|
|
}
|
|
|
|
export type JobName = keyof JobPayloads;
|
|
|
|
/**
|
|
* defineJob, constrained to the journal's queue map: the name must be
|
|
* a known queue and the handler's payload type follows from it.
|
|
*/
|
|
export function defineJournalJob<K extends JobName>(
|
|
definition: TypedJobDefinition<JobPayloads[K]> & { name: K },
|
|
): JobDefinition {
|
|
return defineJob<JobPayloads[K]>(definition);
|
|
}
|