Atomic GPX save: validate + persist row + geometry in one transaction
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>
This commit is contained in:
parent
8ae58a2d26
commit
78b8b8f55f
13 changed files with 624 additions and 182 deletions
32
openspec/changes/atomic-gpx-save/proposal.md
Normal file
32
openspec/changes/atomic-gpx-save/proposal.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
## 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue