Eliminates the silent-failure pattern where a route/activity row could be committed with gpx IS NOT NULL but geom IS NULL if the PostGIS write failed after the row insert. - New gpx-save.server.ts owns all GPX validation (GpxValidationError, validateGpx) and PostGIS geometry writes (writeGeom, tx-aware) - createRoute, updateRoute, createActivity, createRouteFromActivity all wrapped in db.transaction() covering row + geom + version snapshot - demo-bot uses createRoute/createActivity instead of raw inserts; errors propagate loudly - Callback endpoint returns 400 for GpxValidationError instead of 401 - ADR-0006 documents the invariant for future explorers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
32 lines
2.4 KiB
Markdown
32 lines
2.4 KiB
Markdown
## Why
|
|
|
|
Route and activity rows can currently be saved with `geom IS NULL` when the PostGIS geometry write fails after the row insert succeeds — the error is swallowed silently with a `console.error`. This means missing map thumbnails, broken route-push deduplication, and corrupted spatial queries with no signal to the user or operator. The Planner callback endpoint also accepts GPX from an external source with no validation before writing.
|
|
|
|
## What Changes
|
|
|
|
- **New `gpx-save.server.ts` module** owns GPX validation and atomic geometry persistence; `setGeomFromGpx` moves here and becomes internal.
|
|
- **`validateGpx(gpx)`** parses the GPX string, checks ≥2 track points and valid coordinate ranges, throws `GpxValidationError` on failure, and returns the parsed result so callers don't re-parse.
|
|
- **`createRoute`, `updateRoute`, `createActivity`, `createRouteFromActivity`** all wrap their DB writes (row insert/update + geom write + version snapshot) in `db.transaction()` — partial state (row exists, geom NULL) is no longer possible.
|
|
- **`setGeomFromGpx` stops swallowing errors** — it throws, causing the transaction to roll back.
|
|
- **`demo-bot.server.ts`** migrated from raw inserts + `setGeomFromGpx` to `createRoute` / `createActivity`, eliminating a bypass of the new invariant.
|
|
- **`activities.server.ts`** removes inline `parseGpxAsync` call; uses the `ParsedGpx` returned by `validateGpx` for stat extraction instead.
|
|
- **ADR recorded**: PostGIS geometry writes are always transactional with row writes.
|
|
|
|
## Capabilities
|
|
|
|
### New Capabilities
|
|
|
|
- `gpx-save`: Atomic GPX validation and PostGIS geometry persistence for routes and activities.
|
|
|
|
### Modified Capabilities
|
|
|
|
*(none — no user-visible requirement changes; this is an implementation-layer correctness fix)*
|
|
|
|
## Impact
|
|
|
|
- `apps/journal/app/lib/routes.server.ts` — `setGeomFromGpx` export removed; callers updated.
|
|
- `apps/journal/app/lib/activities.server.ts` — inline `parseGpxAsync` removed; imports from `gpx-save`.
|
|
- `apps/journal/app/lib/demo-bot.server.ts` — raw insert pattern replaced with `createRoute` / `createActivity`.
|
|
- `apps/journal/app/routes/api.routes.$id.callback.ts` — no validation changes needed (validation now inside `updateRoute`); GPX errors surface as thrown exceptions → callers return 400.
|
|
- `@trails-cool/gpx` — no changes; `parseGpxAsync` is still the underlying parser.
|
|
- No API surface changes, no schema changes, no migration required.
|