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>
146 lines
7.1 KiB
Markdown
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
|
|
```
|