Model Denmark's Dronebekendtgørelsen § 5 (on top of EU 2019/947) across all three tiers: schema, API Server, and Web App. Schema (migration 1720300700_add_logbook.js): two collections — `drones` (classification inputs: mtom, is_toy, autologs, c_class, operator no.) and `flights` (§5 minimum content + category/purpose/logging-path + operational maturity + a retention_until computed as operation_date + 5y). Locked API rules; access flows through the service account like users/orgs. API Server (logbook.go, logbook_export.go): /api/drones and /api/flights CRUD with per-role scoping in Go (user→own, admin→org, superadmin→all), plus GET /api/logbook/export (CSV — the "readable electronic format" for Trafikstyrelsen / pending police disclosure). Compliance is computed server-side per flight: exemption (toy / club-area / <250 g hobby), effective logging path, and red flags (autologs-without-FDR, specific-category-without- authorisation, missing §5 fields, past retention). Manual-path saves missing a §5 field are blocked (422). Web App: BFF proxies (export preserves the CSV Content-Type/Disposition), api.js client fns, and a Logbook.vue view (Flights/Drones tabs, inline forms, compliance badges + expandable detail, Export CSV) wired into Dashboard.vue, replacing the placeholder. Includes the rebuilt embedded dist bundle. 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.