Files
PilotVault/API Server/README.md
T
tajniak81andClaude Opus 4.8 afc6952eda Initial commit: PilotVault multi-service project
Add API Server (Go/PocketBase), Web App (Go BFF + Vue), Fly App
(Flutter/DJI MSDK), Adobe Plugin, and Docker/Docker AIO deployment
configs. Design assets and build artifacts are gitignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 11:43:33 +02:00

146 lines
7.1 KiB
Markdown

# PilotVault — API Server
Go service that is the **single entry point** for PilotVault. The Fly App streams
drone telemetry to it; the Web App and the built-in API Web Panel read live state
and issue commands. It keeps device state in memory and proxies authentication to
a PocketBase kept behind it (PocketBase's address is never exposed to clients).
```
Fly App ──► /ws/device ┐
├─► API Server (:8080) ──► PocketBase (auth only)
Web App ──► /ws/ui ┘
```
The server root (`GET /`) serves a PilotVault-branded **web panel**: a live health
readout plus a quick reference of both API audiences. Open
http://localhost:8080/ in a browser to check the server at a glance.
The panel is a **Vue 3 + Tailwind v4** app in [`panel/`](panel/), built into
`internal/api/dist` and embedded into the Go binary at compile time:
```powershell
cd panel
npm install
npm run build # outputs to ../internal/api/dist
cd ..; go build -o api-server.exe ./cmd/server # embeds the fresh dist
```
For panel development with hot reload (proxies `/api` to a running server on
`:8080`): `cd panel; npm run dev` → http://localhost:5174.
## Requirements
- Go 1.24+ (`go version`)
- Node 18+ (only for building the panel)
- A reachable PocketBase instance with a `users` auth collection (for login). To
persist user settings, that collection needs a `preferences` JSON field — see
[`pocketbase/README.md`](pocketbase/README.md).
## Configure
```powershell
Copy-Item .env.example .env
# edit .env: set POCKETBASE_URL (and CORS_ALLOW_ORIGINS if needed)
```
| Variable | Purpose | Default |
|---|---|---|
| `API_ADDR` | Listen address | `:8080` |
| `POCKETBASE_URL` | PocketBase base URL (login proxy). Legacy `PB_URL` still honoured. | `http://10.2.1.10:8026` |
| `CORS_ALLOW_ORIGINS` | Comma list, or `*` | `*` |
## Run
```powershell
./scripts/Run-ApiServer.ps1
# or: go run ./cmd/server
```
Health check: `GET http://localhost:8080/healthz`.
## API
### Client / dashboard endpoints
| Method | Path | Description |
|---|---|---|
| `GET` | `/healthz`, `/api/health` | Readiness probe + device count (public) |
| `POST` | `/api/auth/login` | `{email, password}` → PocketBase session (proxied) |
| `GET` | `/api/auth/validate` | Validate the `Authorization` token |
| `GET` | `/api/me` | Caller's `{id, email, role, organization, organizationName}` resolved from their token |
| `GET` | `/api/preferences` | Read the caller's saved settings blob (from their PocketBase user record) |
| `PUT` | `/api/preferences` | `{preferences}` → persist the caller's settings onto their user record |
| `GET` | `/api/users` | List users (**manager**; admin → own org, superadmin → all) |
| `POST` | `/api/users` | `{email, password, role, organization?}` → create a user (**manager**; admin scoped to own org) |
| `PATCH` | `/api/users/{id}` | Edit `{email?, role?, verified?, password?, organization?}` (**manager**; scope-checked; cannot demote self) |
| `DELETE` | `/api/users/{id}` | Delete a user (**manager**; admin → own org only; cannot delete self) |
| `GET` | `/api/orgs` | List organizations (**manager**; admin → own org, superadmin → all) |
| `POST` | `/api/orgs` | `{name}` → create an organization (**superadmin only**) |
| `PATCH` | `/api/orgs/{id}` | `{name}` → rename an organization (**superadmin only**) |
| `DELETE` | `/api/orgs/{id}` | Delete an empty organization (**superadmin only**) |
| `GET` | `/api/admin/pb-config` | Read the PocketBase connection + a live probe (**superadmin only**) |
| `POST` | `/api/admin/pb-config/test` | `{url?, adminEmail?, adminPassword?}` → probe a candidate connection without applying (**superadmin only**) |
| `PUT` | `/api/admin/pb-config` | `{url, adminEmail?, adminPassword?}` → apply at runtime + persist to `.env` (**superadmin only**) |
| `GET` | `/api/admin/plugins` | List plugins with state + last health (**superadmin only**) |
| `POST` | `/api/admin/plugins` | `{name, baseURL, provider?}` → register an external plugin, no rebuild (**superadmin only**) |
| `GET` | `/api/admin/plugins/{name}` | One plugin's view (**superadmin only**) |
| `PUT` | `/api/admin/plugins/{name}` | `{enabled?, config?}` → enable/disable + configure (**superadmin only**) |
| `DELETE` | `/api/admin/plugins/{name}` | Remove an external plugin (**superadmin only**) |
| `POST` | `/api/admin/plugins/{name}/health` | Run a health check now (**superadmin only**) |
| `GET` | `/api/devices` | List devices and their last-known state |
| `GET` | `/api/devices/{id}/track` | GPS track history for a device |
| `POST` | `/api/devices/{id}/command` | Send `{command, payload?}` to a connected device |
| `DELETE` | `/api/devices/{id}` | Forget a device's stored state |
| `GET` | `/ws/ui` | Live telemetry stream (WebSocket) |
### Device endpoints (Fly App)
| Method | Path | Description |
|---|---|---|
| `GET` | `/ws/device?id={id}` | Telemetry uplink (WebSocket) |
| `POST` | `/api/telemetry?id={id}` | Push a single telemetry event over HTTP |
### Telemetry events
Device messages carry a `type`: `registration`, `connection`, `battery`, or
`telemetry` (altitude, lat/lng, velocity, GPS sats, flight mode…). The server
merges them into a per-device `DeviceState` and fans each update out to every
connected dashboard as `{type:"update", device, event}`. `latitude`/`longitude`
samples are appended to the device's GPS track.
## Plugins
The server integrates external third-party services through a uniform **plugin**
contract (`internal/plugins`), managed by a superadmin from the panel. Two kinds
share one interface:
- **Built-in** — Go connectors compiled into the server (type-safe, first-party).
The reference example is **OpenSky Network** (`internal/plugins/builtin/opensky`),
a live ADS-B flight-state connector with an OAuth2 / anonymous auth provider.
Adding a *new* built-in needs a rebuild.
- **External** — a remote HTTP service **registered at runtime, no rebuild**. It
answers a small contract (`GET /health`, `GET /manifest`, `POST /invoke`) and can
run as its own process/container (the sandboxing story).
Enable-state and per-plugin config (secrets included) persist to a local,
gitignored `plugins.json` (override with `PLUGINS_FILE`), loaded on boot. Every
plugin exposes a real `HealthCheck`. Deferred extension points (invocation API,
retry/circuit-breaker, per-tenant credentials, audit logging) are documented in
`internal/plugins/doc.go`.
**Writing a plugin:** see the developer guide
[`internal/plugins/README.md`](internal/plugins/README.md) — step-by-step for both
built-in (Go) and external (HTTP, no rebuild) plugins, with complete examples.
## Project layout
```
cmd/server/main.go entry point, wiring, graceful shutdown
internal/config env/.env configuration
internal/hub in-memory device state + websocket fan-out (drone core)
internal/api router, middleware, handlers, embedded panel
internal/plugins plugin contract, manager, external kind + built-in connectors
panel/ Vue 3 + Tailwind v4 web panel (built into internal/api/dist)
scripts/ run helper
```