getSerialNumber() is a BaseComponent method, so every component answers for
itself — and the bridge reads it off the flight controller. A Mavic Pro reports
08RDE1J00103H1 (what DJI Go labels "Flight Controller SN") where the airframe
sticker, and the registration, say 08QDE3H012032E. We were publishing the former
as the drone's serial, onto records that exist to satisfy BEK 1649 §5.
Same trap as 002e484, where a component's own firmware stood in for the
aircraft's, but with no correct source to switch to: MSDK v4 exposes no
aircraft-level serial at all — BaseProduct offers only the model and the
firmware package version — so the registered serial can only be typed by hand.
So split the two rather than pick one:
serial the airframe's, hand-entered, and the only one that
reaches the logbook and the CSV export
flight_controller_serial what the aircraft reports; auto-filled on connect,
and what POST /api/drones/auto now upserts on
Keying auto-add on the flight controller's serial keeps the fleet recognising a
connected drone without typing — it is stable per airframe — while leaving the
compliance record's serial to the pilot. A flight controller swapped in a repair
now costs a duplicate fleet entry to merge, where before it would have quietly
rewritten what the logbook claimed the drone was.
Note droneInput.payload() is a whole-record write, so any UI editing a drone must
round-trip flightControllerSerial; blanking it forks the drone into a duplicate
on its next connect. Drones.vue carries it through the edit form for that reason.
The migration copies existing serials into flight_controller_serial rather than
moving them: every current value came from auto-add and is therefore a flight
controller's, but a pilot may since have corrected one by hand and this cannot
tell them apart. Copying keeps auto-add matching the airframes it matched before.
Applied to the remote PocketBase, where drones held no records, so the backfill
was a no-op there.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PocketBase — PilotVault schema
PilotVault adds an organizations collection and three fields to the users
auth collection:
preferences(JSON) — each user's settings blob. Written with the user's own token, so PocketBase's default owner-only update rule (@request.auth.id = id) is all the authorization needed.role(select:superadmin|admin|user) — the user-rights level. A superadmin spans every organization; an admin is scoped to their own organization (may manage its users and admins, but not superadmins); a user has no management rights. Missing/empty is treated asuser.organization(relation →organizations, maxSelect 1, optional) — which org the user belongs to. Nullable: a user may belong to no organization.
The organizations collection is a plain base collection with a unique
name. It is reached only through the API Server's superuser service account
(its API rules stay locked to superusers), the same way user management works.
User + org management requires a service account
Listing/creating/deleting users and organizations is done by the API Server
using a superuser service account (POCKETBASE_ADMIN_EMAIL /
POCKETBASE_ADMIN_PASSWORD), but only after verifying the caller's own token
resolves to a manager role (admin for user management, superadmin for org
management). This is the single place the server uses elevated PocketBase
credentials; without the env vars, the /api/users and /api/orgs endpoints
return 503 and the rest is unaffected.
Preferences never need the service account — they use the caller's own token.
Add the schema
Pick one of the following.
Option A — migration (recommended)
Copy the migration files into your PocketBase deployment's pb_migrations/
directory and restart PocketBase (migrations run automatically on boot; they
target the PocketBase v0.22+/v0.23 JS migration API). They are idempotent, so
they are safe even if the schema was already provisioned live:
pb_migrations/1720300000_add_users_preferences.jspb_migrations/1720300100_add_users_role.jspb_migrations/1720300200_add_organizations.jspb_migrations/1720300300_add_users_organization.jspb_migrations/1720300400_extend_users_role_superadmin.jspb_migrations/1720300500_seed_orgs_and_users.js— seeds the PilotVault org + baseline accounts
Option B — Admin UI (any version)
- Open the PocketBase Admin UI → Collections → New collection
organizations(base); add a text fieldname(required) with a unique index. - Collections →
users→ New field. Add JSON fieldpreferences, not required, max size ~5 MB. - Add Select field
role, valuessuperadmin,admin,user, max select 1. - Add Relation field
organization→organizations, max select 1, not required, cascade delete off. - Save.
Verify
With a normal user token you should be able to round-trip the field:
# 1) log in (PocketBase directly, or via the API Server /api/auth/login)
TOKEN=... # the "token" from the auth response
# 2) save
curl -X PATCH "$PB_URL/api/collections/users/records/$USER_ID" \
-H "Authorization: $TOKEN" -H "Content-Type: application/json" \
-d '{"preferences":{"fontSize":"lg","themeMode":"dark"}}'
# 3) read back
curl "$PB_URL/api/collections/users/auth-refresh" -X POST -H "Authorization: $TOKEN"
In the app the round-trip is: browser → GET/PUT /bff/preferences → API Server
GET/PUT /api/preferences → PocketBase user record.