Sync the Fly App's waypoint routes through the API Server
Routes saved in the Fly App's mission library now sync to a new per-user routes collection on the API Server, in the WaypointMissionSpec JSON format. The library pulls on load and pushes on every save and delete, merges last-writer-wins by updatedAt, keeps unacknowledged deletes as tombstones, and keeps working offline. Each signed-in account has its own copy on the device; routes saved while signed out go to the first account that signs in. The API Server gets list / get / upsert / delete route endpoints with a PocketBase migration, and the Web App a read-only Routes view on the dashboard (rebuilt dist). Tested against a fake PocketBase and a fake server only; not yet tried with a real sign-in on the tablet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e49f318a0e
commit
93f4a580e9
18 files changed
+1525
-43
No files matched your search
@@ -0,0 +1,87 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
|
||||
// Creates the `routes` collection: the pilots' saved waypoint missions (the Fly
|
||||
// App's mission library, GO 4-parity plan P7). Each row holds one route exactly
|
||||
// as the Fly App's `WaypointMissionSpec.toJson()` writes it (format version 1)
|
||||
// in `spec`, keyed by the app-generated `route_id` so a tablet can push and pull
|
||||
// without knowing PocketBase record ids.
|
||||
//
|
||||
// Sync is last-writer-wins by the route's own `updatedAt` (copied into
|
||||
// `route_updated_at` so the API Server can compare without parsing `spec`).
|
||||
// Deletes are kept as tombstones (`deleted` = true) so a device that was
|
||||
// offline learns about them instead of re-uploading the route.
|
||||
//
|
||||
// Like the logbook and documents, the collection is reached only through the
|
||||
// API Server's superuser service account, so its API rules stay locked
|
||||
// (superusers only); the API Server enforces per-role scoping in Go.
|
||||
//
|
||||
// Apply by copying into your PocketBase deployment's `pb_migrations/` directory
|
||||
// and restarting. Written for PocketBase v0.22+/v0.23. Idempotent: the
|
||||
// collection is created only if absent, so re-running is a no-op.
|
||||
//
|
||||
// Depends on 1720300200_add_organizations.js (organizations) and the `users`
|
||||
// auth collection.
|
||||
migrate(
|
||||
(app) => {
|
||||
// Idempotency guard.
|
||||
try {
|
||||
app.findCollectionByNameOrId('routes')
|
||||
return // already present
|
||||
} catch (_) {
|
||||
// create below
|
||||
}
|
||||
|
||||
const orgs = app.findCollectionByNameOrId('organizations')
|
||||
const users = app.findCollectionByNameOrId('users')
|
||||
|
||||
const routes = new Collection({
|
||||
type: 'base',
|
||||
name: 'routes',
|
||||
fields: [
|
||||
// The Fly App's own id (e.g. "wp-m1x2y3z"); unique across the store.
|
||||
{ name: 'route_id', type: 'text', required: true, max: 100 },
|
||||
{ name: 'name', type: 'text', max: 200, presentable: true },
|
||||
// The route itself, verbatim WaypointMissionSpec.toJson().
|
||||
{ name: 'spec', type: 'json', maxSize: 2000000 },
|
||||
// The spec's updatedAt (RFC 3339, UTC) — the last-writer-wins clock.
|
||||
{ name: 'route_updated_at', type: 'text', max: 40 },
|
||||
{ name: 'deleted', type: 'bool' },
|
||||
{
|
||||
name: 'owner',
|
||||
type: 'relation',
|
||||
required: false,
|
||||
collectionId: users.id,
|
||||
cascadeDelete: false,
|
||||
minSelect: 0,
|
||||
maxSelect: 1,
|
||||
presentable: false,
|
||||
},
|
||||
{
|
||||
name: 'organization',
|
||||
type: 'relation',
|
||||
required: false,
|
||||
collectionId: orgs.id,
|
||||
cascadeDelete: false,
|
||||
minSelect: 0,
|
||||
maxSelect: 1,
|
||||
presentable: false,
|
||||
},
|
||||
{ name: 'created', type: 'autodate', onCreate: true, onUpdate: false },
|
||||
{ name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
|
||||
],
|
||||
indexes: [
|
||||
'CREATE UNIQUE INDEX `idx_routes_route_id` ON `routes` (`route_id`)',
|
||||
'CREATE INDEX `idx_routes_owner` ON `routes` (`owner`)',
|
||||
'CREATE INDEX `idx_routes_org` ON `routes` (`organization`)',
|
||||
],
|
||||
})
|
||||
app.save(routes)
|
||||
},
|
||||
(app) => {
|
||||
try {
|
||||
app.delete(app.findCollectionByNameOrId('routes'))
|
||||
} catch (_) {
|
||||
// already gone
|
||||
}
|
||||
},
|
||||
)
|
||||
Reference in new issue
Block a user